NestJS
@vperms/nest integrates with NestJS through a global guard, decorators and a
dynamic module. Decorators only store metadata; the guard hydrates the request
context once and evaluates the metadata.
import { Module } from "@nestjs/common";import { VPermsModule } from "@vperms/nest";import { VeguiPermsMemoryAdapter } from "vperms";
@Module({ imports: [ VPermsModule.forRoot({ adapter: new VeguiPermsMemoryAdapter(), workspace: "workspace", resolver: (req) => req.user?.id ?? null, }), ],})export class AppModule {}Module options
Section titled “Module options”interface VPermsModuleOptions { adapter: VeguiPermsAdapter; resolver: SubjectResolver; workspace: string; defaultParents?: DefaultParents; permissionsExport?: { path: string };}
type SubjectResolverResult = SubjectId | Principal | null;type SubjectResolver = (req: Request) => SubjectResolverResult | Promise<SubjectResolverResult>;forRoot is global. It registers the service, the VPermsGuard as an
APP_GUARD and — when permissionsExport is set — a controller that serves the
export endpoint.
Decorators
Section titled “Decorators”import { Ability, AnyPermission, Kind, Permission, Subject,} from "@vperms/nest";
@Controller("posts")export class PostsController { @Get(":id") @Permission("posts.read") read(@Ability() ability: RequestAbility) { return { canWrite: ability.can("posts.write") }; }
@Delete(":id") @AnyPermission("admin", "posts.delete") remove(@Subject() subject: Subject, @Kind() kind: SubjectType) {}}@Permission(...)requires all permissions;@AnyPermission(...)at least one. Both can be applied to methods or whole controllers, and can be combined withhandler/classmetadata resolution.- Parameter decorators
@Ability(),@Subject()and@Kind()read the hydrated state.@Ability()throws if the context is missing. - Permissions may be functions of the request:
import type { PermissionBuilder } from "@vperms/nest";
const ownsPost: PermissionBuilder = (req) => `posts.${req.params.id}.write`;
@Put(":id")@Permission(ownsPost)update() {}Evaluation order
Section titled “Evaluation order”VPermsGuard never denies on its own. For each request it hydrates the context
idempotently (guarded by a symbol on the request), then:
- If
@Permissionmetadata exists, every permission must pass, otherwisecanActivatereturnsfalse(Nest responds403). - Else if
@AnyPermissionmetadata exists, at least one must pass. - Routes without metadata are untouched.
Custom guards
Section titled “Custom guards”Extend AbilityGuard for imperatively defined rules:
import { AbilityGuard } from "@vperms/nest";
@Injectable()export class PostOwnerGuard extends AbilityGuard { protected async check(ability, context: ExecutionContext) { const request = context.switchToHttp().getRequest(); return ability.can(`posts.${request.params.id}.write`); }}check(ability, context) is abstract; getAbility, getSubject and getKind
are available to subclasses.
Request shape
Section titled “Request shape”interface VpermsRequest extends Request { ability: Ability; subject?: Subject; kind?: SubjectType;}Exports
Section titled “Exports”VPermsModule, VPermsGuard, AbilityGuard, Permission, AnyPermission,
Ability, Subject, Kind, PERMISSION_METADATA, ANY_PERMISSION_METADATA,
VPERMS_OPTIONS, VPERMS_SERVICE, PermissionBuilder, PermissionInput,
VPermsModuleOptions, VpermsRequest, SubjectResolver,
SubjectResolverResult, PermissionsExportOptions.