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