From 96e2ef8609e53dbbd7aee307bde4dba3064bf219 Mon Sep 17 00:00:00 2001 From: Krystie Date: Tue, 21 Jul 2026 23:52:33 -0700 Subject: [PATCH] DC-XXX: cookie-only session validation, kill IP-key cache mismatch isSessionValid previously checked verifyIPSession() first, falling back to verifySessionCookie() only if IP miss. Under Caddy --network host forward_auth, req.ip arrived as 100.121.150.22 (DNS2 tailnet) instead of the user's real IP, causing every cross-subdomain auto-login (plex/jellyfin/emby/chat) to 401 even with a valid cookie. Now cookie-only; the IP cache write-back is kept as a no-op for telemetry compat. Verification on DNS2: /dashcaddy-login renders in 184ms (was 7s). app-token/plex with the TOTP-issued cookie returns 200 with a real Plex token. --- dashcaddy-api/src/utilities/middleware.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dashcaddy-api/src/utilities/middleware.js b/dashcaddy-api/src/utilities/middleware.js index e7ec8b8..2f5b547 100644 --- a/dashcaddy-api/src/utilities/middleware.js +++ b/dashcaddy-api/src/utilities/middleware.js @@ -274,10 +274,22 @@ module.exports = function configureMiddleware(app, { ); } + // COOKIE-ONLY session validation. The previous IP-keyed cache (verifyIPSession + // + the write-back in this function) caused cross-subdomain SSO breakage when + // Caddy on --network host forwards auth to the container: req.ip arrives as + // 100.121.150.22 (DNS2's tailnet IP) instead of the user's real IP, so the + // IP cache misses even when the cookie is valid. The cookie is signed with a + // persisted HMAC key (loadOrCreateKey()), scoped to .sami via Domain attr, + // HttpOnly + Secure + SameSite=Lax — it's a stronger credential than the IP + // cache. Ref: skill auth-and-monitoring-pitfalls.md "TOTP session validation + // IP-key issue" (FIXED 2026-07-21). function isSessionValid(req) { - if (verifyIPSession(req)) return true; const cookies = parseCookies(req.headers.cookie); if (verifySessionCookie(cookies[SESSION_COOKIE_NAME])) { + // Re-warm the IP cache as a no-op-only fast path (kept for backwards + // compat with code that reads ctx.session.ipSessions.size for telemetry, + // but it is NOT consulted for auth decisions). The next line intentionally + // does NOT gate the return on verifyIPSession anymore. const ip = getClientIP(req); if (totpConfig.sessionDuration && SESSION_DURATIONS[totpConfig.sessionDuration]) { ipSessions.set(ip, { exp: Date.now() + SESSION_DURATIONS[totpConfig.sessionDuration] });