Access Control (RBAC)
Permission-gated routes by default - the scope.action model, withAuthGuard, and the guard hook.
thunder-core gates every route by permission out of the box. Permissions are scope.action strings, where:
scopeis the router function name, andactionis the handler function name.
For example, a createCRUD router exposes actions like create, get, count, update, and del. A bare scope grants all actions in that router; * grants everything.
This is why thunder-core (and Thunder generally) requires named functions for routers and handlers - the names are the permission identifiers.
Data Model
Read the schema files for exact fields:
| Schema | Description |
|---|---|
schemas/accessControlPolicy.ts | A named bundle of permission strings. |
schemas/accessControl.ts | Maps a role to policies, with subRoles (roles this role may assign to invitees) and skipIntersection. |
When a role resolves to multiple access-control documents, their policy sets are intersected unless skipIntersection is set.
Checking Permissions - withAuthGuard
import { withAuthGuard } from "@/plugins/Huruf-Tech/thunder-core/utils/withAuthGuard.ts";
const { role, subRoles, policies, allow } = await withAuthGuard(req);
if (!allow("tenants", "create")) throw Response.forbidden();withAuthGuard:
- Resolves the effective role for the request (defaults to
guestwhen unauthenticated). - Applies tenant role cascading (the parent inviter's role, unless
RBAC_NO_CASCADE). - Loads the relevant policies, further narrowed by OAuth / API-key
scopesunless they include*orfull_access. - Returns an
allow(scope, action)predicate.
The Guard Hook
hooks/guard.ts runs at very high priority (before everything). For every request it calls withAuthGuard and throws Response.forbidden(...) if allow(scope, name) is false. Every route is permission-gated by default based on the seeded policies.
Because routes are denied unless a policy grants them, exposing a new route means adding its scope.action to a policy and attaching that policy to the appropriate role(s) - via the access-control routes, or by editing scripts/syncAccessControl.ts.
Default Roles & Management
Default seeded roles and policies are defined in scripts/syncAccessControl.ts - read it for the current role names, their policies, and each policy's permission strings (the defaults include roles like guest, user, and admin).
Access-control management routes:
routes/accessControl.ts- role to policy mappings.routes/accessControlPolicies.ts- policies; also exposes a "my policies" endpoint returning the caller's role / subRoles / policies.