diff --git a/lib/cookie-session.js b/lib/cookie-session.js index eec2530..0562e26 100644 --- a/lib/cookie-session.js +++ b/lib/cookie-session.js @@ -3,6 +3,29 @@ var Cookies = require("cookies"); var Proxy = require("node-proxy"); var Handler = require("./ProxyHandler.js"); +function base64urlencode(arg) { + var s = new Buffer(arg).toString('base64'); + s = s.split('=')[0]; // Remove any trailing '='s + s = s.replace(/\+/g, '-'); // 62nd char of encoding + s = s.replace(/\//g, '_'); // 63rd char of encoding + // TODO optimize this; we can do much better + return s; +} + +function base64urldecode(arg) { + var s = arg; + s = s.replace(/-/g, '+'); // 62nd char of encoding + s = s.replace(/_/g, '/'); // 63rd char of encoding + switch (s.length % 4) // Pad with trailing '='s + { + case 0: break; // No pad chars in this case + case 2: s += "=="; break; // Two pad chars + case 3: s += "="; break; // One pad char + default: throw new InputException("Illegal base64url string!"); + } + return new Buffer(s, 'base64').toString('ascii'); // Standard base64 decoder +} + /* * Session object * @@ -14,6 +37,7 @@ function Session(req, res, cookies, opts) { this.cookies = cookies; this.opts = opts; this.content = {}; + this.loaded = false; } Session.prototype = { @@ -26,7 +50,18 @@ Session.prototype = { }, updateCookie: function() { - this.cookies.set(this.opts.cookieName, JSON.stringify(this.content)); + this.cookies.set(this.opts.cookieName, base64urlencode(JSON.stringify(this.content))); + }, + + loadFromCookie: function() { + // XXX ummm, sig verify? + var cookie = this.cookies.get(this.opts.cookieName); + if (cookie) { + this.content = JSON.parse(base64urldecode(cookie)); + } else { + this.content = {}; + } + this.loaded = true; }, // called to create a proxy that monitors the session @@ -37,10 +72,13 @@ Session.prototype = { // all values from content except special values sessionHandler.get = function(rcvr, name) { - if (['clear', 'setExpires', 'updateCookie'].indexOf(name) > -1) + if (['clear', 'setExpires', 'updateCookie', 'loadFromCookie'].indexOf(name) > -1) { return this.target[name].bind(this.target); - else + } else { + if (!this.target.loaded) + this.target.loadFromCookie(); return this.target.content[name]; + } }; // set all values to content diff --git a/test/all-test.js b/test/all-test.js index 9d114ea..17f800a 100644 --- a/test/all-test.js +++ b/test/all-test.js @@ -15,7 +15,7 @@ var middleware = cookieSessions({ var suite = vows.describe('all'); suite.addBatch({ - "request object" : { + "a single request object" : { topic: function() { var self = this; @@ -23,7 +23,6 @@ suite.addBatch({ var app = express.createServer(); app.use(middleware); app.get("/foo", function(req, res) { - console.log("yay"); self.callback(null, req); res.send("hello"); }); @@ -52,4 +51,35 @@ suite.addBatch({ } }); +suite.addBatch({ + "across two requests" : { + topic: function() { + var self = this; + + // simple app + var app = express.createServer(); + app.use(middleware); + app.get("/foo", function(req, res) { + req.session.clear(); + req.session.foo = 'foobar'; + req.session.bar = [1, 2, 3]; + res.send("foo"); + }); + + app.get("/bar", function(req, res) { + self.callback(null, req); + res.send("bar"); + }); + + var browser = tobi.createBrowser(app); + browser.get("/foo", function(res, $) {}); + browser.get("/bar", function(res, $) {}); + }, + "session maintains state": function(err, req) { + assert.equal(req.session.foo, 'foobar'); + assert.equal(req.session.bar, [1,2,3]); + } + } +}); + suite.export(module); \ No newline at end of file