Skip to content

Commit

Permalink
Custom Session Stores
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpatrick committed Feb 11, 2021
1 parent a25f393 commit df6e880
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 6 deletions.
37 changes: 37 additions & 0 deletions examples/custom-session-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const express = require('express');
const { EventEmitter } = require('events');
const { auth } = require('../');

const app = express();
const IN_MEMORY_SESSION = {};

class Store extends EventEmitter {
get (id, cb) {
const val = IN_MEMORY_SESSION[id];
console.log('sessionStoreget', id, val);
process.nextTick(() => cb(null, val));
}
set (id, data, cb) {
console.log('sessionstoreSave', id, data);
IN_MEMORY_SESSION[id] = data;
process.nextTick(() => cb());
}
destroy (id, cb) {
console.log('sessionstoreDestroy', id);
delete IN_MEMORY_SESSION[id];
process.nextTick(() => cb());
}
}

app.use(
auth({
idpLogout: true,
sessionStore: Store
})
);

app.get('/', (req, res) => {
res.send(`hello ${req.oidc.user.sub}`);
});

module.exports = app;
66 changes: 62 additions & 4 deletions lib/appSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const {
JWE,
errors: { JOSEError },
} = require('jose');
const onHeaders = require('on-headers');
const crypto = require('crypto');
const { promisify } = require('util');
const cookie = require('cookie');
const COOKIES = require('./cookies');
const { encryption: deriveKey } = require('./hkdf');
Expand Down Expand Up @@ -136,7 +137,52 @@ module.exports = (config) => {
}
}

return (req, res, next) => {
class CookieStore {
async get(idOrVal) {
return decrypt(idOrVal);
}

async set(id, req, res, iat) {
setCookie(req, res, iat);
}
}

class CustomStore {
constructor(sessionStore) {
this.storeGet = promisify(sessionStore.get);
this.storeSet = promisify(sessionStore.set);
this.storeDestroy = promisify(sessionStore.destroy);
}

async get(id) {
return this.storeGet(id);
}

async set(
id,
req,
res,
{ uat = epoch(), iat = uat, exp = calculateExp(iat, uat) }
) {
if (!req[sessionName] || !Object.keys(req[sessionName]).length) {
await this.storeDestroy(id);
} else {
id = id || crypto.randomBytes(16).toString('hex');
await this.storeSet(id, {
protected: { iat, uat, exp },
cleartext: JSON.stringify(req[sessionName]),
});
const cookieOptions = {
...cookieConfig,
expires: cookieConfig.transient ? 0 : new Date(exp * 1000),
};
delete cookieOptions.transient;
res.cookie(sessionName, id, cookieOptions);
}
}
}

return async (req, res, next) => {
if (req.hasOwnProperty(sessionName)) {
debug(
'request object (req) already has %o property, this is indicative of a middleware setup problem',
Expand All @@ -149,6 +195,10 @@ module.exports = (config) => {
);
}

const store = config.sessionStore
? new CustomStore(config.sessionStore)
: new CookieStore();

req[COOKIES] = cookie.parse(req.get('cookie') || '');

let iat;
Expand Down Expand Up @@ -185,7 +235,9 @@ module.exports = (config) => {
.join('');
}
if (existingSessionValue) {
const { protected: header, cleartext } = decrypt(existingSessionValue);
const { protected: header, cleartext } = await store.get(
existingSessionValue
);
({ iat, uat, exp } = header);

// check that the existing session isn't expired based on options when it was established
Expand Down Expand Up @@ -229,7 +281,13 @@ module.exports = (config) => {
attachSessionObject(req, sessionName, {});
}

onHeaders(res, setCookie.bind(undefined, req, res, { iat }));
const { end: origEnd } = res;
res.end = async function resEnd(...args) {
await store.set(existingSessionValue, req, res, {
iat,
});
origEnd.call(res, ...args);
};

return next();
};
Expand Down
1 change: 1 addition & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { defaultState: getLoginState } = require('./hooks/getLoginState');
const isHttps = /^https:/i;

const paramsSchema = Joi.object({
sessionStore: Joi.function().optional(),
secret: Joi.alternatives([
Joi.string().min(8),
Joi.binary().min(8),
Expand Down
8 changes: 6 additions & 2 deletions test/appSession.tests.js → test/appSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,12 @@ describe('appSession', () => {
it('should throw for reassigning session', async () => {
server = await createServer((req, res, next) => {
appSession(getConfig(defaultConfig))(req, res, () => {
req.appSession = {};
next();
try {
req.appSession = {};
next();
} catch (e) {
next(e);
}
});
});
const res = await request.get('/session', { baseUrl, json: true });
Expand Down

0 comments on commit df6e880

Please sign in to comment.