From 69ba8c9567df56fc2871f4a88a416c8b0e1cc319 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Sun, 2 Mar 2025 01:24:49 +0100 Subject: [PATCH] feat(backend): Generic authentication checks. Added support for deciding based on any predicate about the current AuthenticationInfo whether or not a request will be accepted. --- backend/src/middleware/auth/auth.ts | 40 ++++++++++++------- .../middleware/auth/authenticated-request.ts | 5 ++- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index ec097fd5..0bf63e3d 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -29,7 +29,7 @@ const idpConfigs = { /** * Express middleware which verifies the JWT Bearer token if one is given in the request. */ -export const authenticateUser = expressjwt({ +const verifyJwtToken = expressjwt({ secret: async (_: express.Request, token: jwt.Jwt | undefined) => { if (!token?.payload || !(token.payload as JwtPayload).iss) { throw new Error("Invalid token"); @@ -50,18 +50,19 @@ export const authenticateUser = expressjwt({ }, audience: getEnvVar(EnvVars.IdpAudience), algorithms: ["RS256"], - credentialsRequired: false + credentialsRequired: false, + requestProperty: "jwtPayload" }); /** * Get an object with information about the authenticated user from a given authenticated request. */ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { - if (!req.auth) { + console.log("hi"); + if (!req.jwtPayload) { return; } - - let issuer = req.auth.issuer; + let issuer = req.jwtPayload.iss; let accountType: "student" | "teacher"; if (issuer === idpConfigs.student.issuer) { @@ -71,29 +72,38 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | } else { return; } - return { accountType: accountType, - username: req.auth["preferred_username"]!, - name: req.auth["name"], - firstName: req.auth["given_name"], - lastName: req.auth["family_name"], - email: req.auth["email"], + username: req.jwtPayload["preferred_username"]!, + name: req.jwtPayload["name"], + firstName: req.jwtPayload["given_name"], + lastName: req.jwtPayload["family_name"], + email: req.jwtPayload["email"], } } +/** + * Add the AuthenticationInfo object with the information about the current authentication to the request in order + * to avoid that the routers have to deal with the JWT token. + */ +const addAuthenticationInfo = (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => { + req.auth = getAuthenticationInfo(req); + next(); +}; + +export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; + /** * Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill * the given access condition. * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates * to true. */ -const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => { +export const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => { return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => { - let authInfo = getAuthenticationInfo(req); - if (!authInfo) { + if (!req.auth) { res.status(401).json({ message: "Unauthorized" }); - } else if (!accessCondition(authInfo)) { + } else if (!accessCondition(req.auth)) { res.status(403).json({ message: "Forbidden" }); } else { next(); diff --git a/backend/src/middleware/auth/authenticated-request.ts b/backend/src/middleware/auth/authenticated-request.ts index 9b33de6f..b29cfbac 100644 --- a/backend/src/middleware/auth/authenticated-request.ts +++ b/backend/src/middleware/auth/authenticated-request.ts @@ -1,6 +1,9 @@ import { Request } from "express"; import { JwtPayload } from "jsonwebtoken"; +import {AuthenticationInfo} from "./authentication-info"; export interface AuthenticatedRequest extends Request { - auth?: JwtPayload; // Optional, as req.auth might be undefined if authentication is optional + // Properties are optional since the user is not necessarily authenticated. + jwtPayload?: JwtPayload; + auth?: AuthenticationInfo; }