Subjects
A subject is anything that can be granted permissions: a user, a service, a group, or the anonymous visitor. Subjects are persisted inside a workspace, a logical partition that namespaces both subjects and grants.
The Subject type
Section titled “The Subject type”import type { Subject } from "vperms";
interface Subject { id: string; type: SubjectType; parents: string[];}idis the subject identifier. It must be a non-empty string and is unique within a workspace.typeclassifies the subject (seeSubjectType). Type defaults can attach virtual parents to whole types.parentslists the ids this subject inherits permissions from. See Inheritance for how parents are resolved and how the!negation prefix works.
SubjectType
Section titled “SubjectType”SubjectType is a string enum with four members:
| Member | Value | Typical use |
|---|---|---|
User |
"user" |
Human accounts. |
Service |
"service" |
Machine-to-machine callers. |
Group |
"group" |
Roles and teams that hold grants. |
Anon |
"anon" |
The anonymous, unauthenticated user. |
import { SubjectType } from "vperms";
const group = { id: "developers", type: SubjectType.Group, parents: [],};Workspaces
Section titled “Workspaces”Every service method receives a workspace identifier as its first argument. Subjects are stored per workspace, so the same id in two workspaces is two different subjects:
await vperms.saveSubject("workspace-a", { id: "user", type: SubjectType.User, parents: [] });await vperms.saveSubject("workspace-b", { id: "user", type: SubjectType.User, parents: [] });Workspace ids are opaque strings validated by WorkspaceIdSchema. Use a
workspace to isolate tenants, environments or independent authorization domains.
Principals
Section titled “Principals”A Principal is any object that can answer which subject id it represents.
Methods that identify a subject accept either a raw id or a Principal:
import type { Principal } from "vperms";
class UserPrincipal implements Principal { constructor(private readonly id: string) {}
getSubjectId(): string { return this.id; }}
await vperms.can("workspace", new UserPrincipal("user"), "posts.read");The service normalizes the value with getSubjectId() before validating it, so
adapters only ever see subject ids. This lets you pass an authenticated user
object, an actor wrapper, or any object that knows its own subject id without
unwrapping it at the call site.
The anonymous subject
Section titled “The anonymous subject”When an integration’s subject resolver returns null or undefined, the
request is treated as anonymous. The reserved id is exported as
ANONYMOUS_SUBJECT_ID:
import { ANONYMOUS_SUBJECT_ID } from "vperms";
console.log(ANONYMOUS_SUBJECT_ID); // "anonymous"The anonymous subject is created on demand with SubjectType.Anon the first
time it is needed (for example, by resolveRequestContext), so it can receive
grants just like any other subject. See
Middleware and requests for the request context.
Validation
Section titled “Validation”Subjects are validated by SubjectSchema before they reach an adapter:
idis a non-empty string (SubjectIdSchema).typeis one of theSubjectTypevalues.parentsis an array of ids; the!prefix is allowed in stored parents (it is a negation directive), but not in default parents.
Invalid input throws a ZodError (see
Errors).