import { Injectable, CanActivate, ExecutionContext, SetMetadata, } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; import { PermissionDeniedError } from "../shared/errors/application-error.js"; import type { AuthenticatedRequest } from "./auth.middleware.js"; export type Permission = "CLASSES_CREATE" | "CLASSES_READ" | "CLASSES_UPDATE" | "CLASSES_DELETE"; export const Permissions = { CLASSES_CREATE: "CLASSES_CREATE" as const, CLASSES_READ: "CLASSES_READ" as const, CLASSES_UPDATE: "CLASSES_UPDATE" as const, CLASSES_DELETE: "CLASSES_DELETE" as const, }; export const PERMISSIONS_KEY = "permissions"; export const RequirePermission = (...permissions: Permission[]) => SetMetadata(PERMISSIONS_KEY, permissions); const ROLE_PERMISSIONS: Record = { admin: [ Permissions.CLASSES_CREATE, Permissions.CLASSES_READ, Permissions.CLASSES_UPDATE, Permissions.CLASSES_DELETE, ], teacher: [ Permissions.CLASSES_CREATE, Permissions.CLASSES_READ, Permissions.CLASSES_UPDATE, ], student: [Permissions.CLASSES_READ], parent: [Permissions.CLASSES_READ], }; @Injectable() export class PermissionGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { if (process.env.DEV_MODE === "true") { return true; } const requiredPermissions = this.reflector.getAllAndOverride( PERMISSIONS_KEY, [context.getHandler(), context.getClass()], ); if (!requiredPermissions || requiredPermissions.length === 0) { return true; } const request = context.switchToHttp().getRequest(); const roles = request.userRoles ?? []; for (const role of roles) { const perms = ROLE_PERMISSIONS[role]; if (perms && requiredPermissions.some((p) => perms.includes(p))) { return true; } } throw new PermissionDeniedError(requiredPermissions.join(", ")); } }