docs: Phase 7 — reconcile drift, delete scratch, rewrite admin.md
Some checks are pending
CI / build (push) Waiting to run
Some checks are pending
CI / build (push) Waiting to run
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.
This commit is contained in:
parent
d5e7a9a0b5
commit
9c354543fc
@ -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
|
## Roles
|
||||||
after first login so he can create memories, without code user cant create memories.
|
|
||||||
|
|
||||||
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`.
|
||||||
|
|||||||
@ -1,5 +1,28 @@
|
|||||||
# SuperAdmin & Admin + Code Access System — Implementation Plan
|
# 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
|
## Overview
|
||||||
|
|
||||||
Implement SuperAdmin/admin role management and code-gated memorial creation as outlined in `admin.md`.
|
Implement SuperAdmin/admin role management and code-gated memorial creation as outlined in `admin.md`.
|
||||||
|
|||||||
@ -1,5 +1,43 @@
|
|||||||
# 🏛️ City Monuments Memories — Platform Architecture & Implementation Plan
|
# 🏛️ 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
|
## 1. Tech Stack
|
||||||
|
|
||||||
| Layer | Technology | Why |
|
| Layer | Technology | Why |
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
aaaa
|
|
||||||
Loading…
Reference in New Issue
Block a user