@apipass/cerbos-pep
v0.0.109
Published
Cerbos PEP utility for NestJS
Readme
@apipass/cerbos-pep
NestJS Policy Enforcement Point (PEP) for Cerbos. Provides a declarative interceptor that authorizes requests against a Cerbos PDP before the handler runs, with two strategies: CheckResources and PlanResources.
Installation
npm install @apipass/cerbos-pepModule Registration
Register CerbosModule in your AppModule. It is @Global() — providers are available everywhere without re-importing.
Minimal
import { CerbosModule } from '@apipass/cerbos-pep'
@Module({
imports: [
CerbosModule.register({
url: process.env.CERBOS_PDP_URL ?? 'http://127.0.0.1:3592',
}),
],
})
export class AppModule {}With per-account feature toggle
import { CerbosModule } from '@apipass/cerbos-pep'
@Module({
imports: [
CerbosModule.register({
url: process.env.CERBOS_PDP_URL ?? 'http://127.0.0.1:3592',
featureToggle: { useClass: FeatureToggleCerbosAdapter },
imports: [FeatureToggleModule],
}),
],
})
export class AppModule {}When featureToggle is provided the interceptor calls isCerbosEnabled(accountId) before each check. Accounts that return false fall back to PermissionStrategy.LEGACY.
Strategies
| Strategy | Use case | Throws ForbiddenException when |
|---|---|---|
| CheckResources | Single or multi-resource write/read endpoints | all checked resources are denied |
| PlanResources | List / search endpoints — returns a query filter | Cerbos returns KIND_ALWAYS_DENIED |
Usage
Apply @CerbosPolicy on a route method. The interceptor runs automatically — no @UseInterceptors needed.
List endpoint — PlanResources
import { CerbosPolicy, CerbosStrategy, PermissionContext, PermissionContextType } from '@apipass/cerbos-pep'
@CerbosPolicy({
strategy: CerbosStrategy.PlanResources,
kind: 'project',
actions: ['read'],
})
@Get('/projects')
async listProjects(@PermissionContext() ctx: PermissionContextType) {
// ctx.queryPlan — pass this to your repository to filter results
return this.projectsService.findAll(ctx.queryPlan)
}Single-resource endpoint — CheckResources
@CerbosPolicy({
strategy: CerbosStrategy.CheckResources,
resources: [{ kind: 'flow', actions: ['write'] }],
resourceContext: (req) => ({
id: req.params.flowId,
attributes: { parentProject: req.params.projectId },
}),
})
@Put('/flows/:flowId')
async updateFlow(@Param('flowId') id: string) {
// handler runs only if Cerbos allows — ForbiddenException is thrown otherwise
}Multi-resource endpoint — CheckResources
@CerbosPolicy({
strategy: CerbosStrategy.CheckResources,
resources: [
{ kind: 'stage', actions: ['read'] },
{ kind: 'flow', actions: ['read'] },
],
})
@Get('/stages')
async listStages(@PermissionContext() ctx: PermissionContextType) {
// ForbiddenException only if BOTH resources are denied
// ctx.deniedResources contains which resources had denied actions (partial denial)
}@PermissionContext()
Param decorator that extracts the PermissionContextType set by the interceptor.
export type PermissionContextType =
| { strategy: PermissionStrategy.LEGACY }
| {
strategy: PermissionStrategy.CERBOS
queryPlan?: Record<string, unknown> // PlanResources — apply as repository filter
deniedResources?: CerbosCheckDenial[] // CheckResources — empty = fully allowed
}Use strategy to distinguish between legacy-authorized routes and Cerbos-authorized routes.
CerbosFeatureTogglePort
Implement this interface in the consuming API to enable per-account Cerbos toggling:
import { Injectable } from '@nestjs/common'
import { CerbosFeatureTogglePort } from '@apipass/cerbos-pep'
@Injectable()
export class FeatureToggleCerbosAdapter implements CerbosFeatureTogglePort {
constructor(private readonly featureToggleService: FeatureToggleS3Service) {}
async isCerbosEnabled(accountId: string): Promise<boolean> {
const features = await this.featureToggleService.getFeatures(accountId)
return features?.isCerbosEnabled() ?? false
}
}Expected Request Headers
The interceptor reads authorization context from headers injected by the API Gateway — never from the JWT directly.
| Header | Description |
|---|---|
| account_id | Tenant account ID — used as the Cerbos accountId scope |
| role | User role (e.g. account-admin) — sanitized to account_admin before sending to Cerbos |
| account_domain | Account domain — merged into principal.attr and resource.attr |
@apipass/cerbos-pep — Português
NestJS Policy Enforcement Point (PEP) para o Cerbos. Fornece um interceptor declarativo que autoriza requisições contra um Cerbos PDP antes do handler executar, com duas estratégias: CheckResources e PlanResources.
Instalação
npm install @apipass/cerbos-pepRegistro do Módulo
Registre o CerbosModule no seu AppModule. Ele é @Global() — os providers ficam disponíveis em todos os módulos sem precisar reimportar.
Mínimo
import { CerbosModule } from '@apipass/cerbos-pep'
@Module({
imports: [
CerbosModule.register({
url: process.env.CERBOS_PDP_URL ?? 'http://127.0.0.1:3592',
}),
],
})
export class AppModule {}Com feature toggle por conta
import { CerbosModule } from '@apipass/cerbos-pep'
@Module({
imports: [
CerbosModule.register({
url: process.env.CERBOS_PDP_URL ?? 'http://127.0.0.1:3592',
featureToggle: { useClass: FeatureToggleCerbosAdapter },
imports: [FeatureToggleModule],
}),
],
})
export class AppModule {}Quando featureToggle é fornecido, o interceptor chama isCerbosEnabled(accountId) antes de cada verificação. Contas que retornam false usam PermissionStrategy.LEGACY.
Estratégias
| Estratégia | Caso de uso | Lança ForbiddenException quando |
|---|---|---|
| CheckResources | Endpoints de leitura/escrita em um ou múltiplos recursos | todos os recursos verificados são negados |
| PlanResources | Endpoints de listagem/busca — retorna um filtro de query | Cerbos retorna KIND_ALWAYS_DENIED |
Uso
Aplique @CerbosPolicy no método de rota. O interceptor executa automaticamente — não é necessário @UseInterceptors.
Endpoint de listagem — PlanResources
import { CerbosPolicy, CerbosStrategy, PermissionContext, PermissionContextType } from '@apipass/cerbos-pep'
@CerbosPolicy({
strategy: CerbosStrategy.PlanResources,
kind: 'project',
actions: ['read'],
})
@Get('/projects')
async listProjects(@PermissionContext() ctx: PermissionContextType) {
// ctx.queryPlan — passe para o repositório para filtrar os resultados
return this.projectsService.findAll(ctx.queryPlan)
}Endpoint de recurso único — CheckResources
@CerbosPolicy({
strategy: CerbosStrategy.CheckResources,
resources: [{ kind: 'flow', actions: ['write'] }],
resourceContext: (req) => ({
id: req.params.flowId,
attributes: { parentProject: req.params.projectId },
}),
})
@Put('/flows/:flowId')
async updateFlow(@Param('flowId') id: string) {
// o handler só executa se o Cerbos permitir — caso contrário, ForbiddenException é lançada
}Endpoint com múltiplos recursos — CheckResources
@CerbosPolicy({
strategy: CerbosStrategy.CheckResources,
resources: [
{ kind: 'stage', actions: ['read'] },
{ kind: 'flow', actions: ['read'] },
],
})
@Get('/stages')
async listStages(@PermissionContext() ctx: PermissionContextType) {
// ForbiddenException somente se TODOS os recursos forem negados
// ctx.deniedResources indica quais recursos tiveram ações negadas (negação parcial)
}@PermissionContext()
Decorator de parâmetro que extrai o PermissionContextType definido pelo interceptor.
export type PermissionContextType =
| { strategy: PermissionStrategy.LEGACY }
| {
strategy: PermissionStrategy.CERBOS
queryPlan?: Record<string, unknown> // PlanResources — aplicar como filtro no repositório
deniedResources?: CerbosCheckDenial[] // CheckResources — vazio = totalmente permitido
}Use strategy para distinguir rotas autorizadas pelo Cerbos das rotas legacy.
CerbosFeatureTogglePort
Implemente esta interface na API consumidora para habilitar o toggle do Cerbos por conta:
import { Injectable } from '@nestjs/common'
import { CerbosFeatureTogglePort } from '@apipass/cerbos-pep'
@Injectable()
export class FeatureToggleCerbosAdapter implements CerbosFeatureTogglePort {
constructor(private readonly featureToggleService: FeatureToggleS3Service) {}
async isCerbosEnabled(accountId: string): Promise<boolean> {
const features = await this.featureToggleService.getFeatures(accountId)
return features?.isCerbosEnabled() ?? false
}
}Headers Esperados
O interceptor lê o contexto de autorização dos headers injetados pelo API Gateway — nunca diretamente do JWT.
| Header | Descrição |
|---|---|
| account_id | ID da conta (tenant) — usado como escopo accountId no Cerbos |
| role | Role do usuário (ex: account-admin) — sanitizado para account_admin antes de enviar ao Cerbos |
| account_domain | Domínio da conta — mesclado em principal.attr e resource.attr |
