🍪 feat: Add Session Cookie Secure Override (#13189)

* fix: add session cookie secure override

* chore: remove empty whitespace
This commit is contained in:
Danny Avila 2026-05-19 09:44:14 -04:00 committed by GitHub
parent 9107000161
commit 909329a7e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 2 deletions

View file

@ -488,6 +488,10 @@ ALLOW_UNVERIFIED_EMAIL_LOGIN=true
SESSION_EXPIRY=1000 * 60 * 15
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
# Overrides the Secure attribute for session/auth cookies when set to true or false;
# leave unset to use the default NODE_ENV/DOMAIN_SERVER heuristic.
# Set to false only for HTTP-only deployments where browsers drop Secure cookies.
# SESSION_COOKIE_SECURE=false
JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef
JWT_REFRESH_SECRET=eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418

View file

@ -5,6 +5,7 @@ describe('shouldUseSecureCookie', () => {
beforeEach(() => {
process.env = { ...originalEnv };
delete process.env.SESSION_COOKIE_SECURE;
});
afterAll(() => {
@ -29,6 +30,34 @@ describe('shouldUseSecureCookie', () => {
expect(shouldUseSecureCookie()).toBe(false);
});
it('should return true when SESSION_COOKIE_SECURE=true', () => {
process.env.NODE_ENV = 'development';
process.env.DOMAIN_SERVER = 'http://localhost:3080';
process.env.SESSION_COOKIE_SECURE = 'true';
expect(shouldUseSecureCookie()).toBe(true);
});
it('should return false when SESSION_COOKIE_SECURE=false', () => {
process.env.NODE_ENV = 'production';
process.env.DOMAIN_SERVER = 'http://10.0.0.5:3080';
process.env.SESSION_COOKIE_SECURE = 'false';
expect(shouldUseSecureCookie()).toBe(false);
});
it('should trim and normalize SESSION_COOKIE_SECURE values', () => {
process.env.NODE_ENV = 'development';
process.env.DOMAIN_SERVER = 'http://localhost:3080';
process.env.SESSION_COOKIE_SECURE = ' TRUE ';
expect(shouldUseSecureCookie()).toBe(true);
});
it('should ignore invalid SESSION_COOKIE_SECURE values', () => {
process.env.NODE_ENV = 'production';
process.env.DOMAIN_SERVER = 'https://myapp.example.com';
process.env.SESSION_COOKIE_SECURE = 'yes';
expect(shouldUseSecureCookie()).toBe(true);
});
describe('localhost detection in production', () => {
beforeEach(() => {
process.env.NODE_ENV = 'production';

View file

@ -1,5 +1,6 @@
import crypto from 'crypto';
import type { Request, Response, NextFunction } from 'express';
import { isEnabled } from '~/utils/common';
export const OAUTH_CSRF_COOKIE = 'oauth_csrf';
export const OAUTH_CSRF_MAX_AGE = 10 * 60 * 1000;
@ -10,11 +11,17 @@ export const OAUTH_SESSION_COOKIE_PATH = '/api';
/**
* Determines if secure cookies should be used.
* Returns `true` in production unless the server is running on localhost (HTTP).
* This allows cookies to work on `http://localhost` during local development
* SESSION_COOKIE_SECURE=true/false explicitly overrides the environment heuristic.
* Returns `true` in production unless DOMAIN_SERVER uses a localhost-style hostname.
* This allows cookies to work on localhost during local development
* even when `NODE_ENV=production` (common in Docker Compose setups).
*/
export function shouldUseSecureCookie(): boolean {
const secureOverride = process.env.SESSION_COOKIE_SECURE?.trim().toLowerCase();
if (secureOverride === 'true' || secureOverride === 'false') {
return isEnabled(secureOverride);
}
const isProduction = process.env.NODE_ENV === 'production';
const domainServer = process.env.DOMAIN_SERVER || '';