Code quality pass driven by the plan. Eliminates the duplicated
constants, breaks the 216-line templates file into per-template files,
and removes the third-party QR dependency that was leaking memorial
URLs to api.qrserver.com.
Templates (src/lib/templates.tsx → src/lib/templates/):
- Split into Elegance.tsx, Cinematic.tsx, Serene.tsx and a shared.tsx
holding formatDates() and MemorialFooter.
- New index.tsx re-exports everything plus renderTemplate(), so the
existing '@@/lib/templates' import paths are unchanged.
- Adds eslint-disable-next-line @next/next/no-img-element markers on
the raw <img> tags so the linter (once it works again) won't flag
them; full migration to next/image is deferred to a later phase
pending next.config remotePatterns verification.
QR consolidation:
- dashboard/page.tsx now uses lib/qrcode.ts::generateMonumentQR
(server-rendered async) instead of api.qrserver.com. The external
service was logging every memorial URL to a third party.
- lib/qrcode.ts reads APP_DOMAIN from the shared config (was
process.env.NEXT_PUBLIC_APP_DOMAIN inline). Added
generateMonumentQRPng() helper for the download path.
- Dashboard 'Превземи QR код' link now points at the data: URL in-app
and downloads as '{subdomain}-qr.png'.
Config consolidation:
- SubdomainPicker.tsx now imports APP_DOMAIN, SUBDOMAIN_MIN_LENGTH
and SUBDOMAIN_MAX_LENGTH from lib/config; previously the slug was
normalized with a regex that didn't match the server's stricter
/^[a-z0-9][a-z0-9-]*[a-z0-9]$/ rule and the success message
hardcoded '.testbed.mk'.
- ImageUploader.tsx re-imports MAX_FILE_SIZE / ALLOWED_TYPES /
MAX_FILES from lib/upload (they were literally redefined inline,
causing drift risk). The remaining client-side filter uses a
string-cast to satisfy the readonly-tuple type.
Security hardening covering credentials, brute-force protection, CSRF,
TOCTOU races, upload validation, and migration failure handling.
Secret rotation is deferred; the existing secrets in .env will be
rotated in a later phase. This phase reduces the attack surface and
removes the most exploitable issues.
Removed:
- Hardcoded 'super'/'admin' super-admin credentials in
src/app/api/admin/login/route.ts. Username/hash are now loaded from
env (SUPER_ADMIN_USERNAME / SUPER_ADMIN_PASSWORD_HASH) and verified
via bcrypt like regular admins.
Added:
- src/lib/config.ts: single source of truth for APP_DOMAIN,
APP_URL, subdomain regex/lengths, validation bounds, admin password
policy, image-key allow-list regex, and super-admin env credentials.
- src/lib/rate-limit.ts: in-memory LRU (via lru-cache) rate limiters —
admin login (5/min), validate-code (20/min), check-subdomain
(60/min), upload (10/min) — keyed by client IP, returning 429 with
X-RateLimit-* headers.
- requireAdmin / requireAdminPost / requireSuperAdminPost guards in
src/lib/admin-session.ts. All admin mutating routes now enforce
same-origin (Origin/Referer/Host check against NEXT_PUBLIC_APP_URL)
before running — CSRF protection for the custom admin auth layer.
- Logout now imports COOKIE_NAME_ADMIN instead of hardcoding the string.
- Server-side magic-byte detection for image uploads (no new dep) —
rejects spoofed Content-Type. GIF removed from allowed types.
- /api/publish is now transactional (prisma.) with
explicit P2002 → 409 handling for subdomain collisions.
- /api/image validates the key against an allow-list regex and returns
Macedonian error messages (was the only English-localized file).
- /api/validate-code enforces a 12-hex-char pattern and uses
updateMany with usedByUserId=null guard to make the claim atomic.
- /api/check-subdomain validates the slug against the shared regex
before hitting the DB and returns a short private Cache-Control.
- Admin password minimum length bumped from 6 to 12 with letter+digit
complexity requirement across change-password, users POST and
users/[id] PUT.
Changed:
- scripts/start.sh: prisma migrate deploy failures now exit non-zero
instead of silently continuing (prevents schema drift in prod).
- .env.example: documents SUPER_ADMIN_USERNAME /
SUPER_ADMIN_PASSWORD_HASH with example bcrypt-hash generation.
- package.json: lru-cache added as direct dependency (already present
transitively, promoted to explicit).
Cookie path was /admin, but admin API routes live under /api/admin/.
Browser only sends cookies to paths matching the cookie's path, so
all API calls were unauthenticated. Changed path to / for both
cookie creation (login) and deletion (logout).
The login API was blocked by the admin session middleware, preventing
the creation of the session cookie. Now the API endpoint is exempted
alongside the login page.
- Move login page into (auth) route group — no layout wrapper
- Move dashboard/users/codes + layout into (panel) route group —
session check and sidebar only apply to these
- URL paths remain unchanged (/admin/login, /admin/dashboard, etc.)
- Rewrite admin-session.ts to use Web Crypto API (crypto.subtle) instead
of Node.js crypto module, ensuring compatibility with Edge Runtime
in middleware
- Add ADMIN_SESSION_SECRET to .env.example
- Build passes with zero warnings
- Add /api/validate-code endpoint: validates code, marks it as used by
the current Clerk user, prevents reuse
- Add 'Код' step to onboarding wizard (step 0): user must enter and
validate a code before proceeding to fill memorial details
- Protect /api/publish: reject with 403 if user has not consumed a valid
code
- Code input auto-capitalizes on the onboarding page
- Add admin layout with sidebar navigation and session guard
- Create AdminSidebar client component with role-based nav links
- Add dashboard page showing stats (admin count, code counts)
- Add users management page (SuperAdmin only): list, create, delete,
and reset passwords for admin users
- Add codes management page: list all codes, generate new codes,
delete unused codes
- Add API routes for admin user CRUD (GET, POST, DELETE, PUT)
- Add API routes for code management (GET, POST, DELETE)
- All UI in Macedonian
- Create admin-session lib with sign/verify helpers using HMAC-SHA256
- Add admin login API that checks hardcoded super/admin credentials
and DB-stored admin users with bcrypt password comparison
- Add admin logout API to clear session cookie
- Add change-password API for admin self-service password changes
- Create admin login page with Macedonian UI
- Update middleware to protect /admin/* and /api/admin/* routes
with admin session check, bypassing Clerk auth