Authentication
Operator console (apps/admin) uses email OTP + JWT. There is no password register/login. Tenant identity (users + accounts.password_hash) is separate from admin JWT roles.
Admin login
Section titled “Admin login”POST /api/v1/auth/loginwith{ "email": "ops@example.com" }.- Regular admins receive a one-time code (Workers Email / KV
KV_OTP). Super admins listed inSUPER_ADMIN_EMAILSmay receive an instant JWT. POST /api/v1/auth/otp/verifywith{ "email", "otp" }returns the JWT.- Send it as a bearer token on
/api/v1/admin/*:
curl https://<esim-api-host>/api/v1/admin/me \ -H "Authorization: Bearer <jwt>"Signing key is Worker secret AUTH_SECRET (HS256). Do not register a separate JWT_SECRET.
Allowed admin emails: ALLOWED_ADMIN_EMAILS. Super admins: SUPER_ADMIN_EMAILS.
| Surface | Roles |
|---|---|
| Admin JWT | super_admin | admin | viewer |
Tenant (users.role) | owner | admin | member | viewer |
ProtectedRoute and requireAdminRoles enforce the admin JWT roles. Unauthenticated or under-privileged sessions redirect to /login.
Legacy internal key
Section titled “Legacy internal key”X-Internal-Api-Key: ${INTERNAL_API_KEY} is still accepted on /admin/* for automation. Prefer JWT for the operator console.
Secrets
Section titled “Secrets”Values live in GitHub Organization / Repository Secrets and Worker secrets. See the repo root .env.example (key names only).