Express
@vperms/express wires a VeguiPermsService into an
Express application: it resolves the current subject on every request, exposes
an ability on req, ships route guards and can serve the
permissions export endpoint.
import express from "express";import { vpermsMiddleware, hasPermission } from "@vperms/express";import { VeguiPermsMemoryAdapter } from "vperms";
const app = express();
app.use( vpermsMiddleware({ adapter: new VeguiPermsMemoryAdapter(), workspace: "workspace", resolver: (req) => req.session?.userId ?? null, }),);
app.get("/posts/:id", hasPermission("posts.read"), async (req, res) => { res.json({ allowed: await req.ability.can("posts.write") });});Middleware options
Section titled “Middleware options”interface VpermsMiddlewareOptions { adapter: VeguiPermsAdapter; resolver: SubjectResolver; workspace: string | WorkspaceResolver; defaultParents?: DefaultParents; permissionsExport?: { path: string };}
type SubjectResolver = (req: Request) => SubjectId | Principal | null | Promise<...>;type WorkspaceResolver = (req: Request) => string | Promise<string>;resolverreturns the subject identity for the request. Returningnullresolves the anonymous subject (anonymous, typeanon).workspaceis a constant id or a function when the workspace depends on the request (host, tenant, path segment…).permissionsExport.pathenables the export route, e.g."/vperms/subject/:subjectId".
On each request the middleware hydrates req.ability, req.subject and
req.kind, then calls next(). Any thrown error is forwarded to next(error).
Route guards
Section titled “Route guards”import { hasPermission, hasAnyPermission } from "@vperms/express";
app.get("/posts/:id", hasPermission("posts.read"), handler);app.delete("/posts/:id", hasAnyPermission("admin", "posts.delete"), handler);hasPermission requires all inputs, hasAnyPermission requires at
least one. Permissions may also be functions of the request:
import type { PermissionBuilder } from "@vperms/express";
const ownsPost: PermissionBuilder = (req) => `posts.${req.params.id}.write`;
app.put("/posts/:id", hasPermission(ownsPost), handler);Both guards respond 403 with res.sendStatus(403) when denied. If
vpermsMiddleware was not registered first they throw a descriptive error
instead of silently allowing the request.
Request additions
Section titled “Request additions”The package augments the global Express Request:
declare global { namespace Express { interface Request { ability: RequestAbility; // can(permission): Promise<boolean> subject?: Subject; kind?: SubjectType; } }}req.ability.can() is memoized per request and permission.
Permissions export
Section titled “Permissions export”app.use( vpermsMiddleware({ adapter, workspace: "workspace", resolver, permissionsExport: { path: "/vperms/subject/:subjectId" }, }),);A matching GET is answered directly by the middleware:
| Status | Meaning |
|---|---|
200 |
ResolvedSubject JSON |
403 |
The caller cannot read the target permissions |
404 |
The subject does not exist |
400 |
The subject id is invalid |
See Exporting resolved permissions for the authorization rules.
Exports
Section titled “Exports”RequestAbility, vpermsMiddleware, hasPermission, hasAnyPermission,
PermissionBuilder, PermissionInput, SubjectResolver,
SubjectResolverResult, WorkspaceResolver, PermissionsExportOptions,
VpermsMiddlewareOptions, ANONYMOUS_SUBJECT_ID.