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:

  • scope is the router function name, and
  • action is 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:

SchemaDescription
schemas/accessControlPolicy.tsA named bundle of permission strings.
schemas/accessControl.tsMaps 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 guest when unauthenticated).
  • Applies tenant role cascading (the parent inviter's role, unless RBAC_NO_CASCADE).
  • Loads the relevant policies, further narrowed by OAuth / API-key scopes unless they include * or full_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.

On this page