passport-negotiate
Version:
Negotiate (kerberos) authentication strategy for Passport.
244 lines (203 loc) • 6.45 kB
JavaScript
/*!
* Connect - session
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var uid = require('uid2')
, crc32 = require('buffer-crc32')
, parse = require('url').parse
, signature = require('cookie-signature')
, debug = require('debug')('session')
var Session = require('./session/session')
, MemoryStore = require('./session/memory')
, Cookie = require('./session/cookie')
, Store = require('./session/store')
// environment
var env = process.env.NODE_ENV;
/**
* Expose the middleware.
*/
exports = module.exports = session;
/**
* Expose constructors.
*/
exports.Store = Store;
exports.Cookie = Cookie;
exports.Session = Session;
exports.MemoryStore = MemoryStore;
/**
* Warning message for `MemoryStore` usage in production.
*/
var warning = 'Warning: connect.session() MemoryStore is not\n'
+ 'designed for a production environment, as it will leak\n'
+ 'memory, and will not scale past a single process.';
/**
* Setup session store with the given `options`.
*
* See README.md for documentation of options and formatting.
*
* Session data is _not_ saved in the cookie itself, however cookies are used,
* so you must use the cookie-parser middleware _before_ `session()`.
* [https://github.com/expressjs/cookie-parser]
*
* @param {Object} options
* @return {Function} middleware
* @api public
*/
function session(options){
var options = options || {}
, key = options.key || 'connect.sid'
, store = options.store || new MemoryStore
, cookie = options.cookie || {}
, trustProxy = options.proxy
, storeReady = true
, rollingSessions = options.rolling || false;
// notify user that this store is not
// meant for a production environment
if ('production' == env && store instanceof MemoryStore) {
console.warn(warning);
}
// generates the new session
store.generate = function(req){
req.sessionID = uid(24);
req.session = new Session(req);
req.session.cookie = new Cookie(cookie);
};
store.on('disconnect', function(){ storeReady = false; });
store.on('connect', function(){ storeReady = true; });
return function session(req, res, next) {
// self-awareness
if (req.session) return next();
// Handle connection as if there is no session if
// the store has temporarily disconnected etc
if (!storeReady) return debug('store is disconnected'), next();
// pathname mismatch
var originalPath = parse(req.originalUrl).pathname;
if (0 != originalPath.indexOf(cookie.path || '/')) return next();
// backwards compatibility for signed cookies
// req.secret is passed from the cookie parser middleware
var secret = options.secret || req.secret;
// ensure secret is available or bail
if (!secret) throw new Error('`secret` option required for sessions');
var originalHash
, originalId;
// expose store
req.sessionStore = store;
// grab the session cookie value and check the signature
var rawCookie = req.cookies[key];
// get signedCookies for backwards compat with signed cookies
var unsignedCookie = req.signedCookies[key];
if (!unsignedCookie && rawCookie) {
unsignedCookie = (0 == rawCookie.indexOf('s:'))
? signature.unsign(rawCookie.slice(2), secret)
: rawCookie;
}
// set-cookie
var writeHead = res.writeHead;
res.writeHead = function(){
if (!req.session) {
debug('no session');
writeHead.apply(res, arguments);
return;
}
var cookie = req.session.cookie
, proto = (req.headers['x-forwarded-proto'] || '').split(',')[0].toLowerCase().trim()
, tls = req.connection.encrypted || (trustProxy && 'https' == proto)
, isNew = unsignedCookie != req.sessionID;
// only send secure cookies via https
if (cookie.secure && !tls) {
debug('not secured');
writeHead.apply(res, arguments);
return;
}
// in case of rolling session, always reset the cookie
if (!rollingSessions) {
// browser-session length cookie
if (null == cookie.expires) {
if (!isNew) {
debug('already set browser-session cookie');
writeHead.apply(res, arguments);
return
}
// compare hashes and ids
} else if (originalHash == hash(req.session) && originalId == req.session.id) {
debug('unmodified session');
writeHead.apply(res, arguments);
return
}
}
var val = 's:' + signature.sign(req.sessionID, secret);
debug('set-cookie %s', val);
res.cookie(key, val, cookie.data);
writeHead.apply(res, arguments);
};
// proxy end() to commit the session
var end = res.end;
res.end = function(data, encoding){
res.end = end;
if (!req.session) return res.end(data, encoding);
debug('saving');
req.session.resetMaxAge();
req.session.save(function(err){
if (err) console.error(err.stack);
debug('saved');
res.end(data, encoding);
});
};
// generate the session
function generate() {
store.generate(req);
}
// get the sessionID from the cookie
req.sessionID = unsignedCookie;
// generate a session if the browser doesn't send a sessionID
if (!req.sessionID) {
debug('no SID sent, generating session');
generate();
next();
return;
}
// generate the session object
debug('fetching %s', req.sessionID);
store.get(req.sessionID, function(err, sess){
// error handling
if (err) {
debug('error %j', err);
if ('ENOENT' == err.code) {
generate();
next();
} else {
next(err);
}
// no session
} else if (!sess) {
debug('no session found');
generate();
next();
// populate req.session
} else {
debug('session found');
store.createSession(req, sess);
originalId = req.sessionID;
originalHash = hash(sess);
next();
}
});
};
};
/**
* Hash the given `sess` object omitting changes to `.cookie`.
*
* @param {Object} sess
* @return {String}
* @api private
*/
function hash(sess) {
return crc32.signed(JSON.stringify(sess, function(key, val){
if ('cookie' != key) return val;
}));
}