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