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 {envVars, getEnvVar} from '../../util/envVars.js'; | ||||||
| import { expressjwt } from 'express-jwt'; | import {expressjwt} from 'express-jwt'; | ||||||
| import * as jwt from 'jsonwebtoken'; | import * as jwt from 'jsonwebtoken'; | ||||||
| import { JwtPayload } from 'jsonwebtoken'; | import {JwtPayload} from 'jsonwebtoken'; | ||||||
| import jwksClient from 'jwks-rsa'; | import jwksClient from 'jwks-rsa'; | ||||||
| import * as express from 'express'; | import * as express from 'express'; | ||||||
| import { AuthenticatedRequest } from './authenticated-request.js'; | import {AuthenticatedRequest} from './authenticated-request.js'; | ||||||
| import { AuthenticationInfo } from './authentication-info.js'; | import {AuthenticationInfo} from './authentication-info.js'; | ||||||
| import { UnauthorizedException } from '../../exceptions/unauthorized-exception.js'; |  | ||||||
| import { ForbiddenException } from '../../exceptions/forbidden-exception.js'; |  | ||||||
| 
 | 
 | ||||||
| const JWKS_CACHE = true; | const JWKS_CACHE = true; | ||||||
| const JWKS_RATE_LIMIT = true; | const JWKS_RATE_LIMIT = true; | ||||||
|  | @ -108,36 +106,3 @@ function addAuthenticationInfo(req: AuthenticatedRequest, _res: express.Response | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | 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 express from 'express'; | ||||||
| import { getFrontendAuthConfig } from '../controllers/auth.js'; | 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(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
| // Returns auth configuration for frontend
 | // Returns auth configuration for frontend
 | ||||||
|  |  | ||||||
|  | @ -5,15 +5,19 @@ import { | ||||||
|     getStudentRequestHandler, |     getStudentRequestHandler, | ||||||
|     getStudentRequestsHandler, |     getStudentRequestsHandler, | ||||||
| } from '../controllers/students.js'; | } 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 }); | 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; | export default router; | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import { | ||||||
|     getStudentSubmissionsHandler, |     getStudentSubmissionsHandler, | ||||||
| } from '../controllers/students.js'; | } from '../controllers/students.js'; | ||||||
| import joinRequestRouter from './student-join-requests.js'; | import joinRequestRouter from './student-join-requests.js'; | ||||||
|  | import {onlyAllowUserHimself} from "../middleware/auth/checks/user-auth-checks"; | ||||||
| 
 | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -19,25 +20,25 @@ router.get('/', getAllStudentsHandler); | ||||||
| 
 | 
 | ||||||
| router.post('/', createStudentHandler); | router.post('/', createStudentHandler); | ||||||
| 
 | 
 | ||||||
| router.delete('/:username', deleteStudentHandler); | router.delete('/:username', onlyAllowUserHimself, deleteStudentHandler); | ||||||
| 
 | 
 | ||||||
| // Information about a student's profile
 | // Information about a student's profile
 | ||||||
| router.get('/:username', getStudentHandler); | router.get('/:username', onlyAllowUserHimself, getStudentHandler); | ||||||
| 
 | 
 | ||||||
| // The list of classes a student is in
 | // 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
 | // 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
 | // 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
 | // 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
 | // A list of questions a user has created
 | ||||||
| router.get('/:username/questions', getStudentQuestionsHandler); | router.get('/:username/questions', onlyAllowUserHimself, getStudentQuestionsHandler); | ||||||
| 
 | 
 | ||||||
| router.use('/:username/joinRequests', joinRequestRouter); | router.use('/:username/joinRequests', joinRequestRouter); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger