spomeni/docs/admin.md
dimitar 8c6390d6ac
Some checks failed
CI / build (push) Has been cancelled
super admin prod fix
2026-08-03 18:10:19 +02:00

60 lines
2.3 KiB
Markdown

# 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`.