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).
31 lines
1.0 KiB
Plaintext
31 lines
1.0 KiB
Plaintext
# Clerk
|
|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
|
|
CLERK_SECRET_KEY=sk_test_...
|
|
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
|
|
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
|
|
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
|
|
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
|
|
|
|
# Database
|
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/monuments
|
|
POSTGRES_PASSWORD=postgres
|
|
|
|
# Contabo S3
|
|
S3_ENDPOINT=https://eu2.contabostorage.com
|
|
S3_REGION=eu-2
|
|
S3_ACCESS_KEY_ID=your-access-key
|
|
S3_SECRET_ACCESS_KEY=your-secret-key
|
|
S3_BUCKET_NAME=monuments-images
|
|
|
|
# App
|
|
NEXT_PUBLIC_APP_URL=https://testbed.mk
|
|
NEXT_PUBLIC_APP_DOMAIN=testbed.mk
|
|
|
|
# Admin
|
|
# 64+ random hex chars. Generate with: openssl rand -hex 32
|
|
ADMIN_SESSION_SECRET=your-random-64-char-secret-here-change-it-in-production
|
|
|
|
# Super-admin (stored as bcrypt hash, NOT plaintext). Generate with:
|
|
# node -e "import('bcryptjs').then(b => b.default.hash('YOUR_PASSWORD', 12).then(console.log))"
|
|
SUPER_ADMIN_USERNAME=super
|
|
SUPER_ADMIN_PASSWORD_HASH=$2a$12$REPLACE_WITH_BCRYPT_HASH_OF_YOUR_PASSWORD |