YYayaw

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:

  1. Users belong to groups.
  2. Groups receive roles through explicit bindings.
  3. Roles contain policies.
  4. Policies grant or deny actions on resources.
  5. 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:

  • read
  • list
  • create
  • update
  • delete
  • invite
  • manage

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:

  • groups
  • group_memberships
  • roles
  • role_policies
  • group_role_bindings
  • authorization_audit_events
  • authorization_decision_logs

group_role_bindings is the key scope table. A group can receive a role with:

  • global scope
  • organization scope
  • resource scope

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 Admin
  • Organization Admin
  • Organization Manager
  • Organization Member
  • Authenticated 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.ts
  • src/lib/server/services/authz/better-auth-sync-drizzle.ts
  • src/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.ts
  • src/lib/server/authz/policy-loader.ts
  • src/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 explainability
  • can(...) 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.ts

This 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:

ResourceMemberManager/Admin
pagelist/readmanage
sectionslist/readmanage
medialist/readmanage
code-accesslist/readmanage

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:

  1. Add it to RESOURCES in src/lib/server/authz/contracts.ts.
  2. Add seed policies for Super Admin.
  3. Add organization-role policies when the resource is organization scoped.
  4. Add migrations or repair SQL for existing installs when needed.
  5. Add or update route config permissions.
  6. Add generated action mappings if the resource has database actions.
  7. Update English docs and content/llm/llm-source.md when the resource changes architecture or assistant guidance.
  8. Run authz contract tests.

Debugging Access

When a user cannot access a page:

  1. Confirm the active organization is correct.
  2. Confirm Better Auth membership exists.
  3. Confirm the corresponding Yayaw group membership exists.
  4. Confirm the group has a scoped role binding.
  5. Confirm the role has a policy for the requested resource/action.
  6. Use the quick evaluator in /dashboard/admin/authorization.
  7. Check authorization_decision_logs when 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