Skip to content
This repository has been archived by the owner on Feb 3, 2020. It is now read-only.

Session Tuning #14

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
111 changes: 62 additions & 49 deletions lib/cookie-sessions.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
/*globals escape unescape */

var crypto = require('crypto');
var url = require('url');

var exports = module.exports = function(settings){

// Extend a given object with all the properties in passed-in object(s).
// From underscore.js (http://documentcloud.github.com/underscore/)
function extend(obj) {
Array.prototype.slice.call(arguments).forEach(function(source) {
for (var prop in source) obj[prop] = source[prop];
});
return obj;
}

var exports;
exports = module.exports = function(settings){

var default_settings = {
// don't set a default cookie secret, must be explicitly defined
session_key: '_node',
timeout: 1000 * 60 * 60 * 24, // 24 hours
path: '/'
timeout: 60 * 60 * 24 * 1000, // 24 hours in milliseconds
path: '/',
domain: null,
secure: false,
useMaxAge: true,
useExpires: true,
useHttpOnly: true
};
var s = extend(default_settings, settings);
if(!s.secret) throw new Error('No secret set in cookie-session settings');

if(typeof s.path !== 'string' || s.path.indexOf('/') != 0)
if(typeof s.path !== 'string' || s.path.indexOf('/') !== 0) {
throw new Error('invalid cookie path, must start with "/"');
}

return function(req, res, next){
// if the request is not under the specified path, do nothing.
if (url.parse(req.url).pathname.indexOf(s.path) != 0) {
if (url.parse(req.url).pathname.indexOf(s.path) !== 0) {
next();
return;
}
Expand Down Expand Up @@ -47,29 +66,32 @@ var exports = module.exports = function(settings){
var cookiestr;
if (req.session === undefined) {
if ("cookie" in req.headers) {
cookiestr = escape(s.session_key) + '='
+ '; expires=' + exports.expires(0)
+ '; path=' + s.path + '; HttpOnly';
cookiestr = escape(s.session_key) + '=';
s.timeout = 0;
}
} else {
cookiestr = escape(s.session_key) + '='
+ escape(exports.serialize(s.secret, req.session))
+ '; expires=' + exports.expires(s.timeout)
+ '; path=' + s.path + '; HttpOnly';
cookiestr = escape(s.session_key) + '=' + escape(exports.serialize(s.secret, req.session));
}

if (cookiestr !== undefined) {
if(Array.isArray(headers)) headers.push(['Set-Cookie', cookiestr]);
else {
if (s.useExpires) cookiestr += '; expires=' + exports.expires(s.timeout);
if (s.useMaxAge) cookiestr += '; max-age=' + (s.timeout / 1000); // In seconds
if (s.path) cookiestr += '; path=' + s.path;
if (s.domain) cookiestr += '; domain=' + s.domain;
if (s.secure) cookiestr += '; secure';
if (s.useHttpOnly) cookiestr += '; HttpOnly';

if(Array.isArray(headers)) {
headers.push(['Set-Cookie', cookiestr]);
} else {
// if a Set-Cookie header already exists, convert headers to
// array so we can send multiple Set-Cookie headers.
if(headers['Set-Cookie'] !== undefined){
if (headers['Set-Cookie'] !== undefined) {
headers = exports.headersToArray(headers);
headers.push(['Set-Cookie', cookiestr]);
}
// if no Set-Cookie header exists, leave the headers as an
// object, and add a Set-Cookie property
else {
} else {
// if no Set-Cookie header exists, leave the headers as an
// object, and add a Set-Cookie property
headers['Set-Cookie'] = cookiestr;
}
}
Expand All @@ -81,7 +103,7 @@ var exports = module.exports = function(settings){
}
// call the original writeHead on the request
return _writeHead.apply(res, args);
}
};
next();

};
Expand All @@ -95,21 +117,11 @@ exports.headersToArray = function(headers){
}, []);
};


// Extend a given object with all the properties in passed-in object(s).
// From underscore.js (http://documentcloud.github.com/underscore/)
function extend(obj) {
Array.prototype.slice.call(arguments).forEach(function(source) {
for (var prop in source) obj[prop] = source[prop];
});
return obj;
};

exports.deserialize = function(secret, timeout, str){
// Parses a secure cookie string, returning the object stored within it.
// Throws an exception if the secure cookie string does not validate.

if(!exports.valid(secret, timeout, str)){
if(!exports.valid(secret, timeout, str)) {
throw new Error('invalid cookie');
}
var data = exports.decrypt(secret, exports.split(str).data_blob);
Expand All @@ -121,7 +133,7 @@ exports.serialize = function(secret, data){

var data_str = JSON.stringify(data);
var data_enc = exports.encrypt(secret, data_str);
var timestamp = (new Date()).getTime();
var timestamp = new Date().getTime();
var hmac_sig = exports.hmac_signature(secret, timestamp, data_enc);
var result = hmac_sig + timestamp + data_enc;
if(!exports.checkLength(result)){
Expand Down Expand Up @@ -156,6 +168,7 @@ exports.valid = function(secret, timeout, str){
var hmac_sig = exports.hmac_signature(
secret, parts.timestamp, parts.data_blob
);

return (
parts.hmac_signature === hmac_sig &&
parts.timestamp + timeout > new Date().getTime()
Expand Down Expand Up @@ -184,21 +197,20 @@ exports.readCookies = function(req){
// will already contain the parsed cookies
if (req.cookies) {
return req.cookies;
} else {
// Extracts the cookies from a request object.
var cookie = req.headers.cookie;
if(!cookie){
return {};
}
else {
// Extracts the cookies from a request object.
var cookie = req.headers.cookie;
if(!cookie){
return {};
}
var parts = cookie.split(/\s*;\s*/g).map(function(x){
return x.split('=');
});
return parts.reduce(function(a, x){
a[unescape(x[0])] = unescape(x[1]);
return a;
}, {});
}
var parts = cookie.split(/\s*;\s*/g).map(function(x){
return x.split('=');
});
return parts.reduce(function(a, x){
a[unescape(x[0])] = unescape(x[1]);
return a;
}, {});
}
};

exports.readSession = function(key, secret, timeout, req){
Expand All @@ -212,7 +224,8 @@ exports.readSession = function(key, secret, timeout, req){
return undefined;
};


// Generates an expires date
// @params timeout the time in milliseconds before the cookie expires
exports.expires = function(timeout){
return (new Date(new Date().getTime() + (timeout))).toUTCString();
return new Date(new Date().getTime() + timeout).toUTCString();
};
2 changes: 1 addition & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require.paths.push(__dirname + '/deps');
require.paths.push(__dirname + '/lib');

try {
var testrunner = require('nodeunit').testrunner;
var testrunner = require('nodeunit').reporters.default;
}
catch(e) {
var sys = require('sys');
Expand Down
Loading