# Admin flow ## Roles - **SUPER_ADMIN**: provisioned from environment. Has full power — creates and deletes regular admins, generates codes, resets passwords. - **ADMIN**: created by a SUPER_ADMIN via the admin panel. Can only generate and delete their own codes. ## Authentication The admin layer uses a **custom HMAC-signed cookie**, separate from Clerk (which is for end-users only): - `POST /api/admin/login` — verifies credentials, sets an `admin_session` cookie signed with `ADMIN_SESSION_SECRET`. - `POST /api/admin/logout` — clears the cookie. - `src/lib/admin-session.ts` — sign/verify helpers, plus `requireAdmin` / `requireAdminPost` / `requireSuperAdminPost` guards that handle auth + same-origin (CSRF) checks for admin API routes. - All admin POST routes run through `requireAdminPost` (or `requireSuperAdminPost`), which enforces `Origin`/`Referer`/`Host` matching `NEXT_PUBLIC_APP_URL`. ## Super-admin provisioning Username and bcrypt-hashed password are read from env: - `SUPER_ADMIN_USERNAME` (default `super`) - `SUPER_ADMIN_PASSWORD_HASH` — bcrypt hash, NOT plaintext. Generate with: ```sh node -e "import('bcryptjs').then(b => b.default.hash('YOUR_PASSWORD', 12).then(console.log))" ``` Because `Code.createdById` is a non-nullable FK to `AdminUser`, the env super-admin also needs a row in `AdminUser`. Provisioning is handled by `prisma/seed.cjs`, which upserts the SUPER_ADMIN row from the same env values. Run `npm run db:seed` after starting the DB; the container's `scripts/start.sh` also runs it automatically on every boot (after `prisma migrate deploy`). ## Codes - A `Code` is a 12-char uppercase hex string generated by `POST /api/admin/codes`. - Users validate a code via `POST /api/validate-code`. The claim is atomic (uses `updateMany` with `usedByUserId: null` guard) so a code can't be claimed by two concurrent requests. - `Code.createdById` is a non-nullable FK to `AdminUser` with `onDelete: Restrict` (Prisma default). You cannot delete an admin who has issued codes — they audit-protect the code trail. ## Rate limiting + brute-force defence - `/api/admin/login`: 5 attempts/min per IP (`adminLoginLimiter`). - `/api/validate-code`: 20 attempts/min per IP (`validateCodeLimiter`). - Other admin mutating routes are also CSRF-guarded. - All limiters live in `src/lib/rate-limit.ts`.