summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2020-03-15 17:28:35 -0400
committergumi <git@gumi.ca>2020-03-15 17:28:35 -0400
commita0a35766911f5354487663b0f148b824ca32ba44 (patch)
tree6a5a80a52eaacc8f8b35798cedd16ddea37e3992
parent267b900a82d7870e4bcd56857887fd3f697e6e45 (diff)
downloadapi-a0a35766911f5354487663b0f148b824ca32ba44.tar.gz
api-a0a35766911f5354487663b0f148b824ca32ba44.tar.bz2
api-a0a35766911f5354487663b0f148b824ca32ba44.tar.xz
api-a0a35766911f5354487663b0f148b824ca32ba44.zip
turn the session store into a proper class
-rw-r--r--src/routers/vault/index.js3
-rw-r--r--src/routers/vault/types/SessionStore.js129
-rw-r--r--src/routers/vault/utils/ephemeral.js45
3 files changed, 131 insertions, 46 deletions
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<string, Session>
+ */
+ 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<Map<String, Session>> 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,
}