Skip to content

Admin

Active production application

Admin is deployed by both the automatic production pipeline and the manual Cloudflare recovery workflow. It is intentionally isolated from the shared browser-app packages.

Purpose

Internal admin app - the only app whose entire path space runs through its own Worker (run_worker_first: true), not just specific routes.

Source directory

apps/admin-web/ — src/ (React frontend plus RBAC/MFA progressive-enhancement modules), worker/ (authentication, MFA, RBAC and Admin business APIs).

Framework

React 18 + TypeScript + Vite. Does not depend on @akshaya/ui or @akshaya/shared - the one app in this monorepo that's deliberately isolated from the shared packages.

Commands

npm run dev:admin
npm run build:admin     # tsc -b -> vite build -> tsc -p worker/tsconfig.json

Output

apps/admin-web/dist/.

Deployment mechanism

Cloudflare Worker, name akshaya-admin, run_worker_first: true for every path. wrangler deploy --config apps/admin-web/wrangler.jsonc. Domain: admin.akshayagroupglobal.com.

worker/entry.ts is the outer Admin security boundary. It wraps the existing password/auth Worker with optional TOTP MFA and RBAC before delegating allowed requests to worker/index.ts.

Environment variables / secrets

No VITE_* build-time variables.

First-Admin bootstrap secret:

npm run configure:admin:bootstrap

MFA encryption secret:

npm run configure:admin:mfa-key

ADMIN_MFA_ENCRYPTION_KEY must be a strong value of at least 32 characters and is available only to the Admin Worker. It encrypts TOTP authenticator secrets with AES-GCM before they are written to D1. It is never sent to browser code or committed to the repository.

RBAC itself requires no separate secret.

admin.akshayagroupglobal.com

Authentication

Admin identity requires:

  • an Active shared users row with role='Admin'
  • PBKDF2 password verification
  • failed-login lockout
  • a dedicated host-only HttpOnly Admin session cookie
  • four-hour Admin session lifetime
  • TOTP/recovery-code verification after password authentication when that Admin has enabled MFA

The one-time first-Admin bootstrap route is /api/auth/bootstrap, gated by ADMIN_BOOTSTRAP_TOKEN and only available while no Admin exists.

Multi-factor authentication

Migration 0014_admin_mfa.sql adds:

  • admin_mfa — encrypted TOTP secret, enabled state, confirmation time and replay-prevention step
  • admin_mfa_recovery_codes — one-time recovery codes stored only as SHA-256 hashes
  • admin_mfa_challenges — five-minute opaque login challenges after a successful password factor

MFA is opt-in per Admin in this phase, so deployment cannot lock out existing administrators. Once an Admin enables it, password-only login no longer creates a usable Admin session. The core password login first performs the existing password/status/lockout checks; its temporary session is immediately discarded, and the outer Worker issues a five-minute HttpOnly MFA challenge instead. Only a valid TOTP or unused recovery code creates the real four-hour Admin session.

TOTP behavior:

  • RFC 6238-compatible TOTP
  • HMAC-SHA1, 6 digits, 30-second period
  • accepts one adjacent time step for normal clock skew
  • stores the last accepted TOTP step to reject code replay
  • maximum five MFA attempts per challenge

Enrollment requires the Admin's current password. Setup returns the authenticator secret and eight one-time recovery codes once; the browser never persists either value. The authenticator secret is encrypted with AES-GCM before D1 storage. Recovery codes are normalized and stored only as hashes. No third-party QR-code service is used because sending the TOTP secret to such a service would expose the second-factor credential.

MFA endpoints:

POST /api/auth/mfa          # second factor during sign-in
GET  /api/mfa/status
POST /api/mfa/setup         # current password required
POST /api/mfa/confirm       # 6-digit TOTP required
POST /api/mfa/disable       # current password + TOTP/recovery code; revokes all sessions

Recovery codes can be used for sign-in if the authenticator device is unavailable, including when the Worker encryption key is temporarily unavailable. Each recovery code is consumed atomically and can be used only once.

Role-based access control

Migration 0013_admin_rbac.sql adds admin_access. Existing Admin accounts are backfilled as SuperAdmin so the production upgrade does not unexpectedly lock out current administrators. Future Admin identities do not receive implicit business permissions merely from role='Admin'.

Access level Allowed workspace areas
SuperAdmin Every Admin area plus Admin Access management
Support Dashboard, Customers / Enquiries, private enquiry reference files
Marketplace Dashboard, Freelancers, Customer Requirements
Commerce Dashboard, Orders, Commerce Categories / Products / Images
Operations Dashboard, Orders, Shipments

The browser hides navigation that is not part of the assigned capability set, but the Worker is the authoritative boundary. Unknown/new protected API routes default to SuperAdmin-only.

SuperAdmin management endpoints:

GET   /api/rbac/me
GET   /api/admin-access
PATCH /api/admin-access/:userId

The last remaining SuperAdmin cannot be demoted. Access changes are written to auth_audit and take effect on the next protected API request.

There is still no public Admin registration flow.

Troubleshooting

  • MFA setup says encryption is not configured: set ADMIN_MFA_ENCRYPTION_KEY with npm run configure:admin:mfa-key.
  • Authenticator is unavailable: use one unused recovery code. Codes are single-use.
  • A TOTP code says it was already used: wait for the next authenticator code; replay protection intentionally rejects the same time-step code twice.
  • MFA challenge expired: repeat normal email/password sign-in; challenges last five minutes.
  • Signed in but a section is hidden / returns 403: check /api/rbac/me and admin_access.access_level.
  • SuperAdmin cannot demote an account: the final SuperAdmin is intentionally protected from demotion.
  • Cannot create the first Admin account: confirm ADMIN_BOOTSTRAP_TOKEN is configured.
  • Worker/static path problems: run_worker_first: true means every Admin request passes through the Worker; use wrangler tail --config apps/admin-web/wrangler.jsonc.