spomeni/scripts/start.sh
dimitar 3ff24cda0b feat(security): Phase 1 — harden auth, rate limiting, CSRF, upload validation
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).
2026-08-02 10:24:28 +02:00

18 lines
433 B
Bash

#!/bin/sh
export HOSTNAME=0.0.0.0
export PORT=3000
echo "=== SpomeniQR Starting ==="
echo "DATABASE_URL: ${DATABASE_URL:+set}"
echo "CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:+set}"
echo "S3_ENDPOINT: ${S3_ENDPOINT:+set}"
echo "Running Prisma migrations..."
if ! npx prisma migrate deploy; then
echo "ERROR: Prisma migrations failed. Aborting start."
exit 1
fi
echo "Starting Next.js server on 0.0.0.0:3000..."
exec node server.js