From 3359dd204deaa553df37214e5c48fc83525d1c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Fri, 3 Jun 2022 11:50:58 +0200 Subject: [PATCH] feat: add option to disable 'Clear-Site-Data' header on logout (#1645) --- .../__snapshots__/create-config.test.ts.snap | 2 + src/lib/create-config.ts | 5 ++ src/lib/middleware/session-db.ts | 4 +- src/lib/routes/index.ts | 3 +- src/lib/routes/logout.test.ts | 53 +++++++++++++++++++ src/lib/routes/logout.ts | 14 ++++- src/lib/types/option.ts | 2 + 7 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 04308a0d82..839da9fc2d 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -89,6 +89,8 @@ Object { "unleashUrl": "http://localhost:4242", }, "session": Object { + "clearSiteDataOnLogout": true, + "cookieName": "unleash-session", "db": true, "ttlHours": 48, }, diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 10c8761720..890a0ed657 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -91,6 +91,11 @@ const defaultDbOptions: IDBOption = { const defaultSessionOption: ISessionOption = { ttlHours: parseEnvVarNumber(process.env.SESSION_TTL_HOURS, 48), + clearSiteDataOnLogout: parseEnvVarBoolean( + process.env.SESSION_CLEAR_SITE_DATA_ON_LOGOUT, + true, + ), + cookieName: 'unleash-session', db: true, }; diff --git a/src/lib/middleware/session-db.ts b/src/lib/middleware/session-db.ts index 01527006ff..91eb6aca54 100644 --- a/src/lib/middleware/session-db.ts +++ b/src/lib/middleware/session-db.ts @@ -10,7 +10,7 @@ function sessionDb( knex: Knex, ): RequestHandler { let store; - const { db } = config.session; + const { db, cookieName } = config.session; const age = hoursToMilliseconds(config.session.ttlHours) || hoursToMilliseconds(48); const KnexSessionStore = knexSessionStore(session); @@ -25,7 +25,7 @@ function sessionDb( store = new session.MemoryStore(); } return session({ - name: 'unleash-session', + name: cookieName, rolling: false, resave: false, saveUninitialized: false, diff --git a/src/lib/routes/index.ts b/src/lib/routes/index.ts index 76961ef315..fd8cd4d8c4 100644 --- a/src/lib/routes/index.ts +++ b/src/lib/routes/index.ts @@ -5,13 +5,12 @@ import SimplePasswordProvider from './auth/simple-password-provider'; import { IUnleashConfig } from '../types/option'; import { IUnleashServices } from '../types/services'; import { api } from './api-def'; +import LogoutController from './logout'; const AdminApi = require('./admin-api'); const ClientApi = require('./client-api'); const Controller = require('./controller'); const HealthCheckController = require('./health-check'); -const LogoutController = require('./logout'); - class IndexRouter extends Controller { constructor(config: IUnleashConfig, services: IUnleashServices) { super(config); diff --git a/src/lib/routes/logout.test.ts b/src/lib/routes/logout.test.ts index c31c662afd..b5b0587a41 100644 --- a/src/lib/routes/logout.test.ts +++ b/src/lib/routes/logout.test.ts @@ -44,6 +44,59 @@ test('should set "Clear-Site-Data" header', async () => { .expect('Clear-Site-Data', '"cookies", "storage"'); }); +test('should not set "Clear-Site-Data" header', async () => { + const baseUriPath = ''; + const app = express(); + const config = createTestConfig({ + server: { baseUriPath }, + session: { clearSiteDataOnLogout: false }, + }); + app.use('/logout', new LogoutController(config).router); + const request = supertest(app); + expect.assertions(1); + await request + .get(`${baseUriPath}/logout`) + .expect(302) + .expect((res) => + expect(res.headers['Clear-Site-Data']).toBeUndefined(), + ); +}); + +test('should clear "unleash-session" cookies', async () => { + const baseUriPath = ''; + const app = express(); + const config = createTestConfig({ server: { baseUriPath } }); + app.use('/logout', new LogoutController(config).router); + const request = supertest(app); + expect.assertions(0); + await request + .get(`${baseUriPath}/logout`) + .expect(302) + .expect( + 'Set-Cookie', + 'unleash-session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + ); +}); + +test('should clear "unleash-session" cookie even when disabled clear site data', async () => { + const baseUriPath = ''; + const app = express(); + const config = createTestConfig({ + server: { baseUriPath }, + session: { clearSiteDataOnLogout: false }, + }); + app.use('/logout', new LogoutController(config).router); + const request = supertest(app); + expect.assertions(0); + await request + .get(`${baseUriPath}/logout`) + .expect(302) + .expect( + 'Set-Cookie', + 'unleash-session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + ); +}); + test('should call destroy on session', async () => { const baseUriPath = ''; const fakeSession = { diff --git a/src/lib/routes/logout.ts b/src/lib/routes/logout.ts index a14db86b33..ba5b45bf89 100644 --- a/src/lib/routes/logout.ts +++ b/src/lib/routes/logout.ts @@ -4,11 +4,17 @@ import Controller from './controller'; import { IAuthRequest } from './unleash-types'; class LogoutController extends Controller { + private clearSiteDataOnLogout: boolean; + + private cookieName: string; + private baseUri: string; constructor(config: IUnleashConfig) { super(config); this.baseUri = config.server.baseUriPath; + this.clearSiteDataOnLogout = config.session.clearSiteDataOnLogout; + this.cookieName = config.session.cookieName; this.get('/', this.logout); } @@ -27,10 +33,14 @@ class LogoutController extends Controller { req.logout(); } - res.set('Clear-Site-Data', '"cookies", "storage"'); + res.clearCookie(this.cookieName); + + if (this.clearSiteDataOnLogout) { + res.set('Clear-Site-Data', '"cookies", "storage"'); + } + res.redirect(`${this.baseUri}/`); } } -module.exports = LogoutController; export default LogoutController; diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index 7f83f067f5..5c7a44d268 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -37,6 +37,8 @@ export interface IDBOption { export interface ISessionOption { ttlHours: number; db: boolean; + clearSiteDataOnLogout: boolean; + cookieName: string; } export interface IVersionOption {