feat(backend): Endpoints voor studenten beschermd
This commit is contained in:
		
							parent
							
								
									bc60c18938
								
							
						
					
					
						commit
						6cb8a1b98f
					
				
					 7 changed files with 93 additions and 52 deletions
				
			
		|  | @ -1,13 +1,11 @@ | |||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||
| import { expressjwt } from 'express-jwt'; | ||||
| import {envVars, getEnvVar} from '../../util/envVars.js'; | ||||
| import {expressjwt} from 'express-jwt'; | ||||
| import * as jwt from 'jsonwebtoken'; | ||||
| import { JwtPayload } from 'jsonwebtoken'; | ||||
| import {JwtPayload} from 'jsonwebtoken'; | ||||
| import jwksClient from 'jwks-rsa'; | ||||
| import * as express from 'express'; | ||||
| import { AuthenticatedRequest } from './authenticated-request.js'; | ||||
| import { AuthenticationInfo } from './authentication-info.js'; | ||||
| import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js'; | ||||
| import { ForbiddenException } from '../../exceptions/forbidden-exception.js'; | ||||
| import {AuthenticatedRequest} from './authenticated-request.js'; | ||||
| import {AuthenticationInfo} from './authentication-info.js'; | ||||
| 
 | ||||
| const JWKS_CACHE = true; | ||||
| const JWKS_RATE_LIMIT = true; | ||||
|  | @ -108,36 +106,3 @@ function addAuthenticationInfo(req: AuthenticatedRequest, _res: express.Response | |||
| } | ||||
| 
 | ||||
| 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. | ||||
|  */ | ||||
| export function authorize(accessCondition: (auth: AuthenticationInfo) => boolean) { | ||||
|     return (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { | ||||
|         if (!req.auth) { | ||||
|             throw new UnauthorizedException(); | ||||
|         } else if (!accessCondition(req.auth)) { | ||||
|             throw new ForbiddenException(); | ||||
|         } else { | ||||
|             next(); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Middleware which rejects all unauthenticated users, but accepts all authenticated users. | ||||
|  */ | ||||
| export const authenticatedOnly = authorize((_) => true); | ||||
| 
 | ||||
| /** | ||||
|  * Middleware which rejects requests from unauthenticated users or users that aren't students. | ||||
|  */ | ||||
| export const studentsOnly = authorize((auth) => auth.accountType === 'student'); | ||||
| 
 | ||||
| /** | ||||
|  * Middleware which rejects requests from unauthenticated users or users that aren't teachers. | ||||
|  */ | ||||
| export const teachersOnly = authorize((auth) => auth.accountType === 'teacher'); | ||||
|  |  | |||
							
								
								
									
										38
									
								
								backend/src/middleware/auth/checks/auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								backend/src/middleware/auth/checks/auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| import {AuthenticationInfo} from "../authentication-info"; | ||||
| import {AuthenticatedRequest} from "../authenticated-request"; | ||||
| import * as express from "express"; | ||||
| import {UnauthorizedException} from "../../../exceptions/unauthorized-exception"; | ||||
| import {ForbiddenException} from "../../../exceptions/forbidden-exception"; | ||||
| 
 | ||||
| /** | ||||
|  * 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. | ||||
|  */ | ||||
| export function authorize( | ||||
|     accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean> | ||||
| ) { | ||||
|     return async (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): Promise<void> => { | ||||
|         if (!req.auth) { | ||||
|             throw new UnauthorizedException(); | ||||
|         } else if (!await accessCondition(req.auth, req)) { | ||||
|             throw new ForbiddenException(); | ||||
|         } else { | ||||
|             next(); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Middleware which rejects all unauthenticated users, but accepts all authenticated users. | ||||
|  */ | ||||
| export const authenticatedOnly = authorize((_) => true); | ||||
| /** | ||||
|  * Middleware which rejects requests from unauthenticated users or users that aren't students. | ||||
|  */ | ||||
| export const studentsOnly = authorize((auth) => auth.accountType === 'student'); | ||||
| /** | ||||
|  * Middleware which rejects requests from unauthenticated users or users that aren't teachers. | ||||
|  */ | ||||
| export const teachersOnly = authorize((auth) => auth.accountType === 'teacher'); | ||||
							
								
								
									
										22
									
								
								backend/src/middleware/auth/checks/class-auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								backend/src/middleware/auth/checks/class-auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| import {authorize} from "./auth-checks"; | ||||
| import {AuthenticationInfo} from "../authentication-info"; | ||||
| import {AuthenticatedRequest} from "../authenticated-request"; | ||||
| import {getClassesByTeacher} from "../../../services/teachers"; | ||||
| 
 | ||||
| /** | ||||
|  * To be used on a request with path parameters username and classId. | ||||
|  * Only allows requests whose username parameter is equal to the username of the user who is logged in and requests | ||||
|  * whose classId parameter references a class the logged-in user is a teacher of. | ||||
|  */ | ||||
| export const onlyAllowStudentHimselfAndTeachersOfClass = authorize( | ||||
|     async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { | ||||
|         if (req.params.username === auth.username) { | ||||
|             return true; | ||||
|         } else if (auth.accountType === "teacher") { | ||||
|             const classes: string[] = (await getClassesByTeacher(auth.username, false)) as string[]; | ||||
|             return req.params.classId in classes; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										10
									
								
								backend/src/middleware/auth/checks/user-auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								backend/src/middleware/auth/checks/user-auth-checks.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import {authorize} from "./auth-checks"; | ||||
| import {AuthenticationInfo} from "../authentication-info"; | ||||
| import {AuthenticatedRequest} from "../authenticated-request"; | ||||
| 
 | ||||
| /** | ||||
|  * Only allow the user whose username is in the path parameter "username" to access the endpoint. | ||||
|  */ | ||||
| export const onlyAllowUserHimself = authorize( | ||||
|     (auth: AuthenticationInfo, req: AuthenticatedRequest) => req.params.username === auth.username | ||||
| ); | ||||
|  | @ -1,6 +1,7 @@ | |||
| import express from 'express'; | ||||
| import { getFrontendAuthConfig } from '../controllers/auth.js'; | ||||
| import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/auth/auth.js'; | ||||
| import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/checks/auth-checks"; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
| // Returns auth configuration for frontend
 | ||||
|  |  | |||
|  | @ -5,15 +5,19 @@ import { | |||
|     getStudentRequestHandler, | ||||
|     getStudentRequestsHandler, | ||||
| } from '../controllers/students.js'; | ||||
| import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks"; | ||||
| import {onlyAllowStudentHimselfAndTeachersOfClass} from "../middleware/auth/checks/class-auth-checks"; | ||||
| 
 | ||||
| // Under /:username/joinRequests/
 | ||||
| 
 | ||||
| const router = express.Router({ mergeParams: true }); | ||||
| 
 | ||||
| router.get('/', getStudentRequestsHandler); | ||||
| router.get('/', onlyAllowUserHimself, getStudentRequestsHandler); | ||||
| 
 | ||||
| router.post('/', createStudentRequestHandler); | ||||
| router.post('/', onlyAllowUserHimself, createStudentRequestHandler); | ||||
| 
 | ||||
| router.get('/:classId', getStudentRequestHandler); | ||||
| router.get('/:classId', onlyAllowStudentHimselfAndTeachersOfClass, getStudentRequestHandler); | ||||
| 
 | ||||
| router.delete('/:classId', deleteClassJoinRequestHandler); | ||||
| router.delete('/:classId', onlyAllowStudentHimselfAndTeachersOfClass, deleteClassJoinRequestHandler); | ||||
| 
 | ||||
| export default router; | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import { | |||
|     getStudentSubmissionsHandler, | ||||
| } from '../controllers/students.js'; | ||||
| import joinRequestRouter from './student-join-requests.js'; | ||||
| import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks"; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -19,25 +20,25 @@ router.get('/', getAllStudentsHandler); | |||
| 
 | ||||
| router.post('/', createStudentHandler); | ||||
| 
 | ||||
| router.delete('/:username', deleteStudentHandler); | ||||
| router.delete('/:username', onlyAllowUserHimself, deleteStudentHandler); | ||||
| 
 | ||||
| // Information about a student's profile
 | ||||
| router.get('/:username', getStudentHandler); | ||||
| router.get('/:username', onlyAllowUserHimself, getStudentHandler); | ||||
| 
 | ||||
| // The list of classes a student is in
 | ||||
| router.get('/:username/classes', getStudentClassesHandler); | ||||
| router.get('/:username/classes', onlyAllowUserHimself, getStudentClassesHandler); | ||||
| 
 | ||||
| // The list of submissions a student has made
 | ||||
| router.get('/:username/submissions', getStudentSubmissionsHandler); | ||||
| router.get('/:username/submissions', onlyAllowUserHimself, getStudentSubmissionsHandler); | ||||
| 
 | ||||
| // The list of assignments a student has
 | ||||
| router.get('/:username/assignments', getStudentAssignmentsHandler); | ||||
| router.get('/:username/assignments', onlyAllowUserHimself, getStudentAssignmentsHandler); | ||||
| 
 | ||||
| // The list of groups a student is in
 | ||||
| router.get('/:username/groups', getStudentGroupsHandler); | ||||
| router.get('/:username/groups', onlyAllowUserHimself, getStudentGroupsHandler); | ||||
| 
 | ||||
| // A list of questions a user has created
 | ||||
| router.get('/:username/questions', getStudentQuestionsHandler); | ||||
| router.get('/:username/questions', onlyAllowUserHimself, getStudentQuestionsHandler); | ||||
| 
 | ||||
| router.use('/:username/joinRequests', joinRequestRouter); | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue