From a0a35766911f5354487663b0f148b824ca32ba44 Mon Sep 17 00:00:00 2001 From: gumi Date: Sun, 15 Mar 2020 17:28:35 -0400 Subject: turn the session store into a proper class --- src/routers/vault/index.js | 3 +- src/routers/vault/types/SessionStore.js | 129 ++++++++++++++++++++++++++++++++ src/routers/vault/utils/ephemeral.js | 45 ----------- 3 files changed, 131 insertions(+), 46 deletions(-) create mode 100644 src/routers/vault/types/SessionStore.js diff --git a/src/routers/vault/index.js b/src/routers/vault/index.js index 4213dcb..831ec93 100644 --- a/src/routers/vault/index.js +++ b/src/routers/vault/index.js @@ -1,6 +1,7 @@ const express = require("express"); // from npm registry const Sequelize = require("sequelize"); // from npm registry const Ephemeral = require("./utils/ephemeral.js"); +const SessionStore = require("./types/SessionStore.js"); const models = { vault: [ @@ -46,7 +47,7 @@ module.exports = exports = class Vault { // to middlewares, so we might have to curry them this.api = api; - this.api.locals.session = Ephemeral.session_handler; + this.api.locals.session = new SessionStore(); this.api.locals.identity_pending = Ephemeral.identity_handler; this.router = express.Router(["caseSensitive", "strict"]); this.sequelize = {}; diff --git a/src/routers/vault/types/SessionStore.js b/src/routers/vault/types/SessionStore.js new file mode 100644 index 0000000..daa71c3 --- /dev/null +++ b/src/routers/vault/types/SessionStore.js @@ -0,0 +1,129 @@ +"use strict"; +const Session = require("./Session.js"); + +/** + * we store the timeout directly in Session instances + * @type Symbol("session timeout") + */ +const timeout_symbol = Symbol("session timeout"); + +/** + * holds the Sessions and re-hydrates them when accessed + */ +class SessionStore { + /** + * a Map of all Session instances + * @type Map + */ + sessions = new Map(); + /** lifetime of an unauthenticated Session, in minutes */ + base_lifetime = 30; + /** lifetime of a properly authenticated Session, in minutes */ + authed_lifetime = 60 * 6; // 6 hours + + /** + * creates a new SessionStore + * @param {number} base - the base Session lifetime, in minutes + * @param {number} authed - the lifetime of an authenticated Session + */ + constructor (base = 0, authed = 0) { + this.base_lifetime = base || this.base_lifetime; + this.authed_lifetime = authed || this.authed_lifetime; + } + + /** + * re-hydrates a Session by resetting the expiration + * @param {string} key - the Session key + * @param {Session} sess - the Session to hydrate + * @returns {Session} the same Session + */ + hydrate (key, sess) { + /** the new expiration, in minutes */ + let minutes = this.base_lifetime; + + if (Reflect.has(sess, timeout_symbol)) { + /** clear any existing timeout */ + clearTimeout(sess[timeout_symbol]); + } + + if (sess.authenticated === true) { + /** Session is properly authenticated: set lifetime accordingly */ + minutes = this.authed_lifetime; + } + + /** the new expiry Date */ + const expires = new Date(); + expires.setUTCMinutes(expires.getUTCMinutes() + minutes); // update it + sess.expires = expires; // swap the old for the new expiry + sess[timeout_symbol] = setTimeout(() => this.delete(key), minutes * 60000); + return sess; + } + + /** + * checks whether a Session with the given key exists + * @param {string} key - the Session key + */ + has (key) { + return this.sessions.has(key); + } + + /** + * returns a Session with the matching key + * @param {string} key - the Session key + * @returns {Session} the found Session + */ + get (key) { + /** lookup the session by key */ + const sess = this.sessions.get(key); + + if (sess) { + /** the Session, re-hydrated */ + return this.hydrate(key, sess); + } + + /** session not found */ + return null; + } + + /** + * adds a Session to the store + * @param {string} key - the Session key + * @param {Session} sess - the Session + */ + set (key, sess) { + this.sessions.set(key, this.hydrate(key, sess)); + } + + /** + * removes a Session with the matching key from the store + * + * NOTE: this does not actually delete the Session instance + * @param {string} key - the Session key + */ + delete (key) { + /** lookup the session by key */ + const sess = this.sessions.get(key); + + if (sess) { + if (Reflect.has(sess, timeout_symbol)) { + /** clear any existing timeout */ + clearTimeout(sess[timeout_symbol]); + } + + return this.sessions.delete(key) + } + + /** session not found */ + return false; + } + + /** + * iterator for use in for-of + * @returns {Iterator<[string, Session]>} the Map iterator of the SessionStore instance + */ + [Symbol.iterator] () { + return this.sessions[Symbol.iterator](); + } +} + +module.exports = SessionStore; diff --git a/src/routers/vault/utils/ephemeral.js b/src/routers/vault/utils/ephemeral.js index 570600c..87889da 100644 --- a/src/routers/vault/utils/ephemeral.js +++ b/src/routers/vault/utils/ephemeral.js @@ -1,50 +1,6 @@ -// TODO: use Redis for in-memory caching of sessions - -// this was originally a Proxy> but was replaced because of a nodejs bug -// XXX: maybe we should use an already-existing Express session manager // NIH syndrome const timeout_symbol = Symbol("timeout"); const hydrate_symbol = Symbol("hydrate"); const container_symbol = Symbol("container"); -const session_handler = { - [container_symbol]: new Map(), - [hydrate_symbol] (key, obj) { - if (obj === null || obj === undefined) - return obj; - - let minutes = 30; - - if (Reflect.has(obj, timeout_symbol)) { - clearTimeout(obj[timeout_symbol]); - minutes = 360; // 6 hours - } - - let expires = new Date(); - expires.setUTCMinutes(expires.getUTCMinutes() + minutes); - obj.expires = expires // this could also be a symbol - obj[timeout_symbol] = setTimeout(() => session_handler.delete(key), minutes * 60000); - - return obj; - }, - has (key) { - return session_handler[container_symbol].has(key); - }, - get (key) { - return session_handler[hydrate_symbol](key, session_handler[container_symbol].get(key)); - }, - set (key, obj) { - return session_handler[container_symbol].set(key, session_handler[hydrate_symbol](key, obj)); - }, - delete (key) { - if (session_handler[container_symbol].get(key) && session_handler[container_symbol].get(key)[timeout_symbol]) - clearTimeout(session_handler[container_symbol].get(key)[timeout_symbol]); - return session_handler[container_symbol].delete(key); - }, - [Symbol.iterator]: function* () { - for (const [key, obj] of session_handler[container_symbol]) { - yield [key, obj]; - } - }, -}; // TODO: DRY this shit const identity_handler = { @@ -86,6 +42,5 @@ const identity_handler = { module.exports = { - session_handler, identity_handler, } -- cgit v1.2.3-60-g2f50