From 1d253c5e6caba0cc61eec7e9e9b3c4c4ea9c6677 Mon Sep 17 00:00:00 2001 From: Ajeyakrishna <98796547+Ajeyakrishna-k@users.noreply.github.com> Date: Mon, 25 Dec 2023 00:51:44 +0530 Subject: [PATCH] Fix authentication (#179) * fix: validates verify result * feat: adds test to verify token function * chore: moves error messages to constants --- src/constants/responses.ts | 5 +++ src/controllers/getMembersInServer.ts | 6 ++-- src/utils/verifyAuthToken.ts | 15 +++++++-- tests/unit/utils/verifyToken.test.ts | 47 +++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 tests/unit/utils/verifyToken.test.ts diff --git a/src/constants/responses.ts b/src/constants/responses.ts index a7a56285..44007852 100644 --- a/src/constants/responses.ts +++ b/src/constants/responses.ts @@ -72,3 +72,8 @@ export const OVERDUE_CUSTOM_MESSAGE = export const ONBOARDING_DEFAULT_MESSAGE = `You currently have an onboarding status. Please provide an update explaining any challenges you're facing in completing your tasks. If you're finished, consider assigning new tasks to Admin.`; export const ONBOARDING_CUSTOM_MESSAGE = `Please update your status explaining why you are unable to complete your onboarding tasks within {{days}} days.`; + +export const INVALID_TOKEN_FORMAT = + "Invalid Authentication header format. Expected 'Bearer '"; + +export const AUTHENTICATION_ERROR = "Invalid Authentication token"; diff --git a/src/controllers/getMembersInServer.ts b/src/controllers/getMembersInServer.ts index 3d999b37..d82dc673 100644 --- a/src/controllers/getMembersInServer.ts +++ b/src/controllers/getMembersInServer.ts @@ -5,6 +5,7 @@ import { env } from "../typeDefinitions/default.types"; import JSONResponse from "../utils/JsonResponse"; import { User } from "../typeDefinitions/user.types"; import { getMembersInServer } from "../utils/getMembersInServer"; +import { verifyAuthToken } from "../utils/verifyAuthToken"; export const getMembersInServerHandler = async ( request: IRequest, @@ -16,10 +17,7 @@ export const getMembersInServerHandler = async ( return new JSONResponse(response.BAD_SIGNATURE); } try { - const authToken = authHeader.split(" ")[1]; - await jwt.verify(authToken, env.RDS_SERVERLESS_PUBLIC_KEY, { - algorithm: "RS256", - }); + await verifyAuthToken(authHeader, env); const users = (await getMembersInServer(env)) as User[]; diff --git a/src/utils/verifyAuthToken.ts b/src/utils/verifyAuthToken.ts index b0074c8b..52c64f01 100644 --- a/src/utils/verifyAuthToken.ts +++ b/src/utils/verifyAuthToken.ts @@ -1,3 +1,7 @@ +import { + AUTHENTICATION_ERROR, + INVALID_TOKEN_FORMAT, +} from "../constants/responses"; import { env } from "../typeDefinitions/default.types"; import jwt from "@tsndr/cloudflare-worker-jwt"; @@ -8,8 +12,15 @@ import jwt from "@tsndr/cloudflare-worker-jwt"; */ export async function verifyAuthToken(authHeader: string, env: env) { - const authToken = authHeader.split(" ")[1]; - await jwt.verify(authToken, env.RDS_SERVERLESS_PUBLIC_KEY, { + const parts = authHeader.split(" "); + if (parts.length !== 2 || parts[0] !== "Bearer") { + throw new Error(INVALID_TOKEN_FORMAT); + } + const authToken = parts[1]; + const isValid = await jwt.verify(authToken, env.RDS_SERVERLESS_PUBLIC_KEY, { algorithm: "RS256", }); + if (!isValid) { + throw new Error(AUTHENTICATION_ERROR); + } } diff --git a/tests/unit/utils/verifyToken.test.ts b/tests/unit/utils/verifyToken.test.ts new file mode 100644 index 00000000..2c3c63f5 --- /dev/null +++ b/tests/unit/utils/verifyToken.test.ts @@ -0,0 +1,47 @@ +import jwt from "@tsndr/cloudflare-worker-jwt"; +import { verifyAuthToken } from "../../../src/utils/verifyAuthToken"; +import { + AUTHENTICATION_ERROR, + INVALID_TOKEN_FORMAT, +} from "../../../src/constants/responses"; + +describe("verifyAuthToken", () => { + const authToken = "validToken"; + const mockEnv = { RDS_SERVERLESS_PUBLIC_KEY: "publicKey" }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should verify a valid token successfully", async () => { + jwt.verify = jest.fn().mockResolvedValue(true); + const authHeader = `Bearer ${authToken}`; + await expect(verifyAuthToken(authHeader, mockEnv)).resolves.not.toThrow(); + expect(jwt.verify).toHaveBeenCalledWith( + authToken, + mockEnv.RDS_SERVERLESS_PUBLIC_KEY, + { algorithm: "RS256" } + ); + }); + + it("should throw an error for an invalid token", async () => { + const authHeader = "Bearer invalidToken"; + jwt.verify = jest.fn().mockResolvedValue(false); + await expect(verifyAuthToken(authHeader, mockEnv)).rejects.toThrow( + AUTHENTICATION_ERROR + ); + }); + it("should throw an error when Bearer is not passed", async () => { + const authHeader = "Beaer invalidToken"; + await expect(verifyAuthToken(authHeader, mockEnv)).rejects.toThrow( + INVALID_TOKEN_FORMAT + ); + }); + + it("should throw an error for a malformed auth header", async () => { + const malformedHeader = "invalidformat"; + await expect(verifyAuthToken(malformedHeader, mockEnv)).rejects.toThrow( + INVALID_TOKEN_FORMAT + ); + }); +});