From cc35d78cd03287f1007a5a8fc889517d6dac1f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20G=C3=B6rlitz?= Date: Sun, 19 May 2024 22:05:43 +0200 Subject: [PATCH] centralize "isMentor" to middleware --- .../20221121101837-PermissionSeeder.ts | 2 ++ backend/src/Router.ts | 4 ++++ .../EndorsementGroupAdminController.ts | 5 ---- .../fast-track/FastTrackAdminController.ts | 4 ---- .../LogTemplateAdminController.ts | 5 ---- .../MentorGroupAdminController.ts | 23 +++---------------- .../controllers/user/UserCourseController.ts | 19 ++++++++++++--- .../src/libraries/vateud/VateudCoreLibrary.ts | 10 ++++---- .../vateud/VateudCoreLibraryTypes.ts | 3 +-- .../ExceptionInterceptorMiddleware.ts | 10 +++++++- backend/src/middlewares/IsMentorMiddleware.ts | 15 ++++++++++++ frontend/src/components/template/SideNav.tsx | 19 +++++++++++---- 12 files changed, 68 insertions(+), 51 deletions(-) create mode 100644 backend/src/middlewares/IsMentorMiddleware.ts diff --git a/backend/db/seeders/20221121101837-PermissionSeeder.ts b/backend/db/seeders/20221121101837-PermissionSeeder.ts index f7ed9f7..484dfcf 100644 --- a/backend/db/seeders/20221121101837-PermissionSeeder.ts +++ b/backend/db/seeders/20221121101837-PermissionSeeder.ts @@ -18,6 +18,8 @@ const allPerms = [ "lm.endorsement_groups.edit", "lm.endorsement_groups.create", + "lm.training_types.view", + "atd.view", "atd.solo.delete", "atd.examiner.view", diff --git a/backend/src/Router.ts b/backend/src/Router.ts index eefeec6..532c7f4 100644 --- a/backend/src/Router.ts +++ b/backend/src/Router.ts @@ -37,6 +37,7 @@ import JoblogAdminController from "./controllers/admin-logs/JoblogAdminControlle import UserInformationController from "./controllers/user/UserInformationController"; import CourseInformationAdministrationController from "./controllers/course/CourseInformationAdministrationController"; import UserAdminController from "./controllers/user/UserAdminController"; +import { isMentorMiddleware } from "./middlewares/IsMentorMiddleware"; export const routerGroup = (callback: (router: Router) => void) => { const router = Router(); @@ -172,6 +173,9 @@ router.use( router.use( "/administration", routerGroup((r: Router) => { + // For all routes from this point onwards, a user MUST be at least a mentor. + r.use(isMentorMiddleware); + r.use( "/user", routerGroup((r: Router) => { diff --git a/backend/src/controllers/endorsement-group/EndorsementGroupAdminController.ts b/backend/src/controllers/endorsement-group/EndorsementGroupAdminController.ts index 44bbae3..7721f35 100644 --- a/backend/src/controllers/endorsement-group/EndorsementGroupAdminController.ts +++ b/backend/src/controllers/endorsement-group/EndorsementGroupAdminController.ts @@ -289,13 +289,8 @@ async function removeStationByID(request: Request, response: Response, next: Nex */ async function getUsersByID(request: Request, response: Response, next: NextFunction) { try { - const user: User = response.locals.user; const params = request.params as { id: string }; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor."); - } - const endorsementGroup = await EndorsementGroup.findOne({ where: { id: params.id, diff --git a/backend/src/controllers/fast-track/FastTrackAdminController.ts b/backend/src/controllers/fast-track/FastTrackAdminController.ts index 82871db..9adaffe 100644 --- a/backend/src/controllers/fast-track/FastTrackAdminController.ts +++ b/backend/src/controllers/fast-track/FastTrackAdminController.ts @@ -90,10 +90,6 @@ async function create(request: Request, response: Response, next: NextFunction) const user: User = response.locals.user; const data = request.body; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor"); - } - const file_name = handleUpload(request); if (file_name == null) { diff --git a/backend/src/controllers/log-template/LogTemplateAdminController.ts b/backend/src/controllers/log-template/LogTemplateAdminController.ts index 6542740..97dff21 100644 --- a/backend/src/controllers/log-template/LogTemplateAdminController.ts +++ b/backend/src/controllers/log-template/LogTemplateAdminController.ts @@ -32,11 +32,6 @@ async function getAll(_request: Request, response: Response, next: NextFunction) */ async function getAllMinimalData(_request: Request, response: Response, next: NextFunction) { try { - const user: User = response.locals.user; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor"); - } - const logTemplates = await TrainingLogTemplate.findAll({ attributes: ["id", "name"], }); diff --git a/backend/src/controllers/mentor-group/MentorGroupAdminController.ts b/backend/src/controllers/mentor-group/MentorGroupAdminController.ts index e805677..7be449f 100644 --- a/backend/src/controllers/mentor-group/MentorGroupAdminController.ts +++ b/backend/src/controllers/mentor-group/MentorGroupAdminController.ts @@ -112,11 +112,6 @@ async function update(request: Request, response: Response, next: NextFunction) */ async function getAll(_request: Request, response: Response, next: NextFunction) { try { - const user: User = response.locals.user; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor."); - } - const mentorGroups: MentorGroup[] = await MentorGroup.findAll({ include: { association: MentorGroup.associations.users, @@ -174,6 +169,8 @@ async function getByID(request: Request, response: Response, next: NextFunction) /** * Gets all mentor groups in which the response.locals.user is an admin in + * This is only used to display the mentor group admin list, so we can use the respective permission, rather than generically checking + * if the user is a mentor. * @param _request * @param response * @param next @@ -181,9 +178,7 @@ async function getByID(request: Request, response: Response, next: NextFunction) async function getAllAdmin(_request: Request, response: Response, next: NextFunction) { try { const user: User = response.locals.user; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor."); - } + PermissionHelper.checkUserHasPermission(user, "lm.mentor_group.view"); const userInMentorGroup: UserBelongToMentorGroups[] = await UserBelongToMentorGroups.findAll({ where: { @@ -216,10 +211,6 @@ async function getMembers(request: Request, response: Response, next: NextFuncti const user: User = response.locals.user; const query = request.query; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor"); - } - // Check if the mentor group is even a number const mentor_group_id = Number(query.mentor_group_id); if (isNaN(mentor_group_id)) { @@ -274,9 +265,6 @@ async function getMembers(request: Request, response: Response, next: NextFuncti async function getAllCourseManager(_request: Request, response: Response, next: NextFunction) { try { const user: User = response.locals.user; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor"); - } const userInMentorGroup: UserBelongToMentorGroups[] = await UserBelongToMentorGroups.findAll({ where: { @@ -389,13 +377,8 @@ async function removeMember(request: Request, response: Response, next: NextFunc */ async function getEndorsementGroupsByID(request: Request, response: Response, next: NextFunction) { try { - const user: User = response.locals.user; const params = request.params as { mentor_group_id: string }; - if (!(await user.isMentor())) { - throw new ForbiddenException("You are not a mentor."); - } - Validator.validate(params, { mentor_group_id: [ValidationTypeEnum.NON_NULL], }); diff --git a/backend/src/controllers/user/UserCourseController.ts b/backend/src/controllers/user/UserCourseController.ts index 2b7913b..a89b5d1 100644 --- a/backend/src/controllers/user/UserCourseController.ts +++ b/backend/src/controllers/user/UserCourseController.ts @@ -7,6 +7,8 @@ import Validator, { ValidationTypeEnum } from "../../utility/Validator"; import { MentorGroup } from "../../models/MentorGroup"; import { TrainingType } from "../../models/TrainingType"; import { HttpStatusCode } from "axios"; +import { ForbiddenException } from "../../exceptions/ForbiddenException"; +import PermissionHelper from "../../utility/helper/PermissionHelper"; /** * Returns courses that are available to the current user (i.e. not enrolled in course) @@ -177,13 +179,16 @@ async function withdrawFromCourseByUUID(request: Request, response: Response) { /** * Gets all courses that the current user can mentor (i.e. is member of a mentor group, which is * assigned to a course) - * @param request + * @param _request * @param response * @param next */ -async function getMentorable(request: Request, response: Response, next: NextFunction) { +async function getMentorable(_request: Request, response: Response, next: NextFunction) { try { const user: User = response.locals.user; + if (!(await user.isMentor())) { + throw new ForbiddenException("You are not a mentor"); + } const userWithCourses = await User.findOne({ where: { @@ -240,10 +245,18 @@ async function getMentorable(request: Request, response: Response, next: NextFun * Gets the courses that the user can actively edit * These are all courses associated to a user through their respective * mentor groups, where admin == true! + * + * This is only used to display the course list, so we can simply use the respective permission + * @param _request + * @param response + * @param next */ -async function getEditable(request: Request, response: Response, next: NextFunction) { +async function getEditable(_request: Request, response: Response, next: NextFunction) { try { const user: User = response.locals.user; + if (!user.isMentor()) { + throw new ForbiddenException("You are not a mentor"); + } const dbUser = await User.findOne({ where: { diff --git a/backend/src/libraries/vateud/VateudCoreLibrary.ts b/backend/src/libraries/vateud/VateudCoreLibrary.ts index 2483b6b..8e24e0b 100644 --- a/backend/src/libraries/vateud/VateudCoreLibrary.ts +++ b/backend/src/libraries/vateud/VateudCoreLibrary.ts @@ -6,7 +6,7 @@ import { VateudCoreSoloCreateT, VateudCoreSoloRemoveResponseT, VateudCoreSoloRemoveT, - VateudCoreTypeEnum + VateudCoreTypeEnum, } from "./VateudCoreLibraryTypes"; import { Config } from "../../core/Config"; import { UserSolo } from "../../models/UserSolo"; @@ -42,7 +42,7 @@ async function _send(props: SendT): Promise { return res.data as T; } catch (e: any) { - console.error(e) + console.error(e); Logger.log(LogLevels.LOG_WARN, e); return undefined; } @@ -136,13 +136,13 @@ export async function removeSolo(userSolo: UserSolo) { * - If it fails more than n times, then it really isn't our problem anymore tbh... */ export async function createEndorsement(userEndorsement: EndorsementGroupsBelongsToUsers, endorsementGroup: EndorsementGroup | null) { - if(!endorsementGroup) return false; + if (!endorsementGroup) return false; const endorsementInfo: VateudCoreEndorsementCreateT = { local_id: userEndorsement.id, post_data: { user_cid: userEndorsement.user_id, position: endorsementGroup.name, - instructor_cid: 1439797,//todo userEndorsement.created_by, + instructor_cid: 1439797, //todo userEndorsement.created_by, }, }; @@ -169,5 +169,3 @@ export async function createEndorsement(userEndorsement: EndorsementGroupsBelong return true; } - - diff --git a/backend/src/libraries/vateud/VateudCoreLibraryTypes.ts b/backend/src/libraries/vateud/VateudCoreLibraryTypes.ts index 52c9cf6..6182046 100644 --- a/backend/src/libraries/vateud/VateudCoreLibraryTypes.ts +++ b/backend/src/libraries/vateud/VateudCoreLibraryTypes.ts @@ -24,7 +24,6 @@ export type VateudCoreSoloCreateT = { }; }; - export type VateudCoreSoloCreateResponseT = { success: boolean; data: { @@ -72,4 +71,4 @@ export type VateudCoreEndorsementCreateResponseT = { created_at: string; updated_at: string; }; -}; \ No newline at end of file +}; diff --git a/backend/src/middlewares/ExceptionInterceptorMiddleware.ts b/backend/src/middlewares/ExceptionInterceptorMiddleware.ts index 1c43766..d655096 100644 --- a/backend/src/middlewares/ExceptionInterceptorMiddleware.ts +++ b/backend/src/middlewares/ExceptionInterceptorMiddleware.ts @@ -10,7 +10,15 @@ import { User } from "../models/User"; const sequelizeErrors = ["SequelizeValidationError", "SequelizeForeignKeyConstraintError", "SequelizeUniqueConstraintError"]; -export async function exceptionInterceptorMiddleware(error: any, request: Request, response: Response, next: NextFunction) { +/** + * Intercepts and properly formats any exceptions that occur, such that the frontend + * can handle the data accordingly. + * @param error + * @param request + * @param response + * @param _next + */ +export async function exceptionInterceptorMiddleware(error: any, request: Request, response: Response, _next: NextFunction) { console.error(error); if (error instanceof UnauthorizedException) { diff --git a/backend/src/middlewares/IsMentorMiddleware.ts b/backend/src/middlewares/IsMentorMiddleware.ts new file mode 100644 index 0000000..da98e28 --- /dev/null +++ b/backend/src/middlewares/IsMentorMiddleware.ts @@ -0,0 +1,15 @@ +import { NextFunction, Request, Response } from "express"; +import { User } from "../models/User"; +import { ForbiddenException } from "../exceptions/ForbiddenException"; + +export async function isMentorMiddleware(_request: Request, response: Response, next: NextFunction) { + const user: User = response.locals.user; + + if (!(await user.isMentor())) { + const exception = new ForbiddenException("You are not a mentor!"); + next(exception); + return; + } + + next(); +} diff --git a/frontend/src/components/template/SideNav.tsx b/frontend/src/components/template/SideNav.tsx index 5c37997..b18a715 100644 --- a/frontend/src/components/template/SideNav.tsx +++ b/frontend/src/components/template/SideNav.tsx @@ -175,19 +175,28 @@ export function SideNav() { elementTrue={ <>
LM
- }> + }> Freigabegruppen - }> + }> Mentorgruppen - }> + }> Kurse - }> + }> Trainingstypen - } href={"administration/action-requirement"}> + } + href={"administration/action-requirement"}> Aktionen | Bedingungen