Developer DocsRBAC & permissions

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, keyed resource: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

RoleDefault grant
super_adminEvery permission, cross-tenant (Platform Owner).
tenant_adminEverything in their tenant except the Platform-Owner-only set (tenants:manage, api_keys:manage, webhooks:manage, platform:access).
campaign_managerSegments, campaigns, journeys, templates, tasks, exports, analytics read.
analystRead-only across analytics, players, team, data-quality.
viewerMinimal 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: a super_admin holds 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

  1. RLS — the database rejects unauthorized reads/writes regardless of the caller (belt).
  2. 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.