connect
Version:
High performance middleware framework
174 lines (145 loc) • 5.33 kB
JavaScript
/*!
* Ext JS Connect
* Copyright(c) 2010 Sencha Inc.
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Session = require('./session/session')
, utils = require('./../utils')
, crypto = require('crypto');
/**
* Setup session store with the given `options`.
*
* Options:
*
* - `store` Session store instance
* - `fingerprint` Custom fingerprint generating function
* - `secret` Secret string used to compute hash
*
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function sessionSetup(options){
options = options || {};
// The cookie key to store the session id in.
var key = options.key || 'connect.sid';
// Default memory store
var store = options.store || new (require('./session/memory'));
// Used to verify session ids / defaults to user agent
var fingerprint = options.fingerprint || function fingerprint(req) {
return req.headers['user-agent'] || '';
};
// Ensure secret is present
if (!options.secret) {
throw new Error('session() middleware requires the "secret" option string for security');
}
var secret = options.secret;
return function sessionHandle(req, res, next) {
if (!req.cookies) {
next(new Error("session requires cookieDecoder to work properly"));
return;
}
var pause = utils.pause(req);
// Wrap writeHead as a hook to save the session and send the cookie
var writeHead = res.writeHead;
res.writeHead = function(status, headers){
// Update the session in the store if there is one
if (req.session) {
req.session.touch();
store.set(req.sessionID, req.session);
}
// Only send secure session cookies when there is a secure connection.
// proxySecure is a custom attribute to allow for a reverse proxy to handle SSL connections and to
// communicate to connect over HTTP that the incoming connection is secure.
var secured = store.cookie.secure && (req.connection.secure || req.connection.proxySecure);
if (secured || !store.cookie.secure) {
// Send an updated cookie to the browser
store.cookie.expires = false === store.cookie.persistent
? null
: new Date(Date.now() + store.maxAge);
// Multiple Set-Cookie headers
headers = headers || {};
var cookie = utils.serializeCookie(key, req.sessionID, store.cookie);
if (headers['Set-Cookie']) {
headers['Set-Cookie'] += '\r\nSet-Cookie: ' + cookie;
} else {
headers['Set-Cookie'] = cookie;
}
}
// Pass through the writeHead call
res.writeHead = writeHead;
return res.writeHead(status, headers);
};
// Calculates the security hash to prevent session hijacking
// Uses information on the user-agent that created the session as it's fingerprint
function hash(base) {
return crypto
.createHmac('sha256', secret)
.update(base + fingerprint(req))
.digest('base64')
.replace(/=*$/, '');
}
// Generates the new session
var generate = store.generate = function(){
var base = utils.uid(24);
var sessionID = base + "." + hash(base);
req.sessionID = sessionID;
req.session = new Session(req);
};
// Expose store
req.sessionStore = store;
// Get the sessionID from the cookie
req.sessionID = req.cookies[key];
// Make a new session if the browser doesn't send a sessionID
if (!req.sessionID) {
generate();
next();
return;
}
// Check the fingerprint
var parts = req.sessionID.split('.');
if (parts[1] !== hash(parts[0])) {
// Make a new session if it doesn't check out
generate();
next();
return;
}
// Generate the session object
store.get(req.sessionID, function (err, sess) {
pause.end();
// Error/missing handling
if (err) {
if (err.errno === process.ENOENT) {
// If the session ID is invalid, generate a new session
generate();
next();
} else {
// Otherwise pass the exception along
next(err);
}
pause.resume();
return;
}
if (!sess) {
// Some stores use a falsy result to signify no result
generate();
next();
pause.resume();
return;
}
// Load the session into the request
req.session = new Session(req, sess);
next();
pause.resume();
});
};
};
/**
* Expose constructors.
*/
exports.Session = Session;
exports.Store = require('./session/store');
exports.MemoryStore = require('./session/memory');