import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { ANY_AUTHENTICATED_USER_KEY } from '../decorators/any-authenticated-user.decorator'; import { REQUIRE_PERMISSION_KEY } from '../decorators/require-permission.decorator'; import { PermissionKey } from '../permissions/permission-catalog'; import { PermissionResolverService } from '../permissions/permission-resolver.service'; import { AuthenticatedUser } from '../types/authenticated-user'; interface RequestWithUser { user?: AuthenticatedUser; } /** * Fails closed by decorator absence: a non-public route reaches this guard * only after JwtAuthGuard confirms a valid, active session, and from here it * is denied unless it carries either @RequirePermission (checked against the * caller's live permission set) or the explicit @AnyAuthenticatedUser opt-out * for routes that are meant to be reachable by any logged-in user regardless * of role. A route with neither is a bug - forgetting to decorate a new * route now fails shut at runtime instead of silently granting access to * every authenticated user and relying solely on * test/authorization-coverage.e2e-spec.ts to catch it before deploy. */ @Injectable() export class PermissionsGuard implements CanActivate { constructor( private readonly reflector: Reflector, private readonly permissionResolver: PermissionResolverService, ) {} async canActivate(context: ExecutionContext): Promise { const requiredPermissions = this.reflector.getAllAndOverride< PermissionKey[] >(REQUIRE_PERMISSION_KEY, [context.getHandler(), context.getClass()]); const request = context.switchToHttp().getRequest(); const user = request.user; if (requiredPermissions && requiredPermissions.length > 0) { if (!user) { return false; } return this.permissionResolver.hasAny(user.id, requiredPermissions); } const isAnyAuthenticatedUser = this.reflector.getAllAndOverride( ANY_AUTHENTICATED_USER_KEY, [context.getHandler(), context.getClass()], ); return Boolean(isAnyAuthenticatedUser && user); } }