spomeniV2/docs/admin.md
Dimitar765 d6b29b8e9c
Some checks failed
CI / build (push) Has been cancelled
v2 initial commit
2026-08-03 20:33:49 +02:00

2.3 KiB

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:
    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.