From d017ec7cdce5926158e64b2841ebbb7792127a65 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Thu, 18 Feb 2021 09:03:21 +0100 Subject: [PATCH] feat: add db-session store (#722) * Moves to db managed sessions. --- CHANGELOG.md | 4 ++ package.json | 2 + src/lib/app.js | 4 +- src/lib/middleware/session-db.js | 36 +++++++++++++ src/lib/middleware/session.js | 23 -------- src/lib/options.js | 5 +- src/lib/routes/logout.js | 2 +- src/lib/server-impl.js | 1 - .../20210212114759-add-session-table.js | 24 +++++++++ src/server-dev.ts | 3 ++ src/test/e2e/helpers/test-helper.js | 5 +- yarn.lock | 54 ++++++++++++++++++- 12 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 src/lib/middleware/session-db.js delete mode 100644 src/lib/middleware/session.js create mode 100644 src/migrations/20210212114759-add-session-table.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bce3dd48b..3033b790e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.x.x + +- feat: Use express-session backed by postgres + ## 3.12.0 - feat: setup typescript diff --git a/package.json b/package.json index 9984878f46..68c9351625 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "async": "^3.1.0", "basic-auth": "^2.0.1", "compression": "^1.7.3", + "connect-session-knex": "^2.0.0", "cookie-parser": "^1.4.4", "cookie-session": "^2.0.0-rc.1", "db-migrate": "0.11.11", @@ -75,6 +76,7 @@ "deepmerge": "^4.2.2", "errorhandler": "^1.5.1", "express": "^4.17.1", + "express-session": "^1.17.1", "gravatar-url": "^3.1.0", "helmet": "^4.1.0", "joi": "^17.3.0", diff --git a/src/lib/app.js b/src/lib/app.js index c8d145f970..335db93ad0 100644 --- a/src/lib/app.js +++ b/src/lib/app.js @@ -8,7 +8,7 @@ const cookieParser = require('cookie-parser'); const path = require('path'); const errorHandler = require('errorhandler'); const IndexRouter = require('./routes'); -const unleashSession = require('./middleware/session'); +const unleashDbSession = require('./middleware/session-db'); const responseTime = require('./middleware/response-time'); const requestLogger = require('./middleware/request-logger'); const simpleAuthentication = require('./middleware/simple-authentication'); @@ -32,7 +32,7 @@ module.exports = function(config, services = {}) { app.use(compression()); app.use(cookieParser()); app.use(express.json({ strict: false })); - app.use(unleashSession(config)); + app.use(unleashDbSession(config)); app.use(responseTime(config)); app.use(requestLogger(config)); app.use(secureHeaders(config)); diff --git a/src/lib/middleware/session-db.js b/src/lib/middleware/session-db.js new file mode 100644 index 0000000000..929fea4a20 --- /dev/null +++ b/src/lib/middleware/session-db.js @@ -0,0 +1,36 @@ +const session = require('express-session'); +const KnexSessionStore = require('connect-session-knex')(session); + +const TWO_DAYS = 48 * 60 * 60 * 1000; +module.exports = function(config) { + let store; + let db; + let age; + if (config.session) { + age = config.session.age || TWO_DAYS; + db = config.session.db || false; + } + if (db) { + store = new KnexSessionStore({ + knex: config.stores.db, + tablename: 'unleash_session', + createtable: false, + }); + } else { + store = new session.MemoryStore(); + } + const sessionMiddleware = session({ + name: 'unleash-session', + rolling: false, + resave: false, + saveUninitialized: false, + store, + secret: [config.secret], + cookie: { + path: config.baseUriPath === '' ? '/' : config.baseUriPath, + secure: !!config.secureHeaders, + maxAge: age, + }, + }); + return (req, res, next) => sessionMiddleware(req, res, next); +}; diff --git a/src/lib/middleware/session.js b/src/lib/middleware/session.js deleted file mode 100644 index 5bcbf574c6..0000000000 --- a/src/lib/middleware/session.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const cookieSession = require('cookie-session'); - -module.exports = function(config) { - const sessionMiddleware = cookieSession({ - name: 'unleash-session', - keys: [config.secret], - maxAge: config.sessionAge, - secure: !!config.secureHeaders, - path: config.baseUriPath === '' ? '/' : config.baseUriPath, - }); - - const extendTTL = (req, res, next) => { - // Updates active sessions every hour - req.session.nowInHours = Math.floor(Date.now() / 3600e3); - next(); - }; - - return (req, res, next) => { - sessionMiddleware(req, res, () => extendTTL(req, res, next)); - }; -}; diff --git a/src/lib/options.js b/src/lib/options.js index d6ecf711ce..7b7af79319 100644 --- a/src/lib/options.js +++ b/src/lib/options.js @@ -56,6 +56,10 @@ function defaultOptions() { ), }, }, + session: { + db: process.env.DB_SESSION || true, + age: TWO_DAYS, + }, port: process.env.HTTP_PORT || process.env.PORT || 4242, host: process.env.HTTP_HOST, pipe: undefined, @@ -66,7 +70,6 @@ function defaultOptions() { extendedPermissions: false, publicFolder, enableRequestLogger: false, - sessionAge: TWO_DAYS, adminAuthentication: process.env.ADMIN_AUTHENTICATION || 'unsecure', ui: {}, importFile: process.env.IMPORT_FILE, diff --git a/src/lib/routes/logout.js b/src/lib/routes/logout.js index de61e56535..f0f95e7b2f 100644 --- a/src/lib/routes/logout.js +++ b/src/lib/routes/logout.js @@ -10,7 +10,7 @@ class HealthCheckController extends Controller { logout(req, res) { if (req.session) { - req.session = null; + req.session.destroy(); } if (req.logout) { req.logout(); diff --git a/src/lib/server-impl.js b/src/lib/server-impl.js index dcabe7f5c5..8ab18c20f1 100644 --- a/src/lib/server-impl.js +++ b/src/lib/server-impl.js @@ -49,7 +49,6 @@ async function createApp(options) { eventBus, secret, logFactory: options.getLogger, // TODO: remove in v4.x - ...options, }; diff --git a/src/migrations/20210212114759-add-session-table.js b/src/migrations/20210212114759-add-session-table.js new file mode 100644 index 0000000000..790d0d0fb9 --- /dev/null +++ b/src/migrations/20210212114759-add-session-table.js @@ -0,0 +1,24 @@ +exports.up = function(db, cb) { + db.runSql( + ` + CREATE TABLE unleash_session ( + sid varchar PRIMARY KEY, + sess json NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + expired TIMESTAMP WITH TIME ZONE NOT NULL + ); + CREATE INDEX idx_unleash_session_expired ON unleash_session(expired); + `, + cb, + ); +}; + +exports.down = function(db, cb) { + db.runSql( + ` + DROP INDEX idx_unleash_session_expired; + DROP TABLE unleash_session; + `, + cb, + ); +}; diff --git a/src/server-dev.ts b/src/server-dev.ts index 5fd0268baa..53cd07684c 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -13,4 +13,7 @@ unleash.start({ }, enableRequestLogger: true, enableOAS: true, + session: { + db: true + }, }); diff --git a/src/test/e2e/helpers/test-helper.js b/src/test/e2e/helpers/test-helper.js index c98da85af0..57a7690c3e 100644 --- a/src/test/e2e/helpers/test-helper.js +++ b/src/test/e2e/helpers/test-helper.js @@ -18,7 +18,10 @@ function createApp(stores, adminAuthentication = 'none', preHook) { preHook, adminAuthentication, secret: 'super-secret', - sessionAge: 4000, + session: { + db: true, + age: 4000, + }, getLogger, }; const services = createServices(stores, config); diff --git a/yarn.lock b/yarn.lock index 636b4e5d59..3ec540e310 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,7 +951,7 @@ bintrees@1.0.1: resolved "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz" integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= -bluebird@^3.1.1: +bluebird@^3.1.1, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1405,6 +1405,14 @@ confusing-browser-globals@^1.0.9: resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz" integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== +connect-session-knex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-session-knex/-/connect-session-knex-2.0.0.tgz#c49003b8edd3e4cd64c701356223920abd052053" + integrity sha512-1QaN7k9NjXcXmE+MHoH7YeAGcUGdqZzpIKb8otHgqFQ2IYLhoeGG/o1PP2cdJZNgcr1gPHJEL8hmKIx8XosOhg== + dependencies: + bluebird "^3.7.2" + knex "^0.21.5" + contains-path@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz" @@ -2263,6 +2271,20 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.0" + uid-safe "~2.1.5" + express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz" @@ -3733,6 +3755,24 @@ knex@0.21.15: tildify "2.0.0" v8flags "^3.2.0" +knex@^0.21.5: + version "0.21.17" + resolved "https://registry.yarnpkg.com/knex/-/knex-0.21.17.tgz#f99f5b90132b9bb9c6eb5c81f0a035e0232c870f" + integrity sha512-kAt58lRwjzqwedApKF7luYPa7HsLb0oDiczwKrkZcekIzTmSow5YGK149S2C8HjH63R3NcOBo9+1rjvWnC1Paw== + dependencies: + colorette "1.2.1" + commander "^6.2.0" + debug "4.3.1" + esm "^3.2.25" + getopts "2.2.5" + interpret "^2.2.0" + liftoff "3.1.0" + lodash "^4.17.20" + pg-connection-string "2.4.0" + tarn "^3.0.1" + tildify "2.0.0" + v8flags "^3.2.0" + latest-version@^5.0.0: version "5.1.0" resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz" @@ -5020,6 +5060,11 @@ querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" @@ -6158,6 +6203,13 @@ typescript@^4.1.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz"