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.
This commit is contained in:
		
							parent
							
								
									054e761baa
								
							
						
					
					
						commit
						69ba8c9567
					
				
					 2 changed files with 29 additions and 16 deletions
				
			
		|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger