fix: cross-subdomain SSO auto-login for *arr services

- Set Domain=.sami on session + CSRF cookies so browsers send them to all subdomains
- This fixes Caddy forward_auth returning 401 for radarr/sonarr/prowlarr
- Fix login URL concatenation bug (radarr.samilogin -> radarr.sami/login)
- Fix getSetCookie() missing from _httpsFetch/_httpFetch response objects
- Fix array/string handling for set-cookie header in session-handlers fallback
- Refactor csrf-protection to createCSRFMiddleware() factory with cookieDomain support
- Pass renewCSRFToken through middleware deps chain to TOTP route
This commit is contained in:
Coderbot
2026-05-23 16:15:56 -07:00
parent fe0f52ce17
commit 445da9f5fc
7 changed files with 101 additions and 55 deletions
+11 -5
View File
@@ -13,7 +13,7 @@ const helmet = require('helmet');
const compression = require('compression');
const crypto = require('crypto');
const rateLimit = require('express-rate-limit');
const { csrfCookieMiddleware, csrfValidationMiddleware, CSRF_HEADER_NAME } = require('./csrf-protection');
const { createCSRFMiddleware, csrfValidationMiddleware, CSRF_HEADER_NAME } = require('./csrf-protection');
const { RATE_LIMITS, LIMITS, APP } = require('./constants');
const { CACHE_CONFIGS, createCache } = require('./cache-config');
@@ -75,7 +75,10 @@ module.exports = function configureMiddleware(app, {
// ── Compress responses (gzip/brotli) ──
app.use(compression());
// ── CSRF Protection ──
// ── CSRF protection (cookie domain set to TLD for cross-subdomain SSO) ──
const { csrfCookieMiddleware, renewCSRFToken } = createCSRFMiddleware({
cookieDomain: siteConfig.tld || undefined
});
app.use(csrfCookieMiddleware);
app.use(csrfValidationMiddleware);
@@ -221,8 +224,9 @@ module.exports = function configureMiddleware(app, {
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
const key = cryptoUtils.loadOrCreateKey();
const sig = crypto.createHmac('sha256', key).update(payloadB64).digest('base64url');
const domainAttr = siteConfig.tld ? `; Domain=${siteConfig.tld}` : '';
res.setHeader('Set-Cookie',
`${SESSION_COOKIE_NAME}=${payloadB64}.${sig}; Max-Age=${maxAge}; Path=/; HttpOnly; Secure; SameSite=Lax`
`${SESSION_COOKIE_NAME}=${payloadB64}.${sig}${domainAttr}; Max-Age=${maxAge}; Path=/; HttpOnly; Secure; SameSite=Lax`
);
}
@@ -253,8 +257,9 @@ module.exports = function configureMiddleware(app, {
}
function clearSessionCookie(res) {
const domainAttr = siteConfig.tld ? `; Domain=${siteConfig.tld}` : '';
res.setHeader('Set-Cookie',
`${SESSION_COOKIE_NAME}=; Max-Age=0; Path=/; HttpOnly; SameSite=Lax`
`${SESSION_COOKIE_NAME}=; Max-Age=0${domainAttr}; Path=/; HttpOnly; SameSite=Lax`
);
}
@@ -428,6 +433,7 @@ module.exports = function configureMiddleware(app, {
clearIPSession,
clearSessionCookie,
isSessionValid,
ipSessions
ipSessions,
renewCSRFToken
};
};