NestJS
permdock/nest provides a module, a guard and a Protect decorator so Nest controllers get a request-scoped PermDock over either the Express or the Fastify platform adapter.
Status: planned Phase: 2
Purpose
Nest structures authorization as guards and decorators, so permdock/nest maps the server kernel onto that shape: a module that registers the factory, a guard registered as APP_GUARD that builds the request-scoped instance and evaluates route metadata, a Protect decorator that attaches the permission (and optional loader) to a handler, and a parameter decorator to inject the instance. It works on both @nestjs/platform-express and @nestjs/platform-fastify because the kernel only needs the underlying request converted once. NestJS was permix's most requested and never-shipped adapter (issue 11).
API
// permdock.module.ts
import { Module } from '@nestjs/common'
import { createPermDock } from 'permdock/nest'
import { policy } from './policy'
export const { PermDockModule, PermDockGuard, Protect, InjectPermDock } = createPermDock(policy, {
subject: (request) => request.user ?? null,
})
@Module({ imports: [PermDockModule], providers: [{ provide: APP_GUARD, useClass: PermDockGuard }] })
export class AppModule {}
// posts.controller.ts
@Controller('posts')
export class PostsController {
constructor(private readonly posts: PostsService) {}
@Delete(':id')
@Protect(permissions.post.delete, (request) => loadPost(request.params.id))
async remove(@InjectPermDock() permdock: PermDock, @Param('id') id: string) {
await this.posts.remove(id)
}
@Get()
async list(@InjectPermDock() permdock: PermDock) {
return permdock.filter(permissions.post.read, await this.posts.all())
}
}| Export | Role |
|---|---|
PermDockModule | Registers the kernel factory as a provider so guards and services can inject it. |
PermDockGuard | CanActivate implementation. Builds the request-scoped instance once per request (stored on the request), reads Protect metadata from the handler and class, runs loadData, decides, and throws a ForbiddenException carrying the Problem Details body. Routes without Protect metadata pass through but still get an instance. |
Protect(permission, loadData?) | Method or class decorator setting metadata for the guard. Multiple decorators on class and method are all enforced. |
InjectPermDock() | Parameter decorator injecting the request-scoped instance into handlers. |
PermDockExceptionFilter | Optional filter that renders PermDock exceptions as application/problem+json; without it Nest's default JSON body is used. |
permdockHandler | Controller class for the AuthZEN-shaped decision endpoint, mountable under /api/permdock. |
Request lifecycle
PermDockGuardruns for every route; it converts the platform request, callssubject(request)once, builds the instance and caches it on the request object.- If the handler or class carries
Protectmetadata, the guard runs the loader, validates untrusted input, and decides. Denials throw before the handler executes. - Handlers receive the instance through
@InjectPermDock()and may callassert,filterorwhere. PermDockExceptionFiltermaps thrown PermDock errors to Problem Details;on('decision')events carry the controller and handler names.
What it validates
- Untrusted loader results and bodies against the resource schema under
validate: 'boundary', after Nest's ownValidationPipehas run. - Decision-endpoint bodies against the AuthZEN
evaluationsschema. - Metadata: a
Protectdecorator referencing a permission from a different definition than the module's policy is a type error.
How denials surface
403 application/problem+json for denied; 403 with type ending in /approval-required plus token; 400 with issues for boundary validation failures; 401 for anonymous subjects when a scheme is configured. See Problem Details.
Example app
apps/examples/nest: a posts module on the Express platform with the guard as APP_GUARD, Protect on CRUD handlers, @nestjs/swagger emitting securitySchemes through the openapi hook, the decision endpoint controller, and e2e tests with @nestjs/testing plus supertest run against both platform adapters.
Related standards
Open questions
- Exact identifier names. The plan is silent for Nest; this page proposes
PermDockModule,PermDockGuard,Protect,InjectPermDock(parameter decorator, named to avoid clashing with thePermDocktype) andPermDockExceptionFilter. The decorator could bePermissioninstead ofProtectto mirror Nest'sRolesconvention. - Whether the guard should be global (
APP_GUARD) by default or applied per controller, and how it composes with existingAuthGuardordering. - How
loadDataobtains injected services (a loader that receives theExecutionContextand aModuleRef, or a class-based loader provider). - GraphQL and microservice transports: out of scope for Phase 2, but the guard should not break when
context.getType()is nothttp.
Elysia
permdock/elysia derives a request-scoped PermDock into Elysia's context and provides protect as a typed beforeHandle hook that keeps validated route types.
Node http
permdock/node wraps the Fetch kernel for raw node:http handlers (IncomingMessage and ServerResponse) and for any framework not covered by a dedicated adapter.