Skip to content

Authentication

Operator console (apps/admin) signs in with email OTP + JWT by default. INTERNAL_API_KEY is CI / bootstrap only. Tenant identity (users + accounts.password_hash) is separate from admin roles.

  1. Open #/login and enter an allowed admin email (ALLOWED_ADMIN_EMAILS / SUPER_ADMIN_EMAILS).
  2. Super admin: POST /api/v1/auth/login issues a JWT immediately. Regular admin: receive a 6-digit OTP and POST /api/v1/auth/otp/verify.
  3. The JWT is stored in localStorage and sent as Authorization: Bearer on /admin/*. Signing key is Worker secret AUTH_SECRET (HS256).

CI / bootstrap: paste INTERNAL_API_KEY under the collapsed key form. Optional display name is sent as X-Admin-Actor so audit rows are not only internal.

Terminal window
curl https://<esim-api-host>/api/v1/admin/me \
-H "X-Internal-Api-Key: ${INTERNAL_API_KEY}" \
-H "X-Admin-Actor: ci-bot"

Allowed admin emails (OTP path): ALLOWED_ADMIN_EMAILS. Super admins: SUPER_ADMIN_EMAILS.

SurfaceRoles
Admin (API key or JWT)super_admin | admin | viewer
Tenant (users.role)owner | admin | member | viewer

ProtectedRoute and requireAdminRoles enforce the admin roles. Unauthenticated or under-privileged sessions redirect to /login. An internal API key authenticates as super_admin.

Values live in GitHub Organization / Repository Secrets and Worker secrets. See the repo root .env.example (key names only).