feat(backend): Bescherming van leerobject-manipulatie endpoints.
Ook delete route voor leerobjecten toegevoegd.
This commit is contained in:
		
							parent
							
								
									a7f90aace3
								
							
						
					
					
						commit
						20c04370b5
					
				
					 4 changed files with 50 additions and 4 deletions
				
			
		|  | @ -8,6 +8,7 @@ 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 { RequestHandler } from 'express'; | ||||
| 
 | ||||
| const JWKS_CACHE = true; | ||||
| const JWKS_RATE_LIMIT = true; | ||||
|  | @ -115,11 +116,17 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | |||
|  * @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 => { | ||||
| export function authorize( | ||||
|     accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean> | ||||
| ): RequestHandler { | ||||
|     return async ( | ||||
|         req: AuthenticatedRequest, | ||||
|         _res: express.Response, | ||||
|         next: express.NextFunction | ||||
|     ): Promise<void> => { | ||||
|         if (!req.auth) { | ||||
|             throw new UnauthorizedException(); | ||||
|         } else if (!accessCondition(req.auth)) { | ||||
|         } else if (!(await accessCondition(req.auth, req))) { | ||||
|             throw new ForbiddenException(); | ||||
|         } else { | ||||
|             next(); | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| import { Language } from "@dwengo-1/common/util/language"; | ||||
| import learningObjectService from "../../../services/learning-objects/learning-object-service"; | ||||
| import { authorize } from "../auth"; | ||||
| import { AuthenticatedRequest } from "../authenticated-request"; | ||||
| import { AuthenticationInfo } from "../authentication-info"; | ||||
| 
 | ||||
| export const onlyAdminsForLearningObject = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { | ||||
|     const { hruid } = req.params; | ||||
|     const { version, language } = req.query; | ||||
|     const admins = await learningObjectService.getAdmins({ | ||||
|         hruid, | ||||
|         language: language as Language, | ||||
|         version: parseInt(version as string) | ||||
|     }); | ||||
|     return auth.username in admins; | ||||
| }); | ||||
|  | @ -4,12 +4,15 @@ import { | |||
|     getAttachment, | ||||
|     getLearningObject, | ||||
|     getLearningObjectHTML, | ||||
|     handleDeleteLearningObject, | ||||
|     handlePostLearningObject | ||||
| } from '../controllers/learning-objects.js'; | ||||
| 
 | ||||
| import submissionRoutes from './submissions.js'; | ||||
| import questionRoutes from './questions.js'; | ||||
| import fileUpload from "express-fileupload"; | ||||
| import { teachersOnly } from '../middleware/auth/auth.js'; | ||||
| import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js'; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -25,7 +28,7 @@ const router = express.Router(); | |||
| // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie
 | ||||
| router.get('/', getAllLearningObjects); | ||||
| 
 | ||||
| router.post('/', fileUpload({useTempFiles: true}), handlePostLearningObject) | ||||
| router.post('/', teachersOnly, fileUpload({useTempFiles: true}), handlePostLearningObject) | ||||
| 
 | ||||
| // Parameter: hruid of learning object
 | ||||
| // Query: language
 | ||||
|  | @ -33,6 +36,12 @@ router.post('/', fileUpload({useTempFiles: true}), handlePostLearningObject) | |||
| // Example: http://localhost:3000/learningObject/un_ai7
 | ||||
| router.get('/:hruid', getLearningObject); | ||||
| 
 | ||||
| // Parameter: hruid of learning object
 | ||||
| // Query: language
 | ||||
| // Route to delete a learning object based on its hruid.
 | ||||
| // Example: http://localhost:3000/learningObject/un_ai7?language=nl&version=1
 | ||||
| router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject) | ||||
| 
 | ||||
| router.use('/:hruid/submissions', submissionRoutes); | ||||
| 
 | ||||
| router.use('/:hruid/:version/questions', questionRoutes); | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import {getLearningObjectRepository, getTeacherRepository} from "../../data/repo | |||
| import {processLearningObjectZip} from "./learning-object-zip-processing-service"; | ||||
| import {LearningObject} from "../../entities/content/learning-object.entity"; | ||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||
| import { NotFoundException } from '../../exceptions/not-found-exception.js'; | ||||
| 
 | ||||
| function getProvider(id: LearningObjectIdentifierDTO): LearningObjectProvider { | ||||
|     if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { | ||||
|  | @ -92,6 +93,19 @@ const learningObjectService = { | |||
|     async deleteLearningObject(id: LearningObjectIdentifier): Promise<LearningObject | null> { | ||||
|         const learningObjectRepository = getLearningObjectRepository(); | ||||
|         return await learningObjectRepository.removeByIdentifier(id); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a list of the usernames of the administrators of the learning object with the given identifier. | ||||
|      * @throws NotFoundException if the specified learning object was not found in the database. | ||||
|      */ | ||||
|     async getAdmins(id: LearningObjectIdentifier): Promise<string[]> { | ||||
|         const learningObjectRepo = getLearningObjectRepository(); | ||||
|         const learningObject = await learningObjectRepo.findByIdentifier(id); | ||||
|         if (!learningObject) { | ||||
|             throw new NotFoundException("The specified learning object does not exist."); | ||||
|         } | ||||
|         return learningObject.admins.map(admin => admin.username); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger