From bf12b44fca626f4f8cad5b25d4aaee4d8022eda8 Mon Sep 17 00:00:00 2001 From: "Embruch, Gerd" <gerd.embruch@uni-hamburg.de> Date: Sat, 27 Jul 2024 18:47:10 +0200 Subject: [PATCH] finished testing route auth/refreshJWT --- .../__snapshots__/refreshjwt.test.js.snap | 26 ++++ __tests__/auth/login.test.js | 2 - __tests__/auth/refreshjwt.test.js | 125 ++++++++++++++++++ controllers/Auth.js | 24 ++-- logs/__tests__/users/refreshjwt.test.js | 109 --------------- utils/handleErrors.js | 1 + utils/handleTokens.js | 16 ++- 7 files changed, 176 insertions(+), 127 deletions(-) create mode 100644 __tests__/auth/__snapshots__/refreshjwt.test.js.snap create mode 100644 __tests__/auth/refreshjwt.test.js delete mode 100644 logs/__tests__/users/refreshjwt.test.js diff --git a/__tests__/auth/__snapshots__/refreshjwt.test.js.snap b/__tests__/auth/__snapshots__/refreshjwt.test.js.snap new file mode 100644 index 0000000..d4641ab --- /dev/null +++ b/__tests__/auth/__snapshots__/refreshjwt.test.js.snap @@ -0,0 +1,26 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`refresh JWT > given authtoken is valid > should respond with a proper body 1`] = ` +{ + "accessToken": Any<String>, + "message": "Access token refreshed", +} +`; + +exports[`refresh JWT > given refresh token is invalid > should respond with a proper body 1`] = ` +{ + "message": "Refresh token is invalid", +} +`; + +exports[`refresh JWT > given refresh token is malformed > should respond with a proper body 1`] = ` +{ + "message": "jwt malformed", +} +`; + +exports[`refresh JWT > given refresh token is missing > should respond with a proper body 1`] = ` +{ + "message": "Refresh token is missing. Consider to re-login", +} +`; diff --git a/__tests__/auth/login.test.js b/__tests__/auth/login.test.js index 99ae46d..e3c6d07 100644 --- a/__tests__/auth/login.test.js +++ b/__tests__/auth/login.test.js @@ -13,8 +13,6 @@ let response; // OBJECTS // ############################ const mockedVals = vi.hoisted(() => { - // const password = await bcrypt.hash('StrongPass1!', Number(process.env.BCRYPT_STRENGTH)); - return { foundUser: { _id: '66a29da2942b3eb', diff --git a/__tests__/auth/refreshjwt.test.js b/__tests__/auth/refreshjwt.test.js new file mode 100644 index 0000000..e1a1f87 --- /dev/null +++ b/__tests__/auth/refreshjwt.test.js @@ -0,0 +1,125 @@ +// import vitest, supertest & app +import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; +import supertest from "supertest"; +import app from "../../app.js"; +import jwt from 'jsonwebtoken'; + +// set route +const ROUTE = '/auth'; +// prepare response of each test +let response; + +// ############################ +// OBJECTS +// ############################ +const mockedVals = vi.hoisted(() => { + return { + foundUser: { + _id: '66a29da2942b3eb', + username: 'snoopy', + name: 'My User', + email: 'user@mail.local', + verified: true, + role: 0, + createdAt: '2024-07 - 25T18: 46: 58.982Z', + updatedAt: '2024-07 - 25T18: 46: 58.982Z', + __v: 0, + password: 'StrongPass1!', + // password, + id: '66a29da2942b3ebcaf047f07' + } + }; +}); + +// ############################ +// MOCKS +// ############################ +// import Database Service +import * as dbService from '../../utils/handleDB.js'; +// mock dbService +vi.mock('../../utils/handleDB.js', async (importOriginal) => { + return { + ...await importOriginal(), + dbConnection: vi.fn(() => 'mocked'), + findOneRecord: vi.fn(() => mockedVals.foundUser), + findByIdAndUpdate: vi.fn(() => { return { ...mockedVals.foundUser, refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MOCKED' }; }) + }; +}); +// verifyRefreshToken; + +// ############################ +// TESTS +// ############################ + +describe('refresh JWT', () => { + + + describe('given authtoken is valid', () => { + beforeAll(async () => { + const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY); + // console.log('refreshToken', refreshToken); + response = await supertest(app) + .get(ROUTE) + .set('Cookie', `refreshToken=${refreshToken}`); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(200); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot({ + accessToken: expect.any(String), + }); + }); + }); + + // ############################ + describe('given refresh token is malformed', () => { + beforeAll(async () => { + // const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY); + // console.log('refreshToken', refreshToken); + response = await supertest(app) + .get(ROUTE) + .set('Cookie', 'refreshToken=invalid'); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(400); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given refresh token is invalid', () => { + beforeAll(async () => { + // const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY); + // console.log('refreshToken', refreshToken); + response = await supertest(app) + .get(ROUTE) + .set('Cookie', 'refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImlhdCI6MTcyMjA5ODM3OX0.7Pq8F2zSDwuEzlCQX3vMZAw9D43N6dSViCyVPZ_s_Zs'); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(403); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given refresh token is missing', () => { + beforeAll(async () => { + + response = await supertest(app) + .get(ROUTE); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(401); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); +}); \ No newline at end of file diff --git a/controllers/Auth.js b/controllers/Auth.js index 43b5f72..324836e 100644 --- a/controllers/Auth.js +++ b/controllers/Auth.js @@ -104,17 +104,21 @@ export const login = async (req, res, next) => { * return new accessToken and refreshToken */ export const renewAccessToken = async (req, res, next) => { - const refreshToken = req.cookies.refreshToken; - - if (!refreshToken) return res.status(401).json({ message: 'Refresh token is missing. Consider to re-login' }); - // verify token - const user = await verifyRefreshToken(refreshToken); - if (!user) { - return res.status(403).json({ message: 'Refresh token is invalid' }); + try { + // get token from cookie + const refreshToken = req.cookies.refreshToken; + if (!refreshToken) return res.status(401).json({ message: 'Refresh token is missing. Consider to re-login' }); + // verify token + const user = await verifyRefreshToken(refreshToken); + if (!user) { + return res.status(403).json({ message: 'Refresh token is invalid' }); + } + // create & return + const accessToken = createAccessToken({ id: user._id, role: user.role }); + return res.json({ message: 'Access token refreshed', accessToken }); + } catch (error) { + next(error); } - // create & return - const accessToken = createAccessToken({ id: user._id, role: user.role }); - return res.json({ message: 'Access token refreshed', accessToken }); }; diff --git a/logs/__tests__/users/refreshjwt.test.js b/logs/__tests__/users/refreshjwt.test.js deleted file mode 100644 index b63d4dc..0000000 --- a/logs/__tests__/users/refreshjwt.test.js +++ /dev/null @@ -1,109 +0,0 @@ -// import vitest, supertest & app -import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; -import supertest from "supertest"; -import app from "../../app.js"; -// ignore expiration of the (self-signed) certificate -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; -// set timeout -const BEFORE_ALL_TIMEOUT = 30000; // 30 sec -// set route -const ROUTE = '/users/refreshjwt'; -// prepare response of each test -let response; - -// ############################ -// OBJECTS -// ############################ -const authStoreModel = { - avatar: "", - collectionId: "_pb_users_auth_", - collectionName: "users", - created: "2024-05-06 07:45:18.836Z", - email: "johndoe@local.local", - emailVisibility: false, - id: "jr9mt8yvuri3sbd", - name: "John Doe", - updated: "2024-07-02 13:23:52.155Z", - username: "johndoe", - verified: true -}; -const userLoginVerifiedResponse = { - "record": authStoreModel, - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MjA2NDk3NTQsImlkIjoianI5bXQ4eXZ1cmkzc2JkIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.yFP1vlM_N2Fvpa_56INlaefSXnpwrm9ASCJuxPwf1Vk" -}; - -// ############################ -// MOCKS -// ############################ -// import PocketBase Service -import * as pbService from '../../utils/pocketbase/handlePocketBase.js'; -// mock pbService -vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => { - return { - ...await importOriginal(), - pbVerifyAccessToken: vi.fn().mockImplementation((req, res, next) => { - next(); - }), - pbRefreshJWT: vi.fn().mockImplementation(() => { - return userLoginVerifiedResponse; - }) - }; -}); - -// ############################ -// TESTS -// ############################ - -describe('refresh JWT', () => { - describe('given authtoken is valid', () => { - beforeAll(async () => { - response = await supertest(app) - .get(ROUTE) - .set('Authorization', 'Bearer 123valid'); - }, BEFORE_ALL_TIMEOUT); - it('should return a proper status code', () => { - expect(response.status).toBe(200); - }); - it('should respond with a proper record and token', () => { - expect(response.body).toEqual(userLoginVerifiedResponse); - }); - }); - - // ############################ - describe('given authtoken is invalid', () => { - beforeAll(async () => { - pbService.pbVerifyAccessToken.mockImplementation((req, res, next) => { - res.status(403).json({ message: 'You are not logged in.' }); - }); - - response = await supertest(app) - .get(ROUTE) - .set('Authorization', 'Bearer 123invalid'); - }, BEFORE_ALL_TIMEOUT); - it('should return a proper status code', () => { - expect(response.status).toBe(403); - }); - it('should respond with a proper record and token', () => { - expect(response.body.message).toEqual('You are not logged in.'); - }); - }); - - // ############################ - - describe('given authtoken is missing', () => { - beforeAll(async () => { - pbService.pbVerifyAccessToken.mockImplementation((req, res, next) => { - res.status(403).json({ message: 'You are not logged in.' }); - }); - - response = await supertest(app) - .get(ROUTE); - }, BEFORE_ALL_TIMEOUT); - it('should return a proper status code', () => { - expect(response.status).toBe(403); - }); - it('should respond with a proper record and token', () => { - expect(response.body.message).toEqual('You are not logged in.'); - }); - }); -}); \ No newline at end of file diff --git a/utils/handleErrors.js b/utils/handleErrors.js index 31116fa..2feaeed 100755 --- a/utils/handleErrors.js +++ b/utils/handleErrors.js @@ -19,6 +19,7 @@ const generateErrorStatusCode = (error) => { switch (error.name) { // VALIDATION ERROR case "ValidationError": + case "JsonWebTokenError": // ZOD VALIDATION ERROR case "zodError": { return 400; diff --git a/utils/handleTokens.js b/utils/handleTokens.js index 3018e55..bc7c50a 100644 --- a/utils/handleTokens.js +++ b/utils/handleTokens.js @@ -93,12 +93,16 @@ export const createRefreshToken = async (payload) => { * @return {any} user from DB if exists, false if not */ export const verifyRefreshToken = async (refreshToken) => { - // check if in DB - const foundUser = await User.findOne({ refreshToken }); - if (!foundUser) return false; - // check if valid - const user = jwt.verify(refreshToken, process.env.JWT_REFRESH_KEY); - return (user && user.id === foundUser.id ? foundUser : false); + try { + // check if in DB + const foundUser = await findOneRecord(User, { refreshToken }); + if (!foundUser) return false; + // check if valid + const user = jwt.verify(refreshToken, process.env.JWT_REFRESH_KEY); + return (user && user.id === foundUser.id ? foundUser : false); + } catch (error) { + throw error; + } }; /** -- GitLab