From 9c354543fc8518841f624101a4c26fd05431d714 Mon Sep 17 00:00:00 2001 From: dimitar Date: Sun, 2 Aug 2026 15:15:50 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=207=20=E2=80=94=20reconcile=20dri?= =?UTF-8?q?ft,=20delete=20scratch,=20rewrite=20admin.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reconcile the docs with the shipped implementation. The exploration flagged three docs as out of sync; one was a scratch file. - docs/test.md deleted. File content was literally the four bytes 'aaaa' — a debugging leftover that had no business in the repo. - docs/admin.md rewritten end-to-end. The old file still described the removed hardcoded super/admin credentials ('SuperAdmin will log in with hardcoded username: super and password:admin'). The new version covers roles, the dual-auth model, the requireAdmin/requireAdminPost/requireSuperAdminPost guard flow, env-based super-admin provisioning (with the bcrypt-hash generation command), code generation + the atomic-claim guard, the Code.createdById onDelete:Restrict decision, and the rate-limit surface — i.e. all of the Phase 1–6 decisions in one place. It is now the authoritative source of truth for the admin flow. - docs/description.md (the 324-line original architecture plan) gets a banner at the top listing every known delta with the actual implementation: storage (S3 not UploadThing/Vercel Blob), upload MIME allow-list, dual auth (Clerk + custom HMAC), production deploy (Coolify/Traefik instead of Vercel), subdomain routing (Host header, not X-Subdomain), schema notes for the deliberate String? bornDate/passedDate, rate limiting + CSRF enforcement, and in-house QR rendering. The body of the document is preserved as historical design context. - docs/adminImplem.md (the 259-line original implementation plan) gets a similar banner — most notable is that the cookie path is actually '/' not '/admin', and the rolling helpers in admin-session.ts supersede the per-route inline guards the doc describes. Body preserved as history. No source changes — docs only. Note: docs/db.md is left untouched per the user's instruction to defer secret rotation to a later stage. It still contains an in-repo DB password; that and the other .env secrets will be addressed when rotation happens. --- docs/admin.md | 61 ++++++++++++++++++++++++++++++++++++++++++--- docs/adminImplem.md | 23 +++++++++++++++++ docs/description.md | 38 ++++++++++++++++++++++++++++ docs/test.md | 1 - 4 files changed, 118 insertions(+), 5 deletions(-) delete mode 100644 docs/test.md diff --git a/docs/admin.md b/docs/admin.md index 4f588a3..a7a8a98 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -1,6 +1,59 @@ -## SuperAdmin and admin flow +# Admin flow -we should have SuperAdmin acc. SuperAdmin can create admins, admins generate code which user use -after first login so he can create memories, without code user cant create memories. +## Roles -SuperAdmin will log in with hardcoded username: super and password:admin +- **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.ts`, which upserts the SUPER_ADMIN row from +the same env values. Run `npm run db:seed` after starting the DB +(or the container's `start.sh` will keep migrations up to date on +boot; seed is run manually or via CI as needed). + +## 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`. diff --git a/docs/adminImplem.md b/docs/adminImplem.md index 5aa5567..faef72d 100644 --- a/docs/adminImplem.md +++ b/docs/adminImplem.md @@ -1,5 +1,28 @@ # SuperAdmin & Admin + Code Access System — Implementation Plan +> ## ⚠ Historical implementation plan — see `docs/admin.md` for current state +> +> This was the original plan doc. Several specifics differ from the +> actual implementation (`src/lib/admin-session.ts`, +> `src/app/api/admin/*`, `src/app/admin/**`): +> +> - Cookie **path**: planned as `/admin`; shipped as `/` (so admin +> API routes under `/api/admin/*` receive the cookie). +> - **Super-admin credentials**: planned as hardcoded `super`/`admin`; +> shipped from env (`SUPER_ADMIN_USERNAME` + `SUPER_ADMIN_PASSWORD_HASH`, +> bcrypt) — see `docs/admin.md`. +> - **Super-admin row in `AdminUser`**: planned as optional seed; +> required by the `Code.createdById` FK and provisioned by +> `prisma/seed.ts`. +> - **Rate limiting, CSRF/Origin checks, password complexity, atomic +> code claim** (updateMany guard): all added in Phases 1–6 and not +> in the original plan. +> - All admin API routes now funnel through `requireAdmin` / +> `requireAdminPost` / `requireSuperAdminPost` in +> `src/lib/admin-session.ts`; the rolling-helpers in +> `admin-session.ts` supersede the inline `getAdminSession()` checks +> described per-route in this top-of-the-doc walkthrough. + ## Overview Implement SuperAdmin/admin role management and code-gated memorial creation as outlined in `admin.md`. diff --git a/docs/description.md b/docs/description.md index eee8c43..ff26220 100644 --- a/docs/description.md +++ b/docs/description.md @@ -1,5 +1,43 @@ # 🏛️ City Monuments Memories — Platform Architecture & Implementation Plan +> ## ⚠ Historical spec — partially out of sync with the implementation +> +> This document is the **original architecture plan**. The shipped code +> has drifted from it in several ways; treat this as historical design +> context, not current documentation. +> +> Known deltas (see `docs/admin.md` and the source for the source of +> truth): +> +> - **Storage**: described as "UploadThing" + "Vercel Blob"; the +> implementation uses **AWS-compatible S3 (Contabo Object Storage)** +> via `@aws-sdk/client-s3`. There is an in-house presigned-URL helper +> in `src/lib/upload.ts` (currently the upload route still proxies +> through the server with magic-byte validation; presigned direct-to-S3 +> flow is implemented but not yet wired into the client). +> - **File Upload**: 5MB cap, JPEG/PNG/WebP only (GIF was removed). +> - **Auth**: described as "Clerk only". The implementation uses +> **dual-track auth**: Clerk for end-users, plus a custom +> **HMAC-signed cookie** for admins (`src/lib/admin-session.ts`). +> Admin session secret comes from `ADMIN_SESSION_SECRET`; super-admin +> credentials from `SUPER_ADMIN_USERNAME` + `SUPER_ADMIN_PASSWORD_HASH` +> (bcrypt) — previously hardcoded `super`/`admin`, now removed. +> - **Deployment**: described as "Vercel". Actual deploy is **Docker +> (standalone Next.js) behind Traefik (Coolify)**. The `nginx/conf.d/*` +> configs have been deleted. +> - **Subdomain routing**: described as "Vercel Wildcard Domains"; +> middleware parses the `Host` header (not `X-Subdomain`) behind the +> Traefik reverse proxy. +> - **Schema**: see `prisma/schema.prisma` for the current shape. Most +> notably `bornDate`/`passedDate` are deliberately kept as bounded +> `VARCHAR(50)` (imprecise free-text like "1960" or "early 1990s"), +> not `DateTime`. +> - **Rate limiting + CSRF** are now enforced on admin + sensitive +> public routes (see `src/lib/rate-limit.ts`). +> - **QR codes** are rendered in-house via the `qrcode` package, not +> via the external `api.qrserver.com` service. + + ## 1. Tech Stack | Layer | Technology | Why | diff --git a/docs/test.md b/docs/test.md deleted file mode 100644 index 5d308e1..0000000 --- a/docs/test.md +++ /dev/null @@ -1 +0,0 @@ -aaaa