import assert from 'node:assert/strict'; import test from 'node:test'; import { ExecutionContext, ForbiddenException, UnauthorizedException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { RequirePermissions } from '../../src/authorization/decorators/require-permissions.decorator'; import { PermissionsGuard } from '../../src/authorization/guards/permissions.guard'; import { Public } from '../../src/auth/decorators/public.decorator'; import type { AuthPrincipal } from '../../src/common/http/request-context'; @RequirePermissions('dashboard.read') class ProtectedController { @RequirePermissions('users.create') createUser() {} openToAuthenticatedUsers() {} } class UndecoratedController { handle() {} } @RequirePermissions('dashboard.read') class PublicController { @Public() handle() {} } function principal(permissions: string[]): AuthPrincipal { return { userId: 'b6f6b10e-434a-4d72-9ec9-3520212dd290', username: 'admin', sessionId: '1bab61ac-65d1-40e6-816b-63d90131a795', firstName: 'Admin', lastName: 'DH', email: null, mustChangePassword: false, roles: ['admin'], permissions, transport: 'cookie', }; } function context( controller: new () => object, handlerName: string, auth?: AuthPrincipal, ): ExecutionContext { const handler = controller.prototype[ handlerName as keyof typeof controller.prototype ]; return { getHandler: () => handler, getClass: () => controller, switchToHttp: () => ({ getRequest: () => ({ auth }), }), } as unknown as ExecutionContext; } const guard = new PermissionsGuard(new Reflector()); test('PermissionsGuard leaves handlers without requirements available', () => { assert.equal( guard.canActivate(context(UndecoratedController, 'handle')), true, ); }); test('PermissionsGuard gives explicit public metadata priority', () => { assert.equal(guard.canActivate(context(PublicController, 'handle')), true); }); test('PermissionsGuard returns 401 when required permissions have no principal', () => { assert.throws( () => guard.canActivate(context(ProtectedController, 'createUser')), (error: unknown) => error instanceof UnauthorizedException && error.getStatus() === 401, ); }); test('PermissionsGuard returns 403 when any required permission is missing', () => { assert.throws( () => guard.canActivate( context( ProtectedController, 'createUser', principal(['dashboard.read']), ), ), (error: unknown) => error instanceof ForbiddenException && error.getStatus() === 403, ); }); test('PermissionsGuard requires changing a temporary password first', () => { const auth = principal(['dashboard.read', 'users.create']); auth.mustChangePassword = true; assert.throws( () => guard.canActivate(context(ProtectedController, 'createUser', auth)), (error: unknown) => error instanceof ForbiddenException && error.getStatus() === 403 && (error.getResponse() as { code?: string }).code === 'PASSWORD_CHANGE_REQUIRED', ); }); test('PermissionsGuard authorizes when class and handler permissions are granted', () => { assert.equal( guard.canActivate( context( ProtectedController, 'createUser', principal(['dashboard.read', 'users.create']), ), ), true, ); }); test('PermissionsGuard applies class requirements to every handler', () => { assert.equal( guard.canActivate( context( ProtectedController, 'openToAuthenticatedUsers', principal(['dashboard.read']), ), ), true, ); });