Use 'req.sessionStore' instead of directly using 'store' object.
Description
Use req.sessionStore instead of directly using store object. This gives flexibility to inject additional parameters like req to store.get and store.destroy via custom traps using JavaScript Proxy without changing the interface of the Store.
Motivation and Context
We want to pass req object down to the methods of our Store implementation. store.set method can access it from session.req but it is not available in store.get and store.destroy methods.
One workaround we found is to leverage setter of req.sessionStore to create JavaScript Proxy of the store object. This proxy has traps for store.get and store.set and it adds the req object as a parameter to the function call.
To make this work we need changes in this module to use req.sessionStore.get and req.sessionStore.destroy methods so that the proxy object is used.
Hmmm, I like where you're going with this and I have a solution as well.
Memory/CPU My concern is structure, doesn't everyone reference the same store object? So with this pull request, we are consuming unnecessary memory/cpu resources upon every session instantiation? even thought it may be minimal.
Security? Thought client can access req headers? When you pass req.sessionStore, it does not include data from other users, no potential for session hijacking?
First, notice how the global variable app is utilized to "carry" objects throughout the entire codebase.
These objects can now be accessed anywhere within Express, socket.io, models, controllers, requires files, etc., as long as we pass the global express variable app to them.
var express = require('express'),
app = express()
app.set('server', require('http').createServer(app))
app.set('colors', require('chalk'))
app.set('functions', require('./lib/functions')(app))
app.set('json spaces', 4)
app.disable('x-powered-by')
app.enable('trust proxy')
app.enable('view cache')
app.engine('hbs', require('express-handlebars').create({
helpers: require('./lib/helpers').helpers,
extname: '.hbs',
layoutsDir: './views/layouts'
}).engine)
app.set('view engine', 'hbs')
console.log((process.env.protocol === 'https' ? app.get('colors').bold.green.bgWhite('PRODUCTION') : app.get('colors').bold.black.bgYellow('DEVELOPMENT')))
app.use((req, res, next) => {
res.locals.nonce = require('uuid/v4')
next()
})
Next app.get('session') holds the reference to express_session. When calling express_session, simply return the parameters inside of express_session(parameters) -> session.
Now we can reference app.get('session'), for example....
app.get('session').store.destroy(session, function() { }).
var express_session = require('express-session'),
redis_store = new (require('connect-redis')(express_session))()
var session = express_session({
store: redis_store,
secret: process.env.session_secret,
name: process.env.session_name,
rolling: true,
saveUninitialized: true,
unset: 'destroy',
resave: true,
proxy: true,
logErrors: false,
cookie: {
path: '/',
domain: '.' + process.env.app_domain,
httpOnly: true,
secure: process.env.protocol === 'https',
maxAge: (60 * 60 * 1000) // 60 mins
}
})
app.use(session)
app.set('session', session)
app.use(require('./middleware')(app))
app.get('session').rolling // returns true
app.get('session').resave // returns true
app.get('session').unset // returns 'destroy'
app.get('session').cookie.path // returns '/'
app.get('session').cookie.maxAge // returns 3600000
...
We can also reference the store and all of it's properties and methods
app.get('session').store.destroy(function() {
})
Thanks for looking into this.
The idea behind this PR is to be able to trace the logging for a req. Especially to know why error happened while processing the request and one source of the error is the sessionStore. Right now we are using https://www.npmjs.com/package/continuation-local-storage and we want to move away from it.
A Store implementation which can enable us to do this is:
const Store = app.Store || app.session.Store;
class SomeStore extends Store {
get(id, req, callback) {
if (typeof req === 'function') {
callback = req;
req = void 0;
}
....
if (req) req.log(...);
....
}
set(id, session, callback) {
....
if (session.req) session.req.log(...);
....
}
destroy(id, req, callback) {
if (typeof req === 'function') {
callback = req;
req = void 0;
}
....
if (req) req.log(...);
....
}
...
}
Please note that this is a modified interface and changing store.get(req.sessionID, function(err, sess){ (https://github.com/expressjs/session/blob/master/index.js#L474) will pretty much break everybody. The workaround we are exploring is to inject sessionStore getter and setter in req object and leverage req.sessionStore = store; (https://github.com/expressjs/session/blob/master/index.js#L214) in the express-session middleware function.
Object.defineProperty(req, 'sessionStore', {
get: function() { return this[kSessionStore]; },
set: function(store) {
return this[kSessionStore]=constructJSProxy(store, this);
}
});
req.session.save and req.session.destroy are already using this.req.sessionStore.
The JS Proxy looks like following:
const constructJSProxy = function constructSessionStoreProxy(store, req) {
const handler = {
get: function(target, prop, receiver) {
const origMethod = target[prop];
if (typeof origMethod !== 'function') {
return origMethod;
}
switch (origMethod) {
case SomeStore.prototype.get:
case SomeStore.prototype.destroy:
return function (id, callback) {
return origMethod.call(target, id, req, callback);
};
default:
return origMethod;
}
}
};
return new Proxy(store, handler);
};
@knoxcard, Please let me known if there are any specific concerns you want me to address to help merge this PR.
As I have stated, our goal is to be able to access req object in the Store's get and destroy methods. Any alternate recommendation to achieve this is also appreciated.
@sumeetkakkar - I am not a project contributor or owner to this repo. Your best bet is having @dougwilson review this.
Can you look at #712 and confirm if that will accomplish the same thing as here? It is further along and seems much more flexible, including keeping our internals internal and less at risk for user overrides causing hard to debug issues.
Can you look at #712 and confirm if that will accomplish the same thing as here? It is further along and seems much more flexible, including keeping our internals internal and less at risk for user overrides causing hard to debug issues.
👍
@dougwilson , Can you please share ETA for #712?
We are working though everything, just there is a large backlog. Part of resolving that is resolving the difference between these two PRs, which is why I messaged on here.
Were you able to take a look at the changes in #712 ? Do you believe they will accomplish the same thing has this pull request?
Our goal is to be able access req object in store.get and store.destroy. It seems #712 will make it possible. So yes this PR can be closed in favor of #712.
Thank you!