RBAC & permissions
Authorization is a granular, brand-aware RBAC model enforced both in the app and in the database (RLS).
The pieces
permissions— a catalog of grantable permissions, keyedresource:action(e.g.players:write,segments:export,api_keys:manage).roles— system roles (tenant-less) plus per-tenant custom roles.role_permissions— which permissions a role grants.user_brand_roles— assigns a user a role, optionally scoped to a specific brand (null = tenant-wide).user_permission_overrides— per-user grant/revoke of a single permission, optionally per brand.
System roles
| Role | Default grant |
|---|---|
| super_admin | Every permission, cross-tenant (Platform Owner). |
| tenant_admin | Everything in their tenant except the Platform-Owner-only set (tenants:manage, api_keys:manage, webhooks:manage, platform:access). |
| campaign_manager | Segments, campaigns, journeys, templates, tasks, exports, analytics read. |
| analyst | Read-only across analytics, players, team, data-quality. |
| viewer | Minimal reads (players, campaigns, analytics). |
Resolving effective permissions
auth_effective_permissions(user, brand) unions the permissions from the user’s
role(s) with per-user grant overrides, minus revoke overrides — except for a
super_admin, who keeps everything (revokes are ignored, preventing
self-lockout).
Two gate functions build on it:
auth_has_permission(perm, brand)— brand-precise; used by app-layer checks and brand-scoped RLS.auth_has_tenant_permission(perm, tenant)— tenant-level; used by the settings tables’ RLS. It is super-aware: asuper_adminholds every permission on every tenant (see ADR-013).
Tiers
The most sensitive capabilities are Tier 2, super_admin-only by default:
api_keys:manage, webhooks:manage, brand management, integrations, and custom
fields. A tenant admin can receive one only through an explicit per-user grant
override.
Enforcement in two layers
- RLS — the database rejects unauthorized reads/writes regardless of the caller (belt).
- App layer — server actions re-check permissions to return clean, localized errors and keep the UI honest (suspenders).
Both consult the same helper functions, so they can’t drift apart.
Testing
supabase/tests/rbac_permissions.sql and rbac_overrides.sql assert the
effective-permission union, brand scoping (no leak across brands), grant/revoke
override behaviour, and super_admin protection. They run in
supabase/tests/run.sh.