You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

117 lines
3.3 KiB

/*!
* Connect - cookieSession
* Copyright(c) 2011 Sencha Inc.
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('./../utils')
, Cookie = require('./session/cookie')
, debug = require('debug')('connect:cookieSession')
, signature = require('cookie-signature')
, crc32 = require('buffer-crc32');
/**
* Cookie Session:
*
* Cookie session middleware.
*
* var app = connect();
* app.use(connect.cookieParser());
* app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }}));
*
* Options:
*
* - `key` cookie name defaulting to `connect.sess`
* - `secret` prevents cookie tampering
* - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
* - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
*
* Clearing sessions:
*
* To clear the session simply set its value to `null`,
* `cookieSession()` will then respond with a 1970 Set-Cookie.
*
* req.session = null;
*
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function cookieSession(options){
// TODO: utilize Session/Cookie to unify API
options = options || {};
var key = options.key || 'connect.sess'
, trustProxy = options.proxy;
return function cookieSession(req, res, next) {
// req.secret is for backwards compatibility
var secret = options.secret || req.secret;
if (!secret) throw new Error('`secret` option required for cookie sessions');
// default session
req.session = {};
var cookie = req.session.cookie = new Cookie(options.cookie);
// pathname mismatch
if (0 != req.originalUrl.indexOf(cookie.path)) return next();
// cookieParser secret
if (!options.secret && req.secret) {
req.session = req.signedCookies[key] || {};
req.session.cookie = cookie;
} else {
// TODO: refactor
var rawCookie = req.cookies[key];
if (rawCookie) {
var unsigned = utils.parseSignedCookie(rawCookie, secret);
if (unsigned) {
var originalHash = crc32.signed(unsigned);
req.session = utils.parseJSONCookie(unsigned) || {};
req.session.cookie = cookie;
}
}
}
res.on('header', function(){
// removed
if (!req.session) {
debug('clear session');
cookie.expires = new Date(0);
res.setHeader('Set-Cookie', cookie.serialize(key, ''));
return;
}
delete req.session.cookie;
// check security
var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
, tls = req.connection.encrypted || (trustProxy && 'https' == proto)
, secured = cookie.secure && tls;
// only send secure cookies via https
if (cookie.secure && !secured) return debug('not secured');
// serialize
debug('serializing %j', req.session);
var val = 'j:' + JSON.stringify(req.session);
// compare hashes, no need to set-cookie if unchanged
if (originalHash == crc32.signed(val)) return debug('unmodified session');
// set-cookie
val = 's:' + signature.sign(val, secret);
val = cookie.serialize(key, val);
debug('set-cookie %j', cookie);
res.setHeader('Set-Cookie', val);
});
next();
};
};