diff --git a/.env.example b/.env.example index e72f599..aea48f4 100644 --- a/.env.example +++ b/.env.example @@ -39,4 +39,4 @@ DB_ADMIN_PWD=changeit ACCESS_TOKEN_VALIDITY_DAYS=30 REFRESH_TOKEN_VALIDITY_DAYS=120 TOKEN_ISSUER=afteracademy.com -TOKEN_AUDIENCE=afteracademy_users \ No newline at end of file +TOKEN_AUDIENCE=afteracademy.com \ No newline at end of file diff --git a/README.md b/README.md index 4fe82be..4ac9def 100644 --- a/README.md +++ b/README.md @@ -258,8 +258,7 @@ Following are the features of this project: Host: localhost:3000 x-api-key: GCMUDiuY5a7WvyUNt9n3QztToSHzK7Uj Content-Type: application/json - x-access-token: your_token_received_from_signup_or_login - x-user-id: your_user_id + Authorization: Bearer ``` * Response Body: 200 ```json diff --git a/src/auth/authUtils.ts b/src/auth/authUtils.ts index 9af20a6..6ba6e89 100755 --- a/src/auth/authUtils.ts +++ b/src/auth/authUtils.ts @@ -5,13 +5,19 @@ import { Types } from 'mongoose'; import User from '../database/model/User'; import { tokenInfo } from '../config'; -export const validateTokenData = async (payload: JwtPayload, userId: Types.ObjectId): Promise => { +export const getAccessToken = (authorization: string) => { + if (!authorization) throw new AuthFailureError('Invalid Authorization'); + if (!authorization.startsWith('Bearer ')) throw new AuthFailureError('Invalid Authorization'); + return authorization.split(' ')[1]; +}; + +export const validateTokenData = (payload: JwtPayload): boolean => { if (!payload || !payload.iss || !payload.sub || !payload.aud || !payload.prm || payload.iss !== tokenInfo.issuer || payload.aud !== tokenInfo.audience - || payload.sub !== userId.toHexString()) + || !Types.ObjectId.isValid(payload.sub)) throw new AuthFailureError('Invalid Access Token'); - return payload; + return true; }; export const createTokens = async (user: User, accessTokenKey: string, refreshTokenKey: string) diff --git a/src/auth/authentication.ts b/src/auth/authentication.ts index 44a0bfe..3fe9672 100755 --- a/src/auth/authentication.ts +++ b/src/auth/authentication.ts @@ -2,11 +2,10 @@ import express from 'express'; import { ProtectedRequest, Tokens } from 'app-request'; import UserRepo from '../database/repository/UserRepo'; import { AuthFailureError, AccessTokenError, TokenExpiredError } from '../core/ApiError'; -import JWT, { ValidationParams } from '../core/JWT'; +import JWT from '../core/JWT'; import KeystoreRepo from '../database/repository/KeystoreRepo'; import { Types } from 'mongoose'; -import { validateTokenData } from './authUtils'; -import { tokenInfo } from '../config'; +import { getAccessToken, validateTokenData } from './authUtils'; import validator, { ValidationSource } from '../helpers/validator'; import schema from './schema'; import asyncHandler from '../helpers/asyncHandler'; @@ -15,23 +14,18 @@ const router = express.Router(); export default router.use(validator(schema.auth, ValidationSource.HEADER), asyncHandler(async (req: ProtectedRequest, res, next) => { - req.accessToken = req.headers['x-access-token'].toString(); - - const user = await UserRepo.findById(new Types.ObjectId(req.headers['x-user-id'].toString())); - if (!user) throw new AuthFailureError('User not registered'); - req.user = user; + req.accessToken = getAccessToken(req.headers.authorization); // Express headers are auto converted to lowercase try { - const payload = await JWT.validate( - req.accessToken, - new ValidationParams(tokenInfo.issuer, tokenInfo.audience, user._id.toHexString())); - - const jwtPayload = await validateTokenData(payload, req.user._id); - const keystore = await KeystoreRepo.findforKey(req.user._id, payload.prm); + const payload = await JWT.validate(req.accessToken); + validateTokenData(payload); - if (!keystore || keystore.primaryKey !== jwtPayload.prm) - throw new AuthFailureError('Invalid access token'); + const user = await UserRepo.findById(new Types.ObjectId(payload.sub)); + if (!user) throw new AuthFailureError('User not registered'); + req.user = user; + const keystore = await KeystoreRepo.findforKey(req.user._id, payload.prm); + if (!keystore) throw new AuthFailureError('Invalid access token'); req.keystore = keystore; return next(); diff --git a/src/auth/schema.ts b/src/auth/schema.ts index 05f4d61..ddbacc8 100644 --- a/src/auth/schema.ts +++ b/src/auth/schema.ts @@ -1,12 +1,11 @@ import Joi from '@hapi/joi'; -import { JoiObjectId } from '../helpers/validator'; +import { JoiAuthBearer } from '../helpers/validator'; export default { apiKey: Joi.object().keys({ 'x-api-key': Joi.string().required() }).unknown(true), auth: Joi.object().keys({ - 'x-access-token': Joi.string().required(), - 'x-user-id': JoiObjectId().required(), + 'authorization': JoiAuthBearer().required(), }).unknown(true) }; \ No newline at end of file diff --git a/src/core/JWT.ts b/src/core/JWT.ts index d9e4e2d..3d205fe 100755 --- a/src/core/JWT.ts +++ b/src/core/JWT.ts @@ -34,56 +34,34 @@ export default class JWT { /** * This method checks the token and returns the decoded data when token is valid in all respect */ - public static async validate(token: string, validations: ValidationParams): Promise { + public static async validate(token: string): Promise { const cert = await this.readPublicKey(); try { // @ts-ignore - return await promisify(verify)(token, cert, validations); + return await promisify(verify)(token, cert); } catch (e) { Logger.debug(e); if (e && e.name === 'TokenExpiredError') throw new TokenExpiredError(); + // throws error if the token has not been encrypted by the private key throw new BadTokenError(); } } /** - * This method checks the token and returns the decoded data even when the token is expired + * Returns the decoded payload if the signature is valid even if it is expired */ - public static async decode(token: string, validations: ValidationParams): Promise { + public static async decode(token: string): Promise { const cert = await this.readPublicKey(); try { - // token is verified if it was encrypted by the private key - // and if is still not expired then get the payload // @ts-ignore - return await promisify(verify)(token, cert, validations); + return await promisify(verify)(token, cert, { ignoreExpiration: true }); } catch (e) { Logger.debug(e); - if (e && e.name === 'TokenExpiredError') { - // if the token has expired but was encryped by the private key - // then decode it to get the payload - // @ts-ignore - return decode(token); - } - else { - // throws error if the token has not been encrypted by the private key - // or has not been issued for the user - throw new BadTokenError(); - } + throw new BadTokenError(); } } } -export class ValidationParams { - issuer: string; - audience: string; - subject: string; - constructor(issuer: string, audience: string, subject: string) { - this.issuer = issuer; - this.audience = audience; - this.subject = subject; - } -} - export class JwtPayload { aud: string; sub: string; diff --git a/src/helpers/validator.ts b/src/helpers/validator.ts index d03a90c..7e671c9 100644 --- a/src/helpers/validator.ts +++ b/src/helpers/validator.ts @@ -21,6 +21,11 @@ export const JoiUrlEndpoint = () => Joi.string().custom((value: string, helpers) return value; }, 'Url Endpoint Validation'); +export const JoiAuthBearer = () => Joi.string().custom((value: string, helpers) => { + if (!value.startsWith('Bearer ')) return helpers.error('any.invalid'); + if (!value.split(' ')[1]) return helpers.error('any.invalid'); + return value; +}, 'Authorization Header Validation'); export default (schema: Joi.ObjectSchema, source: ValidationSource = ValidationSource.BODY) => (req: Request, res: Response, next: NextFunction) => { diff --git a/src/routes/v1/access/schema.ts b/src/routes/v1/access/schema.ts index 1d1542b..9d932ee 100644 --- a/src/routes/v1/access/schema.ts +++ b/src/routes/v1/access/schema.ts @@ -1,5 +1,5 @@ import Joi from '@hapi/joi'; -import { JoiObjectId } from '../../../helpers/validator'; +import { JoiAuthBearer } from '../../../helpers/validator'; export default { userCredential: Joi.object().keys({ @@ -10,8 +10,7 @@ export default { refreshToken: Joi.string().required().min(1), }), auth: Joi.object().keys({ - 'x-access-token': Joi.string().required().min(1), - 'x-user-id': JoiObjectId().required(), + 'authorization': JoiAuthBearer().required() }).unknown(true), signup: Joi.object().keys({ name: Joi.string().required().min(3), diff --git a/src/routes/v1/access/token.ts b/src/routes/v1/access/token.ts index ff71fcc..bd92b70 100644 --- a/src/routes/v1/access/token.ts +++ b/src/routes/v1/access/token.ts @@ -4,43 +4,33 @@ import { ProtectedRequest } from 'app-request'; import { Types } from 'mongoose'; import UserRepo from '../../../database/repository/UserRepo'; import { AuthFailureError, } from '../../../core/ApiError'; -import JWT, { ValidationParams } from '../../../core/JWT'; +import JWT from '../../../core/JWT'; import KeystoreRepo from '../../../database/repository/KeystoreRepo'; import crypto from 'crypto'; -import { validateTokenData, createTokens } from '../../../auth/authUtils'; +import { validateTokenData, createTokens, getAccessToken } from '../../../auth/authUtils'; import validator, { ValidationSource } from '../../../helpers/validator'; import schema from './schema'; import asyncHandler from '../../../helpers/asyncHandler'; -import { tokenInfo } from '../../../config'; const router = express.Router(); router.post('/refresh', validator(schema.auth, ValidationSource.HEADER), validator(schema.refreshToken), asyncHandler(async (req: ProtectedRequest, res, next) => { - req.accessToken = req.headers['x-access-token'].toString(); + req.accessToken = getAccessToken(req.headers.authorization); // Express headers are auto converted to lowercase - const user = await UserRepo.findById(new Types.ObjectId(req.headers['x-user-id'].toString())); + const accessTokenPayload = await JWT.decode(req.accessToken); + validateTokenData(accessTokenPayload); + + const user = await UserRepo.findById(new Types.ObjectId(accessTokenPayload.sub)); if (!user) throw new AuthFailureError('User not registered'); req.user = user; - const accessTokenPayload = await validateTokenData( - await JWT.decode(req.accessToken, - new ValidationParams( - tokenInfo.issuer, - tokenInfo.audience, - req.user._id.toHexString())), - req.user._id - ); + const refreshTokenPayload = await JWT.validate(req.body.refreshToken); + validateTokenData(refreshTokenPayload); - const refreshTokenPayload = await validateTokenData( - await JWT.validate(req.body.refreshToken, - new ValidationParams( - tokenInfo.issuer, - tokenInfo.audience, - req.user._id.toHexString())), - req.user._id - ); + if (accessTokenPayload.sub !== refreshTokenPayload.sub) + throw new AuthFailureError('Invalid access token'); const keystore = await KeystoreRepo.find( req.user._id, diff --git a/tests/.env.test.example b/tests/.env.test.example index 1a90325..eb486c8 100644 --- a/tests/.env.test.example +++ b/tests/.env.test.example @@ -32,4 +32,4 @@ DB_USER_PWD=changeit ACCESS_TOKEN_VALIDITY_DAYS=30 REFRESH_TOKEN_VALIDITY_DAYS=120 TOKEN_ISSUER=test.afteracademy.com -TOKEN_AUDIENCE=test.afteracademy_users \ No newline at end of file +TOKEN_AUDIENCE=test.afteracademy.com \ No newline at end of file diff --git a/tests/auth/apikey/unit.test.ts b/tests/auth/apikey/unit.test.ts index 2a04493..7cc3900 100644 --- a/tests/auth/apikey/unit.test.ts +++ b/tests/auth/apikey/unit.test.ts @@ -11,13 +11,13 @@ describe('apikey validation', () => { mockFindApiKey.mockClear(); }); - it('Should response with 400 if api-key header is not passed', async () => { + it('Should response with 400 if x-api-key header is not passed', async () => { const response = await request.get(endpoint); expect(response.status).toBe(400); expect(mockFindApiKey).not.toBeCalled(); }); - it('Should response with 403 if wrong api-key header is passed', async () => { + it('Should response with 403 if wrong x-api-key header is passed', async () => { const wrongApiKey = '123'; const response = await request .get(endpoint) @@ -27,7 +27,7 @@ describe('apikey validation', () => { expect(mockFindApiKey).toBeCalledWith(wrongApiKey); }); - it('Should response with 404 if correct api-key header is passed and when route is not handelled', async () => { + it('Should response with 404 if correct x-api-key header is passed and when route is not handelled', async () => { const response = await request .get(endpoint) .set('x-api-key', API_KEY); diff --git a/tests/auth/authUtils/unit.test.ts b/tests/auth/authUtils/unit.test.ts index 53f8c27..b20f211 100644 --- a/tests/auth/authUtils/unit.test.ts +++ b/tests/auth/authUtils/unit.test.ts @@ -12,20 +12,18 @@ describe('authUtils validateTokenData tests', () => { jest.resetAllMocks(); }); - it('Should throw error when user is different', async () => { - - const userId = new Types.ObjectId(); // Random Key + it('Should throw error when subject in not user id format', async () => { const payload = new JwtPayload( tokenInfo.issuer, tokenInfo.audience, - new Types.ObjectId().toHexString(), // Random Key + 'abc', ACCESS_TOKEN_KEY, tokenInfo.accessTokenValidityDays ); try { - await validateTokenData(payload, userId); + validateTokenData(payload); } catch (e) { expect(e).toBeInstanceOf(AuthFailureError); } @@ -33,38 +31,34 @@ describe('authUtils validateTokenData tests', () => { it('Should throw error when access token key is different', async () => { - const userId = new Types.ObjectId(); // Random Key - const payload = new JwtPayload( tokenInfo.issuer, tokenInfo.audience, - userId.toHexString(), + new Types.ObjectId().toHexString(), '123', tokenInfo.accessTokenValidityDays ); try { - await validateTokenData(payload, userId); + validateTokenData(payload); } catch (e) { expect(e).toBeInstanceOf(AuthFailureError); } }); - it('Should return same payload if all data is correct', async () => { - - const userId = new Types.ObjectId('553f8a4286f5c759f36f8e5b'); // Random Key + it('Should return true if all data is correct', async () => { const payload = new JwtPayload( tokenInfo.issuer, tokenInfo.audience, - userId.toHexString(), + new Types.ObjectId().toHexString(), // Random Key ACCESS_TOKEN_KEY, tokenInfo.accessTokenValidityDays ); - const validatedPayload = await validateTokenData(payload, userId); + const validatedPayload = validateTokenData(payload); - expect(validatedPayload).toMatchObject(payload); + expect(validatedPayload).toBeTruthy(); }); }); @@ -77,7 +71,7 @@ describe('authUtils createTokens function', () => { it('Should process and return accessToken and refreshToken', async () => { - const userId = new Types.ObjectId('553f8a4286f5c759f36f8e5b'); // Random Key + const userId = new Types.ObjectId(); // Random Key const tokens = await createTokens({ _id: userId }, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY); diff --git a/tests/auth/authentication/mock.ts b/tests/auth/authentication/mock.ts index dbb474e..8ce6378 100644 --- a/tests/auth/authentication/mock.ts +++ b/tests/auth/authentication/mock.ts @@ -3,35 +3,39 @@ import { API_KEY } from '../apikey/mock'; import User from '../../../src/database/model/User'; import { Types } from 'mongoose'; -import JWT, { ValidationParams, JwtPayload } from '../../../src/core/JWT'; +import JWT, { JwtPayload } from '../../../src/core/JWT'; import { BadTokenError } from '../../../src/core/ApiError'; import Keystore from '../../../src/database/model/Keystore'; +import * as authUtils from '../../../src/auth/authUtils'; +import { tokenInfo } from '../../../src/config'; export const ACCESS_TOKEN = 'xyz'; export const USER_ID = new Types.ObjectId(); // random id with object id format +export const getAccessTokenSpy = jest.spyOn(authUtils, 'getAccessToken'); + export const mockUserFindById = jest.fn(async (id: Types.ObjectId) => { if (USER_ID.equals(id)) return { _id: new Types.ObjectId(id) }; else return null; }); export const mockJwtValidate = jest.fn( - async (token: string, validations: ValidationParams): Promise => { - if (token == ACCESS_TOKEN) return { prm: 'abcdef' }; + async (token: string): Promise => { + if (token === ACCESS_TOKEN) return { + iss: tokenInfo.issuer, + aud: tokenInfo.audience, + sub: USER_ID.toHexString(), + iat: 1, + exp: 2, + prm: 'abcdef' + }; throw new BadTokenError(); }); export const mockKeystoreFindForKey = jest.fn( async (client: User, key: string): Promise => ({ client: client, primaryKey: key })); -export const mockValidateTokenData = - jest.fn(async (payload: JwtPayload, userId: Types.ObjectId): Promise => payload); - -jest.mock('../../../src/auth/authUtils', () => ({ - get validateTokenData() { return mockValidateTokenData; } -})); - jest.mock('../../../src/database/repository/UserRepo', () => ({ get findById() { return mockUserFindById; } })); @@ -46,8 +50,7 @@ export const addHeaders = (request: any) => request .set('Content-Type', 'application/json') .set('x-api-key', API_KEY); -export const addAuthHeaders = (request: any, userId: Types.ObjectId = USER_ID) => request +export const addAuthHeaders = (request: any, accessToken = ACCESS_TOKEN) => request .set('Content-Type', 'application/json') - .set('x-api-key', API_KEY) - .set('x-access-token', ACCESS_TOKEN) - .set('x-user-id', userId.toHexString()); + .set('Authorization', `Bearer ${accessToken}`) + .set('x-api-key', API_KEY); diff --git a/tests/auth/authentication/unit.test.ts b/tests/auth/authentication/unit.test.ts index e79a200..91845ed 100644 --- a/tests/auth/authentication/unit.test.ts +++ b/tests/auth/authentication/unit.test.ts @@ -1,6 +1,7 @@ import { - USER_ID, ACCESS_TOKEN, addHeaders, addAuthHeaders, - mockValidateTokenData, mockUserFindById, mockJwtValidate, mockKeystoreFindForKey + ACCESS_TOKEN, addHeaders, addAuthHeaders, + mockUserFindById, mockJwtValidate, mockKeystoreFindForKey, + getAccessTokenSpy } from './mock'; import app from '../../../src/app'; @@ -12,63 +13,52 @@ describe('authentication validation', () => { const request = supertest(app); beforeEach(() => { - mockValidateTokenData.mockClear(); - mockUserFindById.mockClear(); + getAccessTokenSpy.mockClear(); mockJwtValidate.mockClear(); + mockUserFindById.mockClear(); mockKeystoreFindForKey.mockClear(); }); - it('Should response with 400 if x-access-token header is not passed', async () => { - const response = await addHeaders(request.get(endpoint)) - .set('x-user-id', USER_ID.toHexString()); + it('Should response with 400 if Authorization header is not passed', async () => { + const response = await addHeaders(request.get(endpoint)); expect(response.status).toBe(400); - expect(response.body.message).toMatch(/x-access-token/); - expect(mockUserFindById).not.toBeCalled(); + expect(response.body.message).toMatch(/authorization/); + expect(getAccessTokenSpy).not.toBeCalled(); }); - it('Should response with 400 if x-user-id header is not passed', async () => { - const response = await addHeaders(request.get(endpoint)) - .set('x-access-token', ACCESS_TOKEN); - expect(response.status).toBe(400); - expect(response.body.message).toMatch(/x-user-id/); - expect(mockUserFindById).not.toBeCalled(); - }); - it('Should response with 400 if x-user-id header is not mongoose id', async () => { + it('Should response with 400 if Authorization header do not have Bearer', async () => { const response = await addHeaders(request.get(endpoint)) - .set('x-access-token', ACCESS_TOKEN) - .set('x-user-id', '123'); + .set('Authorization', '123'); expect(response.status).toBe(400); - expect(response.body.message).toMatch(/x-user-id/); - expect(mockUserFindById).not.toBeCalled(); + expect(response.body.message).toMatch(/authorization/); + expect(getAccessTokenSpy).not.toBeCalled(); }); - it('Should response with 401 if wrong x-user-id header is provided', async () => { + it('Should response with 401 if wrong Authorization header is provided', async () => { const response = await addHeaders(request.get(endpoint)) - .set('x-access-token', ACCESS_TOKEN) - .set('x-user-id', '5e7b8c22d347fc2407c564a6'); // some random mongoose id - expect(response.status).toBe(401); - expect(response.body.message).toMatch(/not registered/); - expect(mockUserFindById).toBeCalledTimes(1); - }); - - it('Should response with 401 if wrong x-access-token header is provided', async () => { - const response = await addHeaders(request.get(endpoint)) - .set('x-access-token', '123') - .set('x-user-id', USER_ID); + .set('Authorization', 'Bearer 123'); expect(response.status).toBe(401); expect(response.body.message).toMatch(/token/i); - expect(mockUserFindById).toBeCalledTimes(1); + expect(getAccessTokenSpy).toBeCalledTimes(1); + expect(getAccessTokenSpy).toBeCalledWith('Bearer 123'); + expect(getAccessTokenSpy).toReturnWith('123'); expect(mockJwtValidate).toBeCalledTimes(1); + expect(mockJwtValidate).toBeCalledWith('123'); + expect(mockUserFindById).not.toBeCalled(); }); - it('Should response with 404 if correct x-access-token and x-user-id header are provided', async () => { + it('Should response with 404 if correct Authorization header is provided', async () => { const response = await addAuthHeaders(request.get(endpoint)); expect(response.body.message).not.toMatch(/not registered/); expect(response.body.message).not.toMatch(/token/i); expect(response.status).toBe(404); - expect(mockUserFindById).toBeCalledTimes(1); - expect(mockValidateTokenData).toBeCalledTimes(1); + expect(getAccessTokenSpy).toBeCalledTimes(1); + expect(getAccessTokenSpy).toBeCalledWith(`Bearer ${ACCESS_TOKEN}`); + expect(getAccessTokenSpy).toReturnWith(ACCESS_TOKEN); expect(mockJwtValidate).toBeCalledTimes(1); + expect(mockJwtValidate).toBeCalledWith(ACCESS_TOKEN); + expect(mockUserFindById).toBeCalledTimes(1); + expect(mockKeystoreFindForKey).toBeCalledTimes(1); }); }); \ No newline at end of file diff --git a/tests/auth/authorization/mock.ts b/tests/auth/authorization/mock.ts index 212068f..438e518 100644 --- a/tests/auth/authorization/mock.ts +++ b/tests/auth/authorization/mock.ts @@ -1,10 +1,12 @@ // all dependent mock should be on the top -import { USER_ID } from '../authentication/mock'; +import { USER_ID, ACCESS_TOKEN } from '../authentication/mock'; import { Types } from 'mongoose'; import User from '../../../src/database/model/User'; import Role, { RoleCode } from '../../../src/database/model/Role'; - +import { BadTokenError } from '../../../src/core/ApiError'; +import JWT, { JwtPayload } from '../../../src/core/JWT'; +import { tokenInfo } from '../../../src/config'; export const LEARNER_ROLE_ID = new Types.ObjectId(); // random id export const WRITER_ROLE_ID = new Types.ObjectId(); // random id @@ -13,6 +15,9 @@ export const EDITOR_ROLE_ID = new Types.ObjectId(); // random id export const USER_ID_WRITER = new Types.ObjectId(); // random id export const USER_ID_EDITOR = new Types.ObjectId(); // random id +export const WRITER_ACCESS_TOKEN = 'def'; +export const EDITOR_ACCESS_TOKEN = 'ghi'; + export const mockUserFindById = jest.fn(async (id: Types.ObjectId) => { if (USER_ID.equals(id)) return { _id: USER_ID, @@ -59,10 +64,37 @@ export const mockRoleRepoFindByCode = jest.fn( return null; }); +export const mockJwtValidate = jest.fn( + async (token: string): Promise => { + let subject = null; + switch (token) { + case ACCESS_TOKEN: + subject = USER_ID.toHexString(); + break; + case WRITER_ACCESS_TOKEN: + subject = USER_ID_WRITER.toHexString(); + break; + case EDITOR_ACCESS_TOKEN: + subject = USER_ID_EDITOR.toHexString(); + break; + } + if (subject) return { + iss: tokenInfo.issuer, + aud: tokenInfo.audience, + sub: subject, + iat: 1, + exp: 2, + prm: 'abcdef' + }; + throw new BadTokenError(); + }); + jest.mock('../../../src/database/repository/UserRepo', () => ({ get findById() { return mockUserFindById; } })); jest.mock('../../../src/database/repository/RoleRepo', () => ({ get findByCode() { return mockRoleRepoFindByCode; } -})); \ No newline at end of file +})); + +JWT.validate = mockJwtValidate; \ No newline at end of file diff --git a/tests/auth/authorization/unit.test.ts b/tests/auth/authorization/unit.test.ts index a568ca0..78ebe71 100644 --- a/tests/auth/authorization/unit.test.ts +++ b/tests/auth/authorization/unit.test.ts @@ -2,7 +2,7 @@ import { addAuthHeaders } from '../authentication/mock'; // import the mock for the current test after all other mock imports // this will prevent the different implementations by the other mock -import { mockRoleRepoFindByCode, mockUserFindById, USER_ID_WRITER } from './mock'; +import { mockRoleRepoFindByCode, mockUserFindById, EDITOR_ACCESS_TOKEN } from './mock'; import app from '../../../src/app'; import supertest from 'supertest'; @@ -39,7 +39,7 @@ describe('authentication validation for writer', () => { }); it('Should response with 404 if user have writer role', async () => { - const response = await addAuthHeaders(request.get(endpoint), USER_ID_WRITER); + const response = await addAuthHeaders(request.get(endpoint), EDITOR_ACCESS_TOKEN); expect(response.status).toBe(404); expect(mockRoleRepoFindByCode).toBeCalledTimes(1); expect(mockUserFindById).toBeCalledTimes(1); diff --git a/tests/core/jwt/unit.test.ts b/tests/core/jwt/unit.test.ts index 89dd78a..90ea0e2 100644 --- a/tests/core/jwt/unit.test.ts +++ b/tests/core/jwt/unit.test.ts @@ -1,5 +1,5 @@ import { readFileSpy } from './mock'; -import JWT, { JwtPayload, ValidationParams } from '../../../src/core/JWT'; +import JWT, { JwtPayload } from '../../../src/core/JWT'; import { BadTokenError, TokenExpiredError } from '../../../src/core/ApiError'; describe('JWT class tests', () => { @@ -17,7 +17,7 @@ describe('JWT class tests', () => { }); try { - await JWT.decode('abc', new ValidationParams(issuer, audience, subject)); + await JWT.decode('abc'); } catch (e) { expect(e).toBeInstanceOf(BadTokenError); } @@ -46,7 +46,7 @@ describe('JWT class tests', () => { const payload = new JwtPayload(issuer, audience, subject, param, validity); const token = await JWT.encode(payload); - const decoded = await JWT.decode(token, new ValidationParams(issuer, audience, subject)); + const decoded = await JWT.decode(token); expect(decoded).toMatchObject(payload); expect(readFileSpy).toBeCalledTimes(2); @@ -69,7 +69,7 @@ describe('JWT class tests', () => { prm: param, }; const token = await JWT.encode(payload); - const decoded = await JWT.decode(token, new ValidationParams(issuer, audience, subject)); + const decoded = await JWT.decode(token); expect(decoded).toMatchObject(payload); expect(readFileSpy).toBeCalledTimes(2); @@ -82,7 +82,7 @@ describe('JWT class tests', () => { }); try { - await JWT.validate('abc', new ValidationParams(issuer, audience, subject)); + await JWT.validate('abc'); } catch (e) { expect(e).toBeInstanceOf(BadTokenError); } @@ -98,7 +98,7 @@ describe('JWT class tests', () => { const payload = new JwtPayload(issuer, audience, subject, param, validity); const token = await JWT.encode(payload); - const decoded = await JWT.validate(token, new ValidationParams(issuer, audience, subject)); + const decoded = await JWT.validate(token); expect(decoded).toMatchObject(payload); expect(readFileSpy).toBeCalledTimes(2); @@ -122,7 +122,7 @@ describe('JWT class tests', () => { }; const token = await JWT.encode(payload); try { - await JWT.validate(token, new ValidationParams(issuer, audience, subject)); + await JWT.validate(token); } catch (e) { expect(e).toBeInstanceOf(TokenExpiredError); } diff --git a/tests/routes/v1/blog/writer/unit.test.ts b/tests/routes/v1/blog/writer/unit.test.ts index 4412716..3dddfb8 100644 --- a/tests/routes/v1/blog/writer/unit.test.ts +++ b/tests/routes/v1/blog/writer/unit.test.ts @@ -1,7 +1,7 @@ import { addAuthHeaders } from '../../../../auth/authentication/mock'; // this import should be below authentication/mock to override for role validation to work -import { USER_ID_WRITER } from '../../../../auth/authorization/mock'; +import { WRITER_ACCESS_TOKEN } from '../../../../auth/authorization/mock'; import { BLOG_ID, BLOG_URL, BLOG_ID_2, @@ -38,7 +38,7 @@ describe('Writer blog create routes', () => { text: 'text', blogUrl: 'blogUrl', }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/title/i); @@ -54,7 +54,7 @@ describe('Writer blog create routes', () => { text: 'text', blogUrl: 'blogUrl', }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/description/i); @@ -70,7 +70,7 @@ describe('Writer blog create routes', () => { description: 'description', blogUrl: 'blogUrl', }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/text/i); @@ -86,7 +86,7 @@ describe('Writer blog create routes', () => { description: 'description', text: 'text', }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/blogUrl/i); @@ -103,7 +103,7 @@ describe('Writer blog create routes', () => { text: 'text', blogUrl: 'https://abc.com/xyz' }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/blogUrl/i); @@ -121,7 +121,7 @@ describe('Writer blog create routes', () => { blogUrl: 'blogUrl', imgUrl: 'abc' }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/imgUrl/i); @@ -139,7 +139,7 @@ describe('Writer blog create routes', () => { blogUrl: 'blogUrl', score: 'abc' }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/must be a number/i); @@ -156,7 +156,7 @@ describe('Writer blog create routes', () => { blogUrl: 'blogUrl', tags: 'abc' }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/must be/i); @@ -173,7 +173,7 @@ describe('Writer blog create routes', () => { text: 'text', blogUrl: BLOG_URL }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/already exists/i); @@ -192,7 +192,7 @@ describe('Writer blog create routes', () => { score: 0.01, tags: ['ABC'], }), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(200); expect(response.body.message).toMatch(/created success/i); @@ -215,7 +215,7 @@ describe('Writer blog submit routes', () => { it('Should send error if submit blog id is not valid', async () => { const response = await addAuthHeaders( request.put(endpoint + 'abc'), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/id/i); @@ -227,7 +227,7 @@ describe('Writer blog submit routes', () => { it('Should send error if submit blog do not exist for id', async () => { const response = await addAuthHeaders( request.put(endpoint + new Types.ObjectId().toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/not exists/i); @@ -238,7 +238,7 @@ describe('Writer blog submit routes', () => { it('Should send success if submit blog for id exists', async () => { const response = await addAuthHeaders( request.put(endpoint + BLOG_ID.toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(200); expect(response.body.message).toMatch(/submitted success/i); @@ -260,7 +260,7 @@ describe('Writer blog withdraw routes', () => { it('Should send error if withdraw blog id is not valid', async () => { const response = await addAuthHeaders( request.put(endpoint + 'abc'), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/id/i); @@ -272,7 +272,7 @@ describe('Writer blog withdraw routes', () => { it('Should send error if withdraw blog do not exist for id', async () => { const response = await addAuthHeaders( request.put(endpoint + new Types.ObjectId().toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/not exists/i); @@ -283,7 +283,7 @@ describe('Writer blog withdraw routes', () => { it('Should send success if withdraw blog for id exists', async () => { const response = await addAuthHeaders( request.put(endpoint + BLOG_ID.toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(200); expect(response.body.message).toMatch(/withdrawn success/i); @@ -305,7 +305,7 @@ describe('Writer blog delete routes', () => { it('Should send error if deleting blog id is not valid', async () => { const response = await addAuthHeaders( request.delete(endpoint + 'abc'), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/id/i); @@ -317,7 +317,7 @@ describe('Writer blog delete routes', () => { it('Should send error if deleting blog do not exist for id', async () => { const response = await addAuthHeaders( request.delete(endpoint + new Types.ObjectId().toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/not exists/i); @@ -328,7 +328,7 @@ describe('Writer blog delete routes', () => { it('Should send success if deleting blog for id exists', async () => { const response = await addAuthHeaders( request.delete(endpoint + BLOG_ID.toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(200); expect(response.body.message).toMatch(/deleted success/i); @@ -349,7 +349,7 @@ describe('Writer blog get by id routes', () => { it('Should send error if fetching blog id is not valid', async () => { const response = await addAuthHeaders( request.get(endpoint + 'abc'), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/id/i); @@ -360,7 +360,7 @@ describe('Writer blog get by id routes', () => { it('Should send error if fetching blog do not exist for id', async () => { const response = await addAuthHeaders( request.get(endpoint + new Types.ObjectId().toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(400); expect(response.body.message).toMatch(/not exists/i); @@ -370,7 +370,7 @@ describe('Writer blog get by id routes', () => { it('Should send error if author is different', async () => { const response = await addAuthHeaders( request.get(endpoint + BLOG_ID_2.toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(403); expect(response.body.message).toMatch(/don't have/i); @@ -381,7 +381,7 @@ describe('Writer blog get by id routes', () => { it('Should send success if fetching blog for id exists', async () => { const response = await addAuthHeaders( request.get(endpoint + BLOG_ID.toHexString()), - USER_ID_WRITER + WRITER_ACCESS_TOKEN ); expect(response.status).toBe(200); expect(response.body.message).toMatch(/success/i);