Authorization
Yayaw RBAC, scoped group-role bindings, policy evaluation, and dashboard authorization operations.
Overview
Yayaw uses Better Auth for identity, sessions, organizations, and membership records. Application authorization is handled by Yayaw's own RBAC engine on top of those identities.
The model is group-based:
- Users belong to groups.
- Groups receive roles through explicit bindings.
- Roles contain policies.
- Policies grant or deny actions on resources.
- Bindings and checks can be scoped globally, by organization, or by resource.
The boolean can(...) helper is the facade most route loaders, server actions,
and services use. The detailed evaluator records the decision chain for
debugging and audit workflows.
Core Concepts
Resources are declared in src/lib/server/authz/contracts.ts. Current resource
families include:
- organizations and members
- platform users
- API keys
- billing plans, products, entitlements, webhook events, and code access
- control-plane audit events
- authorization administration resources
- media, pages, sections, components, global data, design tokens, and themes
- system, organization, and user-preference settings
Actions are:
readlistcreateupdatedeleteinvitemanage
manage is the broad administrative action for a resource. Prefer narrower
actions when a UI or service only needs read/list/update behavior.
Tables
Authorization tables:
groupsgroup_membershipsrolesrole_policiesgroup_role_bindingsauthorization_audit_eventsauthorization_decision_logs
group_role_bindings is the key scope table. A group can receive a role with:
globalscopeorganizationscoperesourcescope
This is why organization access is modeled as roles applied to groups, not as direct user-to-permission rows.
Seeded Roles
The seed creates the platform roles:
Super AdminOrganization AdminOrganization ManagerOrganization MemberAuthenticated User
Super Admin receives manage coverage for every declared resource and is bound
to the superadmins group.
Platform user administration uses the user:manage resource/action pair. Do
not use Better Auth's user.role as Yayaw authorization authority; Yayaw
permissions remain exclusively users -> groups -> roles -> policies.
When an organization is created, Yayaw creates organization-scoped groups:
<organizationSlug>-admin<organizationSlug>-manager<organizationSlug>-member
Each group is bound to the matching organization role with
scopeType = "organization" and scopeId = <organizationId>.
The organization creator is added to <organizationSlug>-admin, because
organization administration is represented by Yayaw group membership and scoped
role bindings rather than by reading Better Auth role strings directly.
Organization Sync
Better Auth organization hooks keep Yayaw authorization groups synchronized:
- organization creation creates default groups and assigns the creator to admin
- invitation acceptance adds members to the right organization group
- member role changes move memberships between organization groups
- member removal removes the internal group membership
Key services:
src/lib/server/services/authz/organization-setup-drizzle.tssrc/lib/server/services/authz/better-auth-sync-drizzle.tssrc/config/better-auth.config.ts
Do not create Better Auth organization members through a path that bypasses the sync service unless the code also repairs the corresponding group membership.
Policy Evaluation
Main files:
src/lib/server/authz/can.tssrc/lib/server/authz/policy-loader.tssrc/lib/server/authz/drizzle-group-membership-delegate.ts
Evaluation rules:
- unknown resources/actions deny by default
- matching deny rules win over allows
- binding scope must match the requested check scope
- organization-scoped checks require the organization id in scope
- resource-scoped checks require the resource id in scope
authorizeDetailed(...)returns a decision chain for explainabilitycan(...)returns a boolean for route and action guards
Server-side authorization must remain in the mutation/query path. UI navigation visibility is only a convenience and must never be the sole access control.
Dashboard Admin
Authorization administration lives at:
/dashboard/admin/authorization/dashboard/admin/authorization/groups/[groupId]/dashboard/admin/users
The admin UI supports:
- role and policy inspection
- group list/detail views
- group membership management
- group-role binding management
- platform user listing, organization membership maintenance, and default organization updates
- quick permission evaluation
- decision/audit diagnostics
The users surface lets superadmins add a user to another organization without
removing existing memberships, change a Better Auth organization membership
role, remove memberships, and start a Yayaw RBAC-checked impersonation session.
The impersonation endpoints intentionally wrap Better Auth's session mechanics
behind user:manage checks and block impersonating another user who also has
user:manage. When the original admin session cookie is no longer available,
stopping impersonation safely ends the impersonated session and requires the
administrator to sign in again instead of leaving the user trapped in the
impersonated account.
Use this page to inspect why a user can or cannot access a resource before changing seed policies.
Route and Action Contracts
Routes declare permission intent in the navigation/route config. Generated database actions also declare resource/action pairs. Contract tests ensure declared resources stay inside the canonical authz contract.
Important test:
bun test src/lib/server/authz/contracts.test.tsThis guards:
- navigation resources
- generated DB action resources
- Super Admin seed coverage
- user admin repair coverage
- section authz repair coverage
- navigable dashboard roots
Common Resource Expectations
Organization defaults:
| Resource | Member | Manager/Admin |
|---|---|---|
page | list/read | manage |
sections | list/read | manage |
media | list/read | manage |
code-access | list/read | manage |
Super Admin gets platform-global manage access, including billing products,
feature flags, system settings, registry templates, platform users, and
authorization resources.
Code access still requires billing eligibility. code-access:read allows a user
to reach the code-access surface, but the billing service decides whether paid
deliverables are unlocked.
Adding a Resource
When adding a resource:
- Add it to
RESOURCESinsrc/lib/server/authz/contracts.ts. - Add seed policies for Super Admin.
- Add organization-role policies when the resource is organization scoped.
- Add migrations or repair SQL for existing installs when needed.
- Add or update route config permissions.
- Add generated action mappings if the resource has database actions.
- Update English docs and
content/llm/llm-source.mdwhen the resource changes architecture or assistant guidance. - Run authz contract tests.
Debugging Access
When a user cannot access a page:
- Confirm the active organization is correct.
- Confirm Better Auth membership exists.
- Confirm the corresponding Yayaw group membership exists.
- Confirm the group has a scoped role binding.
- Confirm the role has a policy for the requested resource/action.
- Use the quick evaluator in
/dashboard/admin/authorization. - Check
authorization_decision_logswhen detailed logging is enabled.
Validation
Useful checks after authz changes:
bun test src/lib/server/authz/policy-loader.test.ts
bun test src/lib/server/authz/contracts.test.ts
bun test src/lib/server/services/authz/authz-service.test.ts
bun run check
bunx tsc --noEmit