Kredal Docs
Adr

ADR 0011 — Platform admin via allowlist + service-role reads

Why /admin gates on a SQL-only platform_admins table and reads through the service-role client instead of new RLS policies.

  • Status: Accepted
  • Date: 2026-07-17

Context

The founder needs a platform-administrator area (/admin) with explicitly separate access: overview KPIs, user/workspace management, a cross-workspace audit viewer, and AI/docs ops. Two questions decide the architecture: how is admin identity established, and how do admin reads cross tenant boundaries that RLS otherwise enforces?

Decision

Identity: an allowlist table, granted only via SQL. platform_admins (migration 0003) has a self-SELECT policy and no client write policies — no session, including an admin's, can grant admin. The security-definer helper is_platform_admin() is called under the caller's own JWT by requirePlatformAdmin() (src/lib/admin/guard.ts).

Reads: service-role client behind the guard — not new RLS policies. Admin pages read through createAdminClient() strictly after requirePlatformAdmin(). We did not add or is_platform_admin() to core table policies because that would (a) touch 8+ tables and add a function call to every policy evaluation for every user, and (b) expose all tenant data through the general PostgREST surface of an admin session. The service-role path confines cross-tenant access to src/lib/admin/** and src/app/admin/**, matching the existing invite/extraction pattern.

Guard layers: middleware redirect (unauth) → admin layout notFound() (non-admin) → per-function guard → admin_* audit events (null workspace_id) for every mutation. Deactivation is an auth ban (ban_duration), never a delete — audit_events.user_id is NOT NULL, so hard-deleting users would break the audit trail. Workspace deletion removes Storage objects explicitly first; they do not cascade with the DB rows.

Consequences

  • Positive: no privilege-escalation surface from the client; core RLS untouched; the whole admin capability is auditable and greppable in two directories; 404 keeps /admin invisible to non-admins.
  • Negative: more code paths using the service-role client (mitigated by the guard-first convention); granting a new admin requires SQL access (accepted — it is intentionally rare and deliberate).

On this page