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.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# 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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user