Commit Graph

6 Commits

Author SHA1 Message Date
ddd4327a99 perf: Phase 3 — parallel uploads + security headers, powered-by-header off
Performance and transport-layer hardening.

ImageUploader.tsx:
- Replaced sequential for-loop uploads with Promise.allSettled, so
  multiple files upload concurrently. Partial failures no longer abort
  the whole batch — successful uploads are kept, failed ones surface
  a concatenated error (and a subsequent retry is still possible).
- Order indices are pre-computed from the existing images.length so
  the parallel results stay correctly ordered.

next.config.ts:
- PoweredByHeader: false (no longer advertises Next.js).
- compress: true explicitly (default, but documented).
- Added Strict-Transport-Security, X-Frame-Options, X-Content-Type-
  Options, Referrer-Policy, Permissions-Policy and a defensive CSP
  (script-src allows 'unsafe-eval' for Next.js dev/HMR invariants,
  connect-src whitelists Clerk endpoints).
- remotePatterns is now only populated when S3_ENDPOINT is set, and
  parsed with URL() so a trailing path no longer produces a phantom
  hostname. Still effectively unused because the app uses raw <img>;
  the migration to next/image is deferred.
2026-08-02 12:21:23 +02:00
c0effe6ec9 refactor(quality): Phase 2 — split templates, consolidate constants, in-house QR
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.
2026-08-02 12:15:19 +02:00
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
5aeb5a0db2 translation done 2026-06-20 19:44:36 +02:00
ff93e8c5be template refinement 2026-06-20 19:28:05 +02:00
4fdb51f583 init 2026-06-20 18:17:30 +02:00