# @wrnexus/authz > Composable authorization for WrNexus — role-based (RBAC), policy-based (PBAC), and attribute-based (ABAC) access control that reduces to a boolean check plus an `authorize()` guard. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/authz` is a small, server-side authorization toolkit. It gives you three interchangeable models — RBAC (roles → permissions), PBAC (policy predicates), and ABAC (attribute matchers) — that all collapse to a `boolean | Promise` decision. Wrap any decision in a `Middleware` guard (`authorize`, `requireRole`, `requirePermission`) to protect WrNexus routes. Reach for it whenever a route or action needs to be gated on who the user is, what roles they hold, or attributes of the user and the resource. It plugs into `@wrnexus/core` by reading `ctx.user` as the authorization subject. ## Installation ```bash bun add @wrnexus/authz ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API The package has a single entry point (`@wrnexus/authz`) exporting the following. ### Types | Symbol | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------- | | `Subject` | The authorized principal: `{ id?: string; roles?: string[]; [attribute: string]: unknown }`. | | `Rbac` | An RBAC checker: `{ can(subject, permission): boolean; permissionsFor(roles): Set }`. | | `Policy` | A predicate `(subject: S, resource?: R) => boolean \| Promise`. | ### RBAC #### `defineRbac(roles: Record): Rbac` Builds an RBAC checker from a role → permissions map. Supported permission forms: - `"*"` — grants every permission. - `"ns:*"` — namespace wildcard (e.g. `"post:*"` grants `"post:write"`). - `"role:"` — inherits all permissions of another role (resolved recursively, cycle-safe). The returned `Rbac` provides: - `can(subject, permission)` — `true` if any of `subject.roles` grants `permission` (honouring `*` and namespace wildcards). Returns `false` when the subject has no roles. - `permissionsFor(roles)` — the resolved `Set` of all permissions granted to a set of roles. #### `hasRole(subject: Subject | undefined, ...required: string[]): boolean` `true` if the subject holds **all** of the given roles. ### PBAC / ABAC combinators - `any(...policies: Policy[]): Policy` — allow if **any** policy passes (OR); awaits async policies. - `all(...policies: Policy[]): Policy` — allow only if **all** policies pass (AND); awaits async policies. - `attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy` — ABAC helper that allows when `subject[name]` equals `match`, or when `match` is a function, when `match(value)` is truthy. ### Guards (middleware) Each guard returns a `@wrnexus/core` `Middleware`. A denied request short-circuits with `Response.json({ ok: false, error: "Forbidden" }, { status: 403 })`. - `authorize(policy: (ctx: Context) => boolean | Promise): Middleware` — runs `policy` against the request `Context`; calls `next()` when it resolves truthy, otherwise returns 403. - `requireRole(...roles: string[]): Middleware` — allows when `ctx.user` holds **any** of the listed roles. - `requirePermission(rbac: Rbac, permission: string): Middleware` — allows when `rbac.can(ctx.user, permission)` is `true`. ## Usage ### RBAC ```ts import { defineRbac, hasRole } from "@wrnexus/authz"; const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"], viewer: ["post:read"], // role inheritance: lead gets everything an editor has, plus post:publish lead: ["role:editor", "post:publish"], }); const user = { id: "u1", roles: ["editor"] }; rbac.can(user, "post:write"); // true rbac.can(user, "post:delete"); // false rbac.permissionsFor(["lead"]); // Set { "post:read", "post:write", "post:publish" } hasRole(user, "editor"); // true ``` ### Guarding routes ```ts import { authorize, requireRole, requirePermission, defineRbac } from "@wrnexus/authz"; const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); // Only admins or editors app.get("/dashboard", requireRole("admin", "editor"), handler); // Requires a specific permission app.post("/posts", requirePermission(rbac, "post:write"), handler); // Arbitrary policy over the request context app.delete( "/posts/:id", authorize((ctx) => hasRole(ctx.user, "admin")), handler, ); ``` ### PBAC / ABAC policies ```ts import { any, all, attr, authorize, type Policy } from "@wrnexus/authz"; interface User { id: string; department?: string; roles?: string[]; } interface Post { authorId: string; } // Ownership policy (subject + resource) const ownsPost: Policy = (u, post) => u.id === post?.authorId; // ABAC: attribute equality, or a predicate const inEngineering = attr("department", "engineering"); const isVerified = attr("verified", (v) => v === true); // Compose: allow if the user owns the post OR is in engineering AND verified const canEdit = any(ownsPost, all(inEngineering, isVerified)); app.put( "/posts/:id", authorize((ctx) => canEdit(ctx.user as User, loadPost(ctx))), handler, ); ``` ## Requirements / Notes - **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported. - Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`. - Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise` (e.g. for a database ownership check).