From 9909f67798d822ed5e98150c0516dfb754096fe2 Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Sat, 18 May 2024 10:44:54 -0700 Subject: [PATCH] allow config of session cookie sameSite value --- lib/authenticator.js | 18 ++++++++++++------ lib/session-manager.js | 7 +++++-- test/lib/authenticator.js | 5 +++++ test/lib/session-manager.js | 2 ++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/authenticator.js b/lib/authenticator.js index 3df0b9d..86b95d6 100644 --- a/lib/authenticator.js +++ b/lib/authenticator.js @@ -53,15 +53,16 @@ class Authenticator { * @param {object} options options * @param {string | string[]} options.encryptionSecret encryption secret * @param {object} options.authenticator authenticator options - * @param {boolean} options.authenticator.secureAuthOnly disable auth over non-https - * @param {string[]} options.authenticator.forbiddenPAMIdentifiers reject these identifiers for PAM auth - * @param {string[]} options.authenticator.authnEnabled in order of preference for storing new credentials + * @param {boolean=} options.authenticator.secureAuthOnly disable auth over non-https + * @param {string=} options.authenticator.sessionCookieSameSite sameSite setting for session cookie, default Lax + * @param {string[]=} options.authenticator.forbiddenPAMIdentifiers reject these identifiers for PAM auth + * @param {string[]=} options.authenticator.authnEnabled in order of preference for storing new credentials * @param {number=} options.authenticator.inactiveSessionLifespanSeconds session timeout * @param {string[]=} options.authenticator.loginBlurb text for login page * @param {string[]=} options.authenticator.indieAuthBlurb text for indieauth login section * @param {string[]=} options.authenticator.userBlurb text for local user login section * @param {string[]=} options.authenticator.otpBlurb text for otp entry - * @param {string=} options.dingus dingus options + * @param {object=} options.dingus dingus options * @param {string=} options.dingus.proxyPrefix base url prefix */ constructor(logger, db, options) { @@ -70,8 +71,13 @@ class Authenticator { this.options = options; this.basicRealm = options.authenticator.basicRealm || packageName; this.secureAuthOnly = options.authenticator.secureAuthOnly ?? true; + this.sameSite = options.authenticator.sessionCookieSameSite || 'Lax'; this.proxyPrefix = options.dingus?.proxyPrefix ?? ''; + if (!['None', 'Lax', 'Strict'].includes(this.sameSite)) { + throw new RangeError(`invalid sameSite value "${this.sameSite}"`); + } + // First construct map of all available code-supported auth mechanisms. this.authn = { indieAuth: {}, @@ -476,7 +482,7 @@ class Authenticator { common.addCookie(res, Enum.SessionCookie, ctx.cookie[Enum.SessionCookie], { httpOnly: true, maxAge: this.cookieLifespan, - sameSite: 'Lax', + sameSite: this.sameSite, path: `${this.proxyPrefix}/`, secure: this.secureAuthOnly, }); @@ -488,7 +494,7 @@ class Authenticator { common.addCookie(res, Enum.SessionCookie, '""', { httpOnly: true, maxAge: 0, - sameSite: 'Lax', + sameSite: this.sameSite, path: `${this.proxyPrefix}/`, secure: this.secureAuthOnly, }); diff --git a/lib/session-manager.js b/lib/session-manager.js index d399e5b..0b4ce4e 100644 --- a/lib/session-manager.js +++ b/lib/session-manager.js @@ -35,6 +35,7 @@ class SessionManager { * @param {string[]} options.authenticator.authnEnabled authentication methods enabled * @param {number=} options.authenticator.inactiveSessionLifespanSeconds session timeout * @param {boolean} options.authenticator.secureAuthOnly allow only https + * @param {string=} options.authenticator.sessionCookieSameSite sameSite setting for session cookie, default Lax * @param {object=} options.dingus dingus options * @param {string=} options.dingus.proxyPrefix prefix on route paths * @param {string} options.dingus.selfBaseUrl base url @@ -47,6 +48,8 @@ class SessionManager { this.db = authenticator.db; // TODO: take db arg in next major version bump this.options = options; this.proxyPrefix = options.dingus?.proxyPrefix ?? ''; + this.secureAuthOnly = options.authenticator.secureAuthOnly ?? true; + this.sameSite = options.authenticator.sessionCookieSameSite || 'Lax'; this.indieAuthCommunication = new IndieAuthCommunication(logger, options); this.mysteryBox = new MysteryBox(options); this.mysteryBox.on('statistics', common.mysteryBoxLogger(logger, _fileScope(this.constructor.name))); @@ -67,8 +70,8 @@ class SessionManager { const secureSession = session && await this.mysteryBox.pack(session) || '""'; common.addCookie(res, cookieName, secureSession, { httpOnly: true, - sameSite: 'Lax', - secure: this.options.authenticator.secureAuthOnly, + sameSite: this.sameSite, + secure: this.secureAuthOnly, maxAge: session && maxAge || 0, path, }); diff --git a/test/lib/authenticator.js b/test/lib/authenticator.js index 75ae9a8..4768e0d 100644 --- a/test/lib/authenticator.js +++ b/test/lib/authenticator.js @@ -53,6 +53,11 @@ describe('Authenticator', function () { authenticator = new Authenticator(stubLogger, stubDb, options); }); + it('covers invalid sameSite', function () { + options.authenticator.sessionCookieSameSite = 'Sometimes'; + assert.throws(() => new Authenticator(stubLogger, stubDb, options), RangeError); + }); + describe('createIdentifier', function () { let dbCtx; beforeEach(function () { diff --git a/test/lib/session-manager.js b/test/lib/session-manager.js index 16243d7..40322c1 100644 --- a/test/lib/session-manager.js +++ b/test/lib/session-manager.js @@ -50,6 +50,8 @@ describe('SessionManager', function () { describe('constructor', function () { it('covers options', function () { delete options.dingus.proxyPrefix; + delete options.authenticator.secureAuthOnly; + options.authenticator.sessionCookieSameSite = 'None'; manager = new SessionManager(stubLogger, stubAuthenticator, options); }); }); // constructor -- 2.45.2