Files
parking_solution/deploy/nginx.conf
T
julian 64d5e45f11 Cookie-based auth/authz with CSRF; remove auth bypass
Replace the dev-only token shim with real authentication.

Backend:
- @fastify/cookie; JWT carried in an HttpOnly + SameSite=Strict cookie
  (parking_token), read from the cookie not the Authorization header.
- Double-submit CSRF: readable parking_csrf cookie + X-CSRF-Token header, both
  cross-checked against a csrf claim baked into the JWT; enforced on mutations.
- Routes: POST /api/auth/login (bcrypt, constant-time-ish), POST logout,
  GET me. requireRole now verifies the cookie + CSRF + role.
- seed-admin script (pnpm --filter @parking/server seed-admin) for the first
  admin; no bootstrap endpoint.
- Removed SETUP_AUTH_BYPASS and catalog.authBypass entirely; setup endpoints
  use the cookie admin guard like everything else.

Frontend:
- apiFetch wrapper: credentials:'include' + X-CSRF-Token on mutations.
- Login form; App gates on /api/auth/me and only shows setup to admins; logout.
- Wizard token field removed (auth is the session cookie).

Deploy:
- deploy/nginx.conf: prod reverse proxy, SPA + /api same-origin, TLS, so the
  Secure cookies work. Dev stays same-origin via the Vite proxy.

Verified (curl + browser): wrong pass -> 401; login sets cookies; me -> admin;
assign without CSRF -> 403, with -> 201; no cookie -> 401; session persists
across reload. wiki/local-jwt-auth updated.
2026-06-14 10:45:38 +02:00

62 lines
2.0 KiB
Nginx Configuration File

# nginx reverse proxy for the parking system (production).
#
# Serves the built SPA (apps/web/dist) and proxies the API to the Fastify
# backend on 127.0.0.1:3000. Same-origin: the SPA and API share one origin, so
# the HttpOnly auth cookie and SameSite=Strict work without CORS.
#
# TLS terminates here. The backend runs with NODE_ENV=production, which makes
# the auth cookies Secure (HTTPS-only) — so this server MUST be served over
# https in production. A minimal http->https redirect block is included.
#
# Install: copy to /etc/nginx/sites-available/parking, symlink into
# sites-enabled, set server_name + cert paths, then `nginx -t && systemctl reload nginx`.
upstream parking_backend {
server 127.0.0.1:3000;
keepalive 16;
}
# Redirect http -> https.
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name parking.local; # <-- set to your hostname
ssl_certificate /etc/ssl/parking/fullchain.pem; # <-- set
ssl_certificate_key /etc/ssl/parking/privkey.pem; # <-- set
ssl_protocols TLSv1.2 TLSv1.3;
# Built SPA assets.
root /opt/parking/apps/web/dist;
index index.html;
# API + health -> Fastify backend.
location ~ ^/(api|health) {
proxy_pass http://parking_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# Cookies pass through unchanged (same-origin) — do not rewrite.
}
# SPA fallback: every other path serves index.html (client-side routing).
location / {
try_files $uri $uri/ /index.html;
}
# Long-cache hashed assets.
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}