Super Admin Access
Platform super admins can open the /admin panel. This is platform-scoped authorization, deliberately separate from organization/workspace roles: a workspace owner is not a platform admin.
Platform super admins can open the /admin panel. This is platform-scoped
authorization, deliberately separate from organization/workspace roles: a
workspace owner is not a platform admin.
Source: convex/admin/access.ts
and convex/admin/accessPolicy.ts.
How access is granted
Access is decided server-side from two env vars (see Environment Variables):
SUPER_ADMIN_EMAILS— comma-separated allow-list of emails. Blank/unset means nobody is an admin and the panel is effectively off.SUPER_ADMIN_REQUIRE_2FA—true(default) orfalse. When on, an allow-listed user must also have Better Auth two-factor enabled.
The decision (decideSuperAdminAccess) runs in order: authenticated → mapped to
an app users row → email verified → email in the allow-list → 2FA enrolled
(if required). Each step has a stable reason the UI can show.
The verified-email check is still enforced even though new email/password
accounts must verify before sign-in. It protects legacy or externally imported
unverified accounts and keeps the admin policy independent of provider-specific
behavior. Google/Microsoft OAuth and magic link all set emailVerified; an
unverified password account does not. (The check runs before the allow-list so
an unverified session never learns who is allow-listed.)
Set the vars on the Convex deployment, not just .env:
bun --cwd packages/backend convex env set SUPER_ADMIN_EMAILS "you@example.com"
bun --cwd packages/backend convex env set SUPER_ADMIN_REQUIRE_2FA "true"Security properties
- No client input is trusted. The email and 2FA status are read from the
Better Auth session server-side; no user id or email argument can grant
access. (
getSuperAdminAccessonly returns a display-safe snapshot.) - Verified email required. An allow-listed address with an unverified email
is denied (
email_not_verified), so an unclaimed admin email cannot be taken over via unverified signup. - Server-enforced everywhere. Every admin query calls
requireSuperAdmin; every admin mutation re-resolves access withresolveSuperAdminAccess. The Next.js route also gates server-side (404 for non-admins), but that is convenience — the backend is the boundary. - Denials are auditable. An authenticated non-admin hitting an admin write
produces an
admin.access_deniedaudit event. Because a Convex mutation rolls back all writes when it throws, the admin writes return a structured failure result instead of throwing, so the denial/failure audit actually commits. Anonymous callers are not recorded (avoids a write-spam vector). Denied reads cannot be audited (a Convex query cannot write). - Writes require step-up. Admin writes additionally require sensitive-action verification — see Admin: Workspaces.
Why an env allow-list and not a database table
The starter intentionally has no super_admins table. An env allow-list is easier to
reason about, has no bootstrap problem, and avoids shipping a second
admin-management product inside the starter. To add runtime grants later:
- Add a
super_adminstable keyed by app user. - Add grant/revoke mutations protected by
requireSuperAdminInMutation. - Require sensitive-action step-up for every grant/revoke.
Removing it
Audit Log
A minimal, first-party audit log for application-level events. It is a starter baseline you can keep, extend, or delete — not a compliance product. A simple time-based retention cron bounds table growth, but there is no legal hold, immutable storage, or SIEM export.
Multi-Tenancy Overview
Leave Localhost is designed as a B2B multi-tenant SaaS. It uses the concept of Organizations (often called Workspaces or Teams) to group users, data, and billing subscriptions.