From 2b029a26f0127927f46d1d565f17fea2c1dad40a Mon Sep 17 00:00:00 2001 From: elpatron Date: Tue, 2 Jun 2026 22:48:15 +0200 Subject: [PATCH] Fix passkey login 429 by forwarding client IPs correctly. Forward X-Forwarded-For through frontend nginx, use TRUST_PROXY=1 for the Docker hop, and limit auth rate limiting to login flows only. Co-authored-by: Cursor --- .env.example | 4 ++-- client/nginx.conf | 3 +++ docs/deployment/npm-security.md | 5 +++-- scripts/server-patch-env-sprint1.sh | 4 ++-- server/src/app.ts | 19 ++++++++++++++++++- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index bc0fd3a..281d216 100755 --- a/.env.example +++ b/.env.example @@ -13,8 +13,8 @@ RP_ID=localhost # Must match the frontend URL exactly (Vite dev: http://localhost:5173; Docker: http://localhost) ORIGIN=http://localhost:5173 -# Behind Nginx Proxy Manager — see docs/deployment/npm-security.md -# TRUST_PROXY=172.16.10.10 +# Behind reverse proxy — see docs/deployment/npm-security.md +# Docker Compose (NPM → frontend nginx → backend): TRUST_PROXY=1 # TRUST_PROXY=1 # Docker Compose database (required for production deploy) diff --git a/client/nginx.conf b/client/nginx.conf index 538ba87..ac4b841 100644 --- a/client/nginx.conf +++ b/client/nginx.conf @@ -43,6 +43,9 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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_cache_bypass $http_upgrade; } } diff --git a/docs/deployment/npm-security.md b/docs/deployment/npm-security.md index 9857d92..6a1b4ed 100644 --- a/docs/deployment/npm-security.md +++ b/docs/deployment/npm-security.md @@ -30,8 +30,9 @@ proxy_set_header X-Real-IP $remote_addr; ORIGIN=https://kapteins-daagbok.eu RP_ID=kapteins-daagbok.eu SESSION_SECRET= -TRUST_PROXY=172.16.10.10 -# oder TRUST_PROXY=1 für genau einen Proxy-Hop +# Docker Compose: Frontend-Nginx ist der direkte Proxy zum Backend → 1 Hop +TRUST_PROXY=1 +# Nur bei direktem Backend-Zugriff ohne Frontend-Nginx: NPM-IP, z. B. TRUST_PROXY=172.16.10.10 ``` `ORIGIN` muss **exakt** der Browser-URL entsprechen (ohne trailing slash). diff --git a/scripts/server-patch-env-sprint1.sh b/scripts/server-patch-env-sprint1.sh index ff34c53..5644a2d 100755 --- a/scripts/server-patch-env-sprint1.sh +++ b/scripts/server-patch-env-sprint1.sh @@ -34,7 +34,7 @@ if ! grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" || grep -q "^POSTGRES_PASSWORD=$" else echo " keep POSTGRES_PASSWORD (already set)" fi -# NPM on 172.16.10.10 → app on this host -ensure_var TRUST_PROXY "172.16.10.10" +# Frontend-Nginx → Backend (one hop); NPM is in front of Nginx, not Backend directly +ensure_var TRUST_PROXY "1" echo "Done. Verify with: docker exec daagbox-prod-db psql -U postgres -d daagbox -c 'SELECT 1'" diff --git a/server/src/app.ts b/server/src/app.ts index b06dd3b..1077715 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -45,6 +45,18 @@ export function createApp(): express.Express { app.use(cookieParser()) app.use(express.json({ limit: '50mb' })) + /** Passkey login/register only — not person-pool, profile, etc. */ + const authFlowPaths = new Set([ + '/register-options', + '/register-verify', + '/login-options', + '/login-verify', + '/reauth-options', + '/reauth-verify', + '/logout', + '/session' + ]) + const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 60, @@ -66,7 +78,12 @@ export function createApp(): express.Express { legacyHeaders: false }) - app.use('/api/auth', authLimiter) + app.use('/api/auth', (req, res, next) => { + if (authFlowPaths.has(req.path)) { + return authLimiter(req, res, next) + } + return next() + }) app.use('/api/collaboration/invite-details', publicCollaborationLimiter) app.use('/api/collaboration/share-pull', publicCollaborationLimiter) app.use('/api', apiLimiter)