restructured tests to reduce crazy concurrency, also no sharing of express apps between tobis, tobis tend to be jealous, and added check that only single cookie is set, fixed bug where reset() was setting cookie
This commit is contained in:
Родитель
ed7e6b0f8b
Коммит
40b0cb2306
|
@ -37,14 +37,15 @@ function Session(req, res, cookies, opts) {
|
|||
this.res = res;
|
||||
this.cookies = cookies;
|
||||
this.opts = opts;
|
||||
this.content = {};
|
||||
this.loaded = false;
|
||||
this.dirty = false;
|
||||
|
||||
// support for maxAge
|
||||
if (opts.cookie.maxAge) {
|
||||
this.expires = new Date(new Date().getTime() + opts.cookie.maxAge);
|
||||
}
|
||||
|
||||
this.content = {};
|
||||
this.loaded = false;
|
||||
this.dirty = false;
|
||||
this.createdAt = new Date().getTime();
|
||||
}
|
||||
|
||||
Session.prototype = {
|
||||
|
@ -61,14 +62,15 @@ Session.prototype = {
|
|||
|
||||
reset: function(keysToPreserve) {
|
||||
this.clearContent(keysToPreserve);
|
||||
this.createdAt = new Date().getTime();
|
||||
this.dirty = true;
|
||||
this.updateCookie();
|
||||
},
|
||||
|
||||
// take the content and do the encrypt-and-sign
|
||||
// boxing builds in the concept of createdAt
|
||||
box: function() {
|
||||
// format will be:
|
||||
// iv.ciphertext.hmac
|
||||
// iv.ciphertext.createdAt.hmac
|
||||
|
||||
// generate iv
|
||||
var iv = crypto.randomBytes(16).toString('binary');
|
||||
|
@ -81,10 +83,13 @@ Session.prototype = {
|
|||
// hmac it
|
||||
var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
|
||||
hmacAlg.update(iv);
|
||||
hmacAlg.update(ciphertext);
|
||||
hmacAlg.update(".");
|
||||
hmacAlg.update(ciphertext);
|
||||
hmacAlg.update(".");
|
||||
hmacAlg.update(this.createdAt.toString());
|
||||
var hmac = hmacAlg.digest();
|
||||
|
||||
return base64urlencode(iv) + "." + base64urlencode(ciphertext) + "." +base64urlencode(hmac);
|
||||
return base64urlencode(iv) + "." + base64urlencode(ciphertext) + "." + this.createdAt + "." + base64urlencode(hmac);
|
||||
},
|
||||
|
||||
unbox: function(content) {
|
||||
|
@ -93,24 +98,28 @@ Session.prototype = {
|
|||
// stop at any time if there's an issue
|
||||
|
||||
var components = content.split(".");
|
||||
if (components.length != 3)
|
||||
return;
|
||||
if (components.length != 4)
|
||||
return false;
|
||||
|
||||
var iv = base64urldecode(components[0]);
|
||||
var ciphertext = base64urldecode(components[1]);
|
||||
var hmac = base64urldecode(components[2]);
|
||||
var createdAt = parseInt(components[2]);
|
||||
var hmac = base64urldecode(components[3]);
|
||||
|
||||
// make sure IV is right length
|
||||
if (iv.length != 16)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// check hmac
|
||||
var hmacAlg = crypto.createHmac('sha256', this.opts.signatureKey);
|
||||
hmacAlg.update(iv);
|
||||
hmacAlg.update(".");
|
||||
hmacAlg.update(ciphertext);
|
||||
hmacAlg.update(".");
|
||||
hmacAlg.update(createdAt.toString());
|
||||
var expected_hmac = hmacAlg.digest();
|
||||
if (hmac != expected_hmac)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// decrypt
|
||||
var cipher = crypto.createDecipheriv('aes256', this.opts.encryptionKey, iv);
|
||||
|
@ -124,8 +133,12 @@ Session.prototype = {
|
|||
self.content[k] = new_content[k];
|
||||
});
|
||||
} catch (x) {
|
||||
// do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
// all is well, accept creation time
|
||||
this.createdAt = createdAt;
|
||||
return true;
|
||||
},
|
||||
|
||||
updateCookie: function() {
|
||||
|
|
280
test/all-test.js
280
test/all-test.js
|
@ -6,14 +6,21 @@ var vows = require("vows"),
|
|||
tobi = require("tobi"),
|
||||
Browser = require("zombie");
|
||||
|
||||
// set up the session middleware
|
||||
var middleware = cookieSessions({
|
||||
cookieName: 'session',
|
||||
secret: 'yo',
|
||||
cookie: {
|
||||
maxAge: 5000
|
||||
}
|
||||
});
|
||||
function create_app() {
|
||||
// set up the session middleware
|
||||
var middleware = cookieSessions({
|
||||
cookieName: 'session',
|
||||
secret: 'yo',
|
||||
cookie: {
|
||||
maxAge: 5000
|
||||
}
|
||||
});
|
||||
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
var suite = vows.describe('all');
|
||||
|
||||
|
@ -22,14 +29,12 @@ suite.addBatch({
|
|||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
var app = create_app();
|
||||
app.get("/foo", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("hello");
|
||||
});
|
||||
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {});
|
||||
},
|
||||
|
@ -73,8 +78,8 @@ suite.addBatch({
|
|||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
var app = create_app();
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.foo = 'foobar';
|
||||
res.send("hello");
|
||||
|
@ -88,6 +93,9 @@ suite.addBatch({
|
|||
"includes a set-cookie header": function(err, res) {
|
||||
assert.isArray(res.headers['set-cookie']);
|
||||
},
|
||||
"only one set-cookie header": function(err, res) {
|
||||
assert.equal(res.headers['set-cookie'].length, 1);
|
||||
},
|
||||
"with an expires attribute": function(err, res) {
|
||||
assert.match(res.headers['set-cookie'][0], /expires/);
|
||||
},
|
||||
|
@ -106,8 +114,8 @@ suite.addBatch({
|
|||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
var app = create_app();
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.reset();
|
||||
req.session.foo = 'foobar';
|
||||
|
@ -142,8 +150,8 @@ suite.addBatch({
|
|||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
var app = create_app();
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.reset();
|
||||
req.session.foo = 'foobar';
|
||||
|
@ -182,8 +190,8 @@ suite.addBatch({
|
|||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(middleware);
|
||||
var app = create_app();
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.foo = 'foobar';
|
||||
res.send("foo");
|
||||
|
@ -208,103 +216,169 @@ suite.addBatch({
|
|||
});
|
||||
|
||||
suite.addBatch({
|
||||
"short duration" : {
|
||||
"writing to a session" : {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(cookieSessions({
|
||||
cookieName: 'session',
|
||||
secret: 'yo',
|
||||
duration: 500 // 0.5 seconds
|
||||
}));
|
||||
|
||||
var app = create_app();
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.reset();
|
||||
req.session.foo = 'foobar';
|
||||
res.send("foo");
|
||||
});
|
||||
|
||||
return app;
|
||||
},
|
||||
"querying within the duration time": {
|
||||
topic: function(app) {
|
||||
var self = this;
|
||||
|
||||
app.get("/bar", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
browser.get("/bar", function(res, $) {
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
},
|
||||
"session still has state": function(err, req) {
|
||||
assert.equal(req.session.foo, 'foobar');
|
||||
}
|
||||
},
|
||||
"querying outside the duration time": {
|
||||
topic: function(app) {
|
||||
var self = this;
|
||||
app.get("/bar", function(req, res) {
|
||||
req.session.reset();
|
||||
req.session.reset();
|
||||
req.session.bar = 'bar';
|
||||
req.session.baz = 'baz';
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
app.get("/bar2", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar2");
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {
|
||||
browser.get("/bar", function(res, $) {
|
||||
// observe the response to the second request
|
||||
self.callback(null, res);
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
browser.get("/bar2", function(res, $) {
|
||||
});
|
||||
}, 800);
|
||||
});
|
||||
},
|
||||
"session no longer has state": function(err, req) {
|
||||
assert.isUndefined(req.session.foo);
|
||||
}
|
||||
});
|
||||
},
|
||||
"querying twice, each at 3/4 duration time": {
|
||||
topic: function(app) {
|
||||
var self = this;
|
||||
|
||||
app.get("/bar3", function(req, res) {
|
||||
req.session.baz = Math.random();
|
||||
res.send("bar3");
|
||||
});
|
||||
|
||||
app.get("/bar4", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar4");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
// first query resets the session to full duration
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
// this query should NOT reset the session
|
||||
browser.get("/bar3", function(res, $) {
|
||||
setTimeout(function () {
|
||||
// so the session should be dead by now
|
||||
browser.get("/bar4", function(res, $) {
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
},
|
||||
"session no longer has state": function(err, req) {
|
||||
assert.isUndefined(req.session.baz);
|
||||
}
|
||||
"sets a cookie": function(err, res) {
|
||||
assert.isArray(res.headers['set-cookie']);
|
||||
},
|
||||
"and only one cookie": function(err, res) {
|
||||
assert.equal(res.headers['set-cookie'].length, 1);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function create_app_with_duration() {
|
||||
// simple app
|
||||
var app = express.createServer();
|
||||
app.use(cookieSessions({
|
||||
cookieName: 'session',
|
||||
secret: 'yo',
|
||||
duration: 500 // 0.5 seconds
|
||||
}));
|
||||
|
||||
app.get("/foo", function(req, res) {
|
||||
req.session.reset();
|
||||
req.session.foo = 'foobar';
|
||||
res.send("foo");
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
suite.addBatch({
|
||||
"querying within duration" : {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
var app = create_app_with_duration();
|
||||
app.get("/bar", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
browser.get("/bar", function(res, $) {
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
},
|
||||
"session still has state": function(err, req) {
|
||||
assert.equal(req.session.foo, 'foobar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
suite.addBatch({
|
||||
"modifying the session": {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
var app = create_app_with_duration();
|
||||
app.get("/bar", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
var firstCreatedAt, secondCreatedAt;
|
||||
browser.get("/foo", function(res, $) {
|
||||
browser.get("/bar", function(res, $) {
|
||||
});
|
||||
});
|
||||
},
|
||||
"doesn't change createdAt": function(err, req) {
|
||||
assert.equal(req.session.foo, 'foobar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
suite.addBatch({
|
||||
"querying outside the duration time": {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
var app = create_app_with_duration();
|
||||
app.get("/bar", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
browser.get("/bar", function(res, $) {
|
||||
});
|
||||
}, 800);
|
||||
});
|
||||
},
|
||||
"session no longer has state": function(err, req) {
|
||||
assert.isUndefined(req.session.foo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
suite.addBatch({
|
||||
"querying twice, each at 3/4 duration time": {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
|
||||
var app = create_app_with_duration();
|
||||
app.get("/bar", function(req, res) {
|
||||
req.session.baz = Math.random();
|
||||
res.send("bar");
|
||||
});
|
||||
|
||||
app.get("/bar2", function(req, res) {
|
||||
self.callback(null, req);
|
||||
res.send("bar2");
|
||||
});
|
||||
|
||||
var browser = tobi.createBrowser(app);
|
||||
// first query resets the session to full duration
|
||||
browser.get("/foo", function(res, $) {
|
||||
setTimeout(function () {
|
||||
// this query should NOT reset the session
|
||||
browser.get("/bar", function(res, $) {
|
||||
setTimeout(function () {
|
||||
// so the session should be dead by now
|
||||
browser.get("/bar2", function(res, $) {
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
},
|
||||
"session no longer has state": function(err, req) {
|
||||
assert.isUndefined(req.session.baz);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче