diff --git a/admin-frontend/src/store/modules/auth.js b/admin-frontend/src/store/modules/auth.js index cfbd20f22..b90d9667b 100644 --- a/admin-frontend/src/store/modules/auth.js +++ b/admin-frontend/src/store/modules/auth.js @@ -6,13 +6,6 @@ function isFollowUpVisit(jwtToken) { return !!jwtToken; } -function isExpiredToken(jwtToken) { - const now = Date.now().valueOf() / 1000; - const jwtPayload = jwtToken.split('.')[1]; - const payload = JSON.parse(window.atob(jwtPayload)); - return payload.exp <= now; -} - export const authStore = defineStore('auth', { namespaced: true, state: () => ({ @@ -90,10 +83,6 @@ export const authStore = defineStore('auth', { async getJwtToken() { await this.setError(false); if (isFollowUpVisit(this.jwtToken)) { - if (isExpiredToken(this.jwtToken)) { - await this.logout(); - return; - } const response = await AuthService.refreshAuthToken( this.jwtToken, diff --git a/backend/src/admin-app.ts b/backend/src/admin-app.ts index a19e97865..4caa18282 100644 --- a/backend/src/admin-app.ts +++ b/backend/src/admin-app.ts @@ -141,7 +141,7 @@ function addLoginPassportUse( //set access and refresh tokens profile.jwtFrontend = adminAuth.generateFrontendToken(); profile.jwt = accessToken; - profile._json = parseJwt(accessToken); + profile._json = utils.parseJwt(accessToken); profile.refreshToken = refreshToken; profile.idToken = idToken; return done(null, profile); @@ -150,14 +150,6 @@ function addLoginPassportUse( ); } -const parseJwt = (token) => { - try { - return JSON.parse(atob(token.split('.')[1])); - } catch (e) { - return null; - } -}; - //initialize our authentication strategy utils.getOidcDiscovery().then((oicdDiscoveryDocument) => { //OIDC Strategy is used for authorization diff --git a/backend/src/app.ts b/backend/src/app.ts index 0b484bdc8..4346171ce 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -161,7 +161,7 @@ function addLoginPassportUse( //set access and refresh tokens profile.jwtFrontend = publicAuth.generateFrontendToken(); profile.jwt = accessToken; - profile._json = parseJwt(accessToken); + profile._json = utils.parseJwt(accessToken); profile.refreshToken = refreshToken; profile.idToken = idToken; return done(null, profile); @@ -170,14 +170,6 @@ function addLoginPassportUse( ); } -const parseJwt = (token) => { - try { - return JSON.parse(atob(token.split('.')[1])); - } catch (e) { - return null; - } -}; - //initialize our authentication strategy utils.getOidcDiscovery().then((discovery) => { //OIDC Strategy is used for authorization diff --git a/backend/src/v1/services/utils-service.spec.ts b/backend/src/v1/services/utils-service.spec.ts index 1f927081a..98f64d368 100644 --- a/backend/src/v1/services/utils-service.spec.ts +++ b/backend/src/v1/services/utils-service.spec.ts @@ -1,7 +1,7 @@ import { utils } from './utils-service'; import { config } from '../../config'; import axios from 'axios'; -import { exceptions } from 'winston'; +import jwt from 'jsonwebtoken'; jest.mock('axios'); @@ -9,58 +9,113 @@ afterEach(() => { jest.clearAllMocks(); }); -describe('postDataToDocGenService (&& postData)', () => { - describe('when the config parameter is omitted', () => { - it('creates a new config', async () => { - const body = 'test'; - const url = 'http://localhost'; - const corr = '1234asdf'; - - const configResult = { - headers: { - 'x-correlation-id': corr, - 'x-api-key': config.get('docGenService:apiKey'), - }, - }; - - const spyPostData = jest.spyOn(axios, 'post').mockResolvedValue({ - data: 'test', +describe('utils-service', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('postDataToDocGenService (&& postData)', () => { + describe('when the config parameter is omitted', () => { + it('creates a new config', async () => { + const body = 'test'; + const url = 'http://localhost'; + const corr = '1234asdf'; + + const configResult = { + headers: { + 'x-correlation-id': corr, + 'x-api-key': config.get('docGenService:apiKey'), + }, + }; + + const spyPostData = jest.spyOn(axios, 'post').mockResolvedValue({ + data: 'test', + }); + + await utils.postDataToDocGenService(body, url, corr); + + expect(spyPostData).toHaveBeenCalledWith(url, body, configResult); }); + }); + describe('when the config parameter is provided', () => { + it('adds the required headers', async () => { + const body = 'test'; + const url = 'http://localhost'; + const corr = '1234asdf'; + + const configParam = { + headers: { + Accept: 'application/pdf', + }, + responseType: 'stream', + }; + + const configResult = { + headers: { + Accept: 'application/pdf', + 'x-correlation-id': corr, + 'x-api-key': config.get('docGenService:apiKey'), + }, + responseType: 'stream', + }; - await utils.postDataToDocGenService(body, url, corr); + const spyPostData = jest.spyOn(axios, 'post').mockResolvedValue({ + data: 'test', + }); - expect(spyPostData).toHaveBeenCalledWith(url, body, configResult); + await utils.postDataToDocGenService(body, url, corr, configParam); + + expect(spyPostData).toHaveBeenCalledWith(url, body, configResult); + }); }); }); - describe('when the config parameter is provided', () => { - it('adds the required headers', async () => { - const body = 'test'; - const url = 'http://localhost'; - const corr = '1234asdf'; - - const configParam = { - headers: { - Accept: 'application/pdf', - }, - responseType: 'stream', - }; - - const configResult = { - headers: { - Accept: 'application/pdf', - 'x-correlation-id': corr, - 'x-api-key': config.get('docGenService:apiKey'), - }, - responseType: 'stream', - }; - - const spyPostData = jest.spyOn(axios, 'post').mockResolvedValue({ - data: 'test', - }); - await utils.postDataToDocGenService(body, url, corr, configParam); + describe('parseJwt', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when the JWT is invalid', () => { + it('throws an error', () => { + jest.spyOn(jwt, 'decode').mockImplementationOnce(() => { + throw new Error('test'); + }); - expect(spyPostData).toHaveBeenCalledWith(url, body, configResult); + expect(utils.parseJwt('test')).toBe(null); + }); }); + + describe('when the JWT is valid', () => { + describe('with special characters', () => { + it('parses a JWT successfully', () => { + const expectedPayload = { + sub: '1234567890', + name: 'John Doe', + iat: 1516239022, + display_name: 'Kevin O’Riely', + }; + + const output = utils.parseJwt('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJkaXNwbGF5X25hbWUiOiJLZXZpbiBP4oCZUmllbHkifQ.ab6ATknTP8_gksT7mnGV9XdEbE8JatEEeAYD4ipPQMg'); + expect(output).toEqual(expectedPayload); + }); + }); + + describe('without special characters', () => { + it('parses a JWT successfully', () => { + const expectedPayload = { + sub: '1234567890', + name: 'John Doe', + iat: 1516239022, + display_name: 'Kevin ORiely', + }; + + const output = utils.parseJwt( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJkaXNwbGF5X25hbWUiOiJLZXZpbiBPUmllbHkifQ.Q7ZwUBIr5hwFuPKq4twT_nE7J3PVQj6hVukhT0xurrY', + ); + expect(output).toEqual(expectedPayload); + }); + }); + + }) + }); }); diff --git a/backend/src/v1/services/utils-service.ts b/backend/src/v1/services/utils-service.ts index dad0545b9..e19fbf3b7 100644 --- a/backend/src/v1/services/utils-service.ts +++ b/backend/src/v1/services/utils-service.ts @@ -2,6 +2,8 @@ import axios from 'axios'; import { NextFunction, Request, Response } from 'express'; import { config } from '../../config'; import { logger as log, logger } from '../../logger'; +import jsonwebtoken from 'jsonwebtoken'; + const fs = require('fs'); axios.interceptors.response.use((response) => { const headers = response.headers; @@ -23,6 +25,15 @@ axios.interceptors.response.use((response) => { return response; }); +const parseJwt = (token) => { + try { + return jsonwebtoken.decode(token); + } catch (e) { + logger.error(`Error parsing jwt: ${e}`); + return null; + } +}; + let discovery = null; async function getOidcDiscovery() { @@ -108,7 +119,8 @@ const utils = { asyncHandler: (fn) => (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }, - delay + delay, + parseJwt }; export { utils }; diff --git a/frontend/src/store/modules/auth.js b/frontend/src/store/modules/auth.js index a5b9697a8..6e338c499 100644 --- a/frontend/src/store/modules/auth.js +++ b/frontend/src/store/modules/auth.js @@ -6,13 +6,6 @@ function isFollowUpVisit(jwtToken) { return !!jwtToken; } -function isExpiredToken(jwtToken) { - const now = Date.now().valueOf() / 1000; - const jwtPayload = jwtToken.split('.')[1]; - const payload = JSON.parse(window.atob(jwtPayload)); - return payload.exp <= now; -} - export const authStore = defineStore('auth', { namespaced: true, state: () => ({ @@ -84,11 +77,6 @@ export const authStore = defineStore('auth', { async getJwtToken() { await this.setError(false); if (isFollowUpVisit(this.jwtToken)) { - if (isExpiredToken(this.jwtToken)) { - await this.logout(); - return; - } - const response = await AuthService.refreshAuthToken( this.jwtToken, localStorage.getItem('correlationID'),