merge: cookie secure fix from release/1.0.0
All checks were successful
Build and Push Docker Image / build (push) Successful in 26s
CI / Security audit (push) Successful in 41s
CI / Tests & coverage (push) Successful in 52s

This commit is contained in:
2026-05-17 09:43:11 +01:00

View File

@@ -70,13 +70,15 @@ router.post('/login', loginLimiter, async (req, res) => {
// Set authentication cookie (signed when COOKIE_SECRET is set). // Set authentication cookie (signed when COOKIE_SECRET is set).
// rememberMe=true → persistent cookie, expires in 30 days // rememberMe=true → persistent cookie, expires in 30 days
// rememberMe=false → session cookie, expires when browser closes // rememberMe=false → session cookie, expires when browser closes
// secure is always true — the app should sit behind HTTPS in production; // secure:true only when TRUST_PROXY is set — i.e. a TLS-terminating reverse
// behind a reverse proxy set TRUST_PROXY=1 so req.secure works correctly. // proxy is in front. Without it the app may be accessed over plain HTTP and
// secure cookies would never be sent back by the browser.
const cookiePayload = JSON.stringify({ id: user.Id, name: user.Name, isAdmin }); const cookiePayload = JSON.stringify({ id: user.Id, name: user.Name, isAdmin });
const signed = !!process.env.COOKIE_SECRET; const signed = !!process.env.COOKIE_SECRET;
const secureCookie = !!process.env.TRUST_PROXY; // only send over HTTPS when behind a TLS proxy
const cookieOptions = { const cookieOptions = {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: secureCookie,
sameSite: 'strict', sameSite: 'strict',
signed, signed,
path: '/' path: '/'
@@ -91,7 +93,7 @@ router.post('/login', loginLimiter, async (req, res) => {
const csrfToken = crypto.randomBytes(32).toString('hex'); const csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('csrf_token', csrfToken, { res.cookie('csrf_token', csrfToken, {
httpOnly: false, // intentionally readable by JS for the double-submit pattern httpOnly: false, // intentionally readable by JS for the double-submit pattern
secure: process.env.NODE_ENV === 'production', secure: secureCookie,
sameSite: 'strict', sameSite: 'strict',
path: '/' path: '/'
}); });
@@ -142,7 +144,7 @@ router.get('/csrf', (req, res) => {
const csrfToken = crypto.randomBytes(32).toString('hex'); const csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('csrf_token', csrfToken, { res.cookie('csrf_token', csrfToken, {
httpOnly: false, httpOnly: false,
secure: process.env.NODE_ENV === 'production', secure: !!process.env.TRUST_PROXY,
sameSite: 'strict', sameSite: 'strict',
path: '/' path: '/'
}); });
@@ -168,14 +170,14 @@ router.post('/logout', async (req, res) => {
} }
res.clearCookie('emby_user', { res.clearCookie('emby_user', {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: !!process.env.TRUST_PROXY,
sameSite: 'strict', sameSite: 'strict',
signed: !!process.env.COOKIE_SECRET, signed: !!process.env.COOKIE_SECRET,
path: '/' path: '/'
}); });
res.clearCookie('csrf_token', { res.clearCookie('csrf_token', {
httpOnly: false, httpOnly: false,
secure: process.env.NODE_ENV === 'production', secure: !!process.env.TRUST_PROXY,
sameSite: 'strict', sameSite: 'strict',
path: '/' path: '/'
}); });