Exporting resolved permissions
The vperms package ships a framework-independent permission export
behavior, used by the Express, Hono and Nest integrations. It serves the
JSON-safe ResolvedSubject of a target subject after authorizing the request.
Reserved permissions
Section titled “Reserved permissions”Reading resolved permissions is itself protected by reserved permission nodes:
vperms.subject.me.permissions— every subject can read its own set. It is resolved as the lowest-priority grant, so an explicit deny overrides it.vperms.subject.<subjectId>.permissions— required to read another subject’s set. Normal wildcards apply, e.g.vperms.subject.*.permissions.
import { SELF_PERMISSIONS_PERMISSION, subjectPermissionsPermission,} from "vperms";
SELF_PERMISSIONS_PERMISSION; // "vperms.subject.me.permissions"subjectPermissionsPermission("user"); // "vperms.subject.user.permissions"To let a group read everyone’s permissions:
await vperms.setPermission("workspace", "admins", "vperms.subject.*.permissions", true);Route pattern
Section titled “Route pattern”The export path must contain exactly one :param segment, which identifies the
target subject. parsePermissionsExportPath turns it into a matcher:
import { parsePermissionsExportPath } from "vperms";
const route = parsePermissionsExportPath("/subject/:subjectId");
route.base; // "subject"route.routePattern; // "subject/:subjectId"route.param; // "subjectId"route.match("/subject/user"); // { subjectId: "user" }route.match("/subject/a/b"); // nullThe literal target "me" is treated as the current subject. Any other value is
validated as a subject id.
Authorizing and loading
Section titled “Authorizing and loading”exportResolvedSubject is the shared implementation:
import { exportResolvedSubject } from "vperms";
const resolved = await exportResolvedSubject({ service, adapter, workspaceId: "workspace", currentSubjectId: context.id, targetSubjectId: "user", ability: context.ability,});It always authorizes before loading the target:
- Resolve the target (treating
"me"as the current subject) and validate it. - Require
ability.can(...)for the matching reserved permission. - Load the subject record and return its resolved permissions.
It throws typed errors so each integration can map them to an HTTP response:
| Error | Meaning | HTTP |
|---|---|---|
PermissionDeniedError |
The ability lacks the reserved node. | 403 |
SubjectNotFoundError |
No record for the target subject. | 404 |
InvalidSubjectIdError |
The target id is malformed. | 400 |
Configuring an integration
Section titled “Configuring an integration”Enable the endpoint by passing permissionsExport to the integration’s
options. The route is disabled unless configured.
// ExpressvpermsMiddleware({ adapter, workspace: "workspace", resolver: (req) => req.user?.id ?? null, permissionsExport: { path: "/subject/:subjectId" },});
// NestJSVPermsModule.forRoot({ adapter, workspace: "workspace", resolver: (req) => req.user?.id ?? null, permissionsExport: { path: "/subject/:subjectId" },});The result is a JSON ResolvedSubject that the
client and React bindings can
evaluate directly. See the per-framework pages for the exact behavior: