Permalink
Browse files

code so far

  • Loading branch information...
1 parent 6dd822a commit 4750436521ed054f448fe011116812c8ee2fae1a @caolan committed Jul 28, 2010
Showing with 553 additions and 0 deletions.
  1. +3 −0 .gitmodules
  2. +1 −0 deps/nodeunit
  3. +146 −0 lib/cookie-sessions.js
  4. +22 −0 test.js
  5. +381 −0 test/test-cookie-sessions.js
View
@@ -0,0 +1,3 @@
+[submodule "deps/nodeunit"]
+ path = deps/nodeunit
+ url = git://github.com/caolan/nodeunit.git
Submodule nodeunit added at ce2bcf
View
@@ -0,0 +1,146 @@
+var crypto = require('crypto');
+
+
+// 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)){
+ throw new Error('invalid cookie');
+ }
+ var data = exports.decrypt(secret, exports.split(str).data_blob);
+ return JSON.parse(data);
+};
+
+exports.serialize = function(secret, data){
+ // Turns a JSON-compatibile object literal into a secure cookie string
+
+ var data_str = JSON.stringify(data);
+ var data_enc = exports.encrypt(secret, data_str);
+ 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)){
+ throw new Error('data too long to store in a cookie');
+ }
+ return result;
+};
+
+exports.split = function(str){
+ // Splits a cookie string into hmac signature, timestamp and data blob.
+ return {
+ hmac_signature: str.slice(0,40),
+ timestamp: parseInt(str.slice(40, 53), 10),
+ data_blob: str.slice(53)
+ };
+};
+
+exports.hmac_signature = function(secret, timestamp, data){
+ // Generates a HMAC for the timestamped data, returning the
+ // hex digest for the signature.
+ var hmac = crypto.createHmac('sha1', secret);
+ hmac.update(timestamp + data);
+ return hmac.digest('hex');
+};
+
+exports.valid = function(secret, timeout, str){
+ // Tests the validity of a cookie string. Returns true if the HMAC
+ // signature of the secret, timestamp and data blob matches the HMAC in the
+ // cookie string, and the cookie's age is less than the timeout value.
+
+ var parts = exports.split(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()
+ );
+};
+
+exports.decrypt = function(secret, str){
+ // Decrypt the aes192 encoded str using secret.
+ var decipher = crypto.createDecipher("aes192", secret);
+ return decipher.update(str, 'hex', 'utf8') + decipher.final('utf8');
+};
+
+exports.encrypt = function(secret, str){
+ // Encrypt the str with aes192 using secret.
+ var cipher = crypto.createCipher("aes192", secret);
+ return cipher.update(str, 'utf8', 'hex') + cipher.final('hex');
+};
+
+exports.checkLength = function(str){
+ // Test if a string is within the maximum length allowed for a cookie.
+ return str.length <= 4096;
+};
+
+exports.readCookies = function(req){
+ // Extracts the cookies from a request object.
+ var cookie = req.headers.cookie;
+ if(!cookie){
+ return {};
+ }
+ var parts = cookie.split('; ').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){
+ // Reads the session data stored in the cookie named 'key' if it validates,
+ // otherwise returns an empty object.
+
+ var cookies = exports.readCookies(req);
+ if(cookies[key]){
+ return exports.deserialize(secret, timeout, cookies[key]);
+ }
+ return {};
+};
+
+exports.filter = 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
+ };
+ var s = extend(default_settings, settings);
+ if(!s.secret) throw new Error('No secret set in cookie-session settings');
+
+ return function(req, res, next){
+
+ // Read session data from a request and store it in req.session
+ req.session = exports.readSession(
+ s.session_key, s.secret, s.timeout, req);
@dvv
dvv May 20, 2011

if readSession throws (on invalid cookie), user either sees the stack dump, or misterious error 500. Wouldn't it be better to just remove erroneous cookie and let the rest of middleware work? For that, either readSession shouldn't throw (providing catching exceptions in async environment is rather hard task, it has sense), or here should be guarded by try/catch. What do you think?

+
+ // proxy writeHead to add cookie to response
+ var _writeHead = req.writeHead;
+ req.writeHead = function(headers){
+
+ // Add a Set-Cookie header to all responses with the session data
+ // and the current timestamp. The cookie needs to be set on every
+ // response so that the timestamp is up to date, and the session
+ // does not expire unless the user is inactive.
+ headers['Set-Cookie'] = escape(s.session_key) + '=' +
+ escape(exports.serialize(s.secret, req.session));
+
+ // call the original writeHead on the request
+ return _writeHead.apply(req, Array.prototype.slice.call(arguments));
+ }
+ next();
+
+ };
+};
View
@@ -0,0 +1,22 @@
+#!/usr/local/bin/node
+
+require.paths.push(__dirname);
+require.paths.push(__dirname + '/deps');
+require.paths.push(__dirname + '/lib');
+
+try {
+ var testrunner = require('nodeunit').testrunner;
+}
+catch(e) {
+ var sys = require('sys');
+ sys.puts("Cannot find nodeunit module.");
+ sys.puts("You can download submodules for this project by doing:");
+ sys.puts("");
+ sys.puts(" git submodule init");
+ sys.puts(" git submodule update");
+ sys.puts("");
+ process.exit();
+}
+
+process.chdir(__dirname);
+testrunner.run(['test']);
Oops, something went wrong. Retry.

0 comments on commit 4750436

Please sign in to comment.