import { Injectable, CanActivate, ExecutionContext, ForbiddenException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Request } from 'express'; import { REQUIRED_PERMISSION_KEY, RequiredPermission } from './RequirePermission.decorator'; import { AbilitySubject } from './Roles.types'; /** * Guard that checks if the user has the required permission to access a route. * Uses CASL ability instance attached to the request by AuthorizationGuard. */ @Injectable() export class PermissionGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} /** * Checks if the user has the required permission to access the route. * @param context - The execution context * @returns A boolean indicating if the user can access the route * @throws ForbiddenException if the user doesn't have the required permission */ canActivate(context: ExecutionContext): boolean { const requiredPermission = this.reflector.getAllAndOverride( REQUIRED_PERMISSION_KEY, [context.getHandler(), context.getClass()], ); // If no permission is required, allow access if (!requiredPermission) { return true; } const request = context.switchToHttp().getRequest(); const ability = (request as any).ability; // If no ability instance is attached to the request, deny access if (!ability) { throw new ForbiddenException('Ability instance not found. Ensure AuthorizationGuard is applied.'); } const { ability: action, subject } = requiredPermission; // Check if the user has the required permission using CASL ability const hasPermission = ability.can(action, subject); if (!hasPermission) { throw new ForbiddenException( `You do not have permission to ${action} ${subject}`, ); } return true; } /** * Helper method to check if a subject value is a valid AbilitySubject. * @param subject - The subject value to check * @returns True if the subject is a valid AbilitySubject enum value */ private isValidSubject(subject: string): subject is AbilitySubject { return Object.values(AbilitySubject).includes(subject as AbilitySubject); } }