diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 82f1b23f..ec097fd5 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -5,6 +5,7 @@ import jwksClient from 'jwks-rsa'; import * as express from "express"; import * as jwt from "jsonwebtoken"; import {AuthenticatedRequest} from "./authenticated-request.js"; +import {AuthenticationInfo} from "./authentication-info"; function createJwksClient(uri: string): jwksClient.JwksClient { return jwksClient({ @@ -25,6 +26,9 @@ const idpConfigs = { } }; +/** + * Express middleware which verifies the JWT Bearer token if one is given in the request. + */ export const authenticateUser = expressjwt({ secret: async (_: express.Request, token: jwt.Jwt | undefined) => { if (!token?.payload || !(token.payload as JwtPayload).iss) { @@ -49,27 +53,65 @@ export const authenticateUser = expressjwt({ credentialsRequired: false }); -const authorizeRole = (studentsAllowed: boolean, teachersAllowed: boolean) => { +/** + * Get an object with information about the authenticated user from a given authenticated request. + */ +function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { + if (!req.auth) { + return; + } + + let issuer = req.auth.issuer; + let accountType: "student" | "teacher"; + + if (issuer === idpConfigs.student.issuer) { + accountType = "student"; + } else if (issuer === idpConfigs.teacher.issuer) { + accountType = "teacher"; + } 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"], + } +} + +/** + * 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) => { return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => { - if (!req.auth) { + let authInfo = getAuthenticationInfo(req); + if (!authInfo) { res.status(401).json({ message: "Unauthorized" }); - return; + } else if (!accessCondition(authInfo)) { + res.status(403).json({ message: "Forbidden" }); + } else { + next(); } + } +} - const issuer = req.auth.iss; - if (issuer === idpConfigs.student.issuer && !studentsAllowed) { - res.status(403).json({ message: "Students not allowed" }); - return; - } - if (issuer === idpConfigs.teacher.issuer && !teachersAllowed) { - res.status(403).json({ message: "Teachers not allowed" }); - return; - } +/** + * Middleware which rejects all unauthenticated users, but accepts all authenticated users. + */ +export const authenticatedOnly = authorize(_ => true); - next(); // User is allowed - }; -}; +/** + * Middleware which rejects requests from unauthenticated users or users that aren't students. + */ +export const studentsOnly = authorize(auth => auth.accountType === "student"); -export const authenticatedOnly = authorizeRole(true, true); -export const studentsOnly = authorizeRole(true, false); -export const teachersOnly = authorizeRole(false, true); +/** + * Middleware which rejects requests from unauthenticated users or users that aren't teachers. + */ +export const teachersOnly = authorize(auth => auth.accountType === "teacher"); diff --git a/backend/src/middleware/auth/authentication-info.ts b/backend/src/middleware/auth/authentication-info.ts new file mode 100644 index 00000000..6711edd0 --- /dev/null +++ b/backend/src/middleware/auth/authentication-info.ts @@ -0,0 +1,11 @@ +/** + * Object with information about the user who is currently logged in. + */ +export type AuthenticationInfo = { + accountType: "student" | "teacher", + username: string, + name?: string, + firstName?: string, + lastName?: string, + email?: string +};