import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; import { AuthGuard } from "@nestjs/passport"; import { Request } from "express"; import { IS_PUBLIC_KEY, ROLES_KEY, PERMISSIONS_KEY, } from "../../../common/decorators"; import { normalizeRole } from "../../../common/constants/roles"; interface AuthenticatedUser { id: string; email: string; roles: string[]; role?: string; permissions: string[]; } /** * JWT Auth Guard - Validates JWT token */ @Injectable() export class JwtAuthGuard extends AuthGuard("jwt") { constructor(private reflector: Reflector) { super(); } canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); if (request?.method === "OPTIONS") { return true; } // Check if route is public const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), ]); if (isPublic) { return true; } return super.canActivate(context); } handleRequest( err: Error | null, user: TUser | false, info: any, ): TUser { if (err || !user) { if (info?.name === "TokenExpiredError") { throw new UnauthorizedException("TOKEN_EXPIRED"); } throw err || new UnauthorizedException("AUTH_REQUIRED"); } return user; } } /** * Roles Guard - Check if user has required roles */ @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const req = context.switchToHttp().getRequest(); if (req?.method === "OPTIONS") { return true; } const requiredRoles = this.reflector.getAllAndOverride( ROLES_KEY, [context.getHandler(), context.getClass()], ); if (!requiredRoles || requiredRoles.length === 0) { return true; } const user = req.user as AuthenticatedUser | undefined; if (!user) { return false; } const normalizedUserRoles = ( user.roles?.length ? user.roles : user.role ? [user.role] : [] ).map((role) => normalizeRole(role)); const normalizedRequiredRoles = requiredRoles.map((role) => normalizeRole(role), ); if (normalizedUserRoles.length === 0) { return false; } const hasRole = normalizedRequiredRoles.some((role) => normalizedUserRoles.includes(role), ); if (!hasRole) { throw new ForbiddenException("PERMISSION_DENIED"); } return true; } } /** * Permissions Guard - Check if user has required permissions */ @Injectable() export class PermissionsGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const req = context.switchToHttp().getRequest(); if (req?.method === "OPTIONS") { return true; } const requiredPermissions = this.reflector.getAllAndOverride( PERMISSIONS_KEY, [context.getHandler(), context.getClass()], ); if (!requiredPermissions || requiredPermissions.length === 0) { return true; } const user = req.user as AuthenticatedUser | undefined; if (!user || !user.permissions) { return false; } const hasPermission = requiredPermissions.every((permission) => user.permissions.includes(permission), ); if (!hasPermission) { throw new ForbiddenException("PERMISSION_DENIED"); } return true; } }