Socket
Socket
Sign inDemoInstall

client-sessions

Package Overview
Dependencies
2
Maintainers
3
Versions
19
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.3.1 to 0.4.0

89

lib/client-sessions.js

@@ -6,3 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public

var Cookies = require("cookies");
var Proxy = require("node-proxy");
var Proxy_ = (typeof Proxy !== 'undefined') ? Proxy : require("node-proxy");
var Handler = require("./ProxyHandler.js");

@@ -12,5 +12,6 @@ var crypto = require("crypto");

const COOKIE_NAME_SEP = '=';
const ACTIVE_DURATION = 1000 * 60 * 5;
function base64urlencode(arg) {
var s = new Buffer(arg).toString('base64');
var s = arg.toString('base64');
s = s.split('=')[0]; // Remove any trailing '='s

@@ -44,2 +45,14 @@ s = s.replace(/\+/g, '-'); // 62nd char of encoding

function constantTimeEquals(a, b) {
// Ideally this would be a native function, so it's less sensitive to how the
// JS engine might optimize.
if (a.length != b.length)
return false;
var ret = 0;
for (var i = 0; i < a.length; i++) {
ret |= a.readUInt8(i) ^ b.readUInt8(i);
}
return ret == 0;
}
function encode(opts, content, duration, createdAt){

@@ -74,2 +87,4 @@ // format will be:

ciphertext += cipher.final('binary');
// Before 0.10, crypto returns binary-encoded strings. Remove when
// dropping 0.8 support.
ciphertext = new Buffer(ciphertext, 'binary');

@@ -88,2 +103,5 @@

var hmac = hmacAlg.digest();
// Before 0.10, crypto returns binary-encoded strings. Remove when
// dropping 0.8 support.
hmac = new Buffer(hmac, 'binary');

@@ -133,4 +151,7 @@ return base64urlencode(iv) + "." + base64urlencode(ciphertext) + "." + createdAt + "." + duration + "." + base64urlencode(hmac);

var expected_hmac = hmacAlg.digest();
// Before 0.10, crypto returns binary-encoded strings. Remove when
// dropping 0.8 support.
expected_hmac = new Buffer(expected_hmac, 'binary');
if (hmac.toString('utf8') != expected_hmac.toString('utf8'))
if (!constantTimeEquals(hmac, expected_hmac))
return;

@@ -169,5 +190,4 @@

this.opts = opts;
// support for maxAge
if (opts.cookie.maxAge) {
this.expires = new Date(new Date().getTime() + opts.cookie.maxAge);
if (opts.cookie.ephemeral && opts.cookie.maxAge) {
throw new Error("you cannot have an ephemeral cookie with a maxAge.");
}

@@ -183,3 +203,11 @@

this.duration = opts.duration;
this.activeDuration = opts.activeDuration;
// support for maxAge
if (opts.cookie.maxAge) {
this.expires = new Date(new Date().getTime() + opts.cookie.maxAge);
} else {
this.updateDefaultExpires();
}
// here, we check that the security bits are set correctly

@@ -192,2 +220,15 @@ var secure = res.socket.encrypted || req.connection.proxySecure;

Session.prototype = {
updateDefaultExpires: function() {
if (this.opts.cookie.maxAge) return;
if (this.opts.cookie.ephemeral) {
this.expires = null;
} else {
var time = this.createdAt || new Date().getTime();
// the cookie should expire when it becomes invalid
// we add an extra second because the conversion to a date truncates the milliseconds
this.expires = new Date(time + this.duration + 1000);
}
},
clearContent: function(keysToPreserve) {

@@ -208,2 +249,3 @@ var self = this;

this.duration = this.opts.duration;
this.updateDefaultExpires();
this.dirty = true;

@@ -213,3 +255,6 @@ this.loaded = true;

setDuration: function(newDuration) {
setDuration: function(newDuration, ephemeral) {
if (ephemeral && this.opts.cookie.maxAge) {
throw new Error("you cannot have an ephemeral cookie with a maxAge.");
}
if (!this.loaded)

@@ -220,2 +265,4 @@ this.loadFromCookie(true);

this.createdAt = new Date().getTime();
this.opts.cookie.ephemeral = ephemeral;
this.updateDefaultExpires();
},

@@ -243,2 +290,3 @@

this.duration = unboxed.duration;
this.updateDefaultExpires();
},

@@ -248,6 +296,4 @@

if (this.dirty) {
// support for expires
if (this.expires) {
this.opts.cookie.expires = this.expires;
}
// support for adding/removing cookie expires
this.opts.cookie.expires = this.expires;

@@ -268,5 +314,13 @@ try {

var expiresAt = this.createdAt + this.duration;
var now = Date.now();
// should we reset this session?
if ((this.createdAt + this.duration) < new Date().getTime())
if (expiresAt < now)
this.reset();
// if expiration is soon, push back a few minutes to not interrupt user
else if (expiresAt - now < this.activeDuration) {
this.createdAt += this.activeDuration;
this.dirty = true;
this.updateDefaultExpires();
}
} else {

@@ -331,3 +385,3 @@ if (force_reset) {

var proxySession = Proxy.create(sessionHandler);
var proxySession = Proxy_.create(sessionHandler);
return proxySession;

@@ -349,2 +403,3 @@ }

opts.duration = opts.duration || 24*60*60*1000;
opts.activeDuration = 'activeDuration' in opts ? opts.activeDuration : ACTIVE_DURATION;

@@ -368,3 +423,9 @@ // set up cookie defaults

const propertyName = opts.requestKey || opts.cookieName;
return function(req, res, next) {
if (req[propertyName]) {
return next(); //self aware
}
var cookies = new Cookies(req, res);

@@ -380,3 +441,3 @@ var raw_session;

req[opts.requestKey || opts.cookieName] = raw_session.monitor();
req[propertyName] = raw_session.monitor();

@@ -383,0 +444,0 @@ res.on('header', function() {

@@ -0,0 +0,0 @@

14

package.json
{
"name" : "client-sessions",
"version" : "0.3.1",
"private" : false,
"version" : "0.4.0",
"description" : "secure sessions stored in cookies",

@@ -12,7 +11,7 @@ "main" : "lib/client-sessions",

"dependencies" : {
"cookies" : "0.2.1",
"cookies" : "0.3.6",
"node-proxy": "0.6.0"
},
"devDependencies": {
"vows": "0.5.13",
"vows": "0.7.0",
"express": "2.5.0",

@@ -31,3 +30,8 @@ "tobi": "https://github.com/Cowboy-coder/tobi/tarball/fd733a3",

"node": ">= 0.8.0"
}
},
"licenses": {
"type": "MPL 2.0",
"url": "https://raw.github.com/mozilla/node-client-sessions/master/LICENSE"
},
"bugs": "https://github.com/mozilla/node-client-sessions/issues"
}

@@ -18,2 +18,3 @@ [![build status](https://secure.travis-ci.org/mozilla/node-client-sessions.png)](http://travis-ci.org/mozilla/node-client-sessions)

duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms
activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
}));

@@ -40,2 +41,4 @@

path: '/api', // cookie will only be sent to requests under '/api'
maxAge: 60000, // duration of the cookie in milliseconds, defaults to duration above
ephemeral: false, // when true, cookie expires when the browser closes
httpOnly: true, // when true, cookie is not accessible from javascript

@@ -46,3 +49,3 @@ secure: false // when true, cookie will only be sent over SSL

Finally, you can have multiple cookies:
You can have multiple cookies:

@@ -65,2 +68,22 @@ // a 1 week session

Finally, you can use requestKey to force the name where information can be accessed on the request object.
var sessions = require("client-sessions");
app.use(sessions({
cookieName: 'mySession',
requestKey: 'forcedSessionKey', // requestKey overrides cookieName for the key name added to the request object.
secret: 'blargadeeblargblarg', // should be a large unguessable string
duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms
}));
app.use(function(req, res, next) {
// requestKey forces the session information to be
// accessed via forcedSessionKey
if (req.forcedSessionKey.seenyou) {
res.setHeader('X-Seen-You', 'true');
}
next();
});
## License

@@ -67,0 +90,0 @@

@@ -19,2 +19,3 @@ // a NODE_ENV of test will supress console output to stderr which

secret: 'yo',
activeDuration: 0,
cookie: {

@@ -32,2 +33,3 @@ maxAge: 5000

secret: 'yo',
activeDuration: 0,
cookie: {

@@ -334,2 +336,3 @@ maxAge: 5000

secret: 'yo',
activeDuration: 0,
duration: 500 // 0.5 seconds

@@ -422,3 +425,3 @@ }));

suite.addBatch({
"querying twice, each at 3/4 duration time": {
"querying twice, each at 2/5 duration time": {
topic: function() {

@@ -445,2 +448,39 @@ var self = this;

setTimeout(function () {
// so the session should still be valid
browser.get("/bar2", function(res, $) {
});
}, 200);
});
}, 200);
});
},
"session still has state": function(err, req) {
assert.isDefined(req.session.baz);
}
}
});
suite.addBatch({
"querying twice, each at 3/5 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

@@ -467,2 +507,3 @@ browser.get("/bar2", function(res, $) {

secret: 'yobaby',
activeDuration: 0,
duration: 5000 // 5.0 seconds

@@ -651,2 +692,3 @@ }));

secret: 'yo',
activeDuration: 0,
cookie: {

@@ -760,2 +802,11 @@ maxAge: 5000,

assert.equal(decoded.duration, 86400000);
},
"encode and decode - tampered HMAC" : function(err, req){
var encodedReal = 'LVB3G2lnPF75RzsT9mz7jQ.RT1Lcq0dOJ_DMRHyWJ4NZPjBXr2WzkFcUC4NO78gbCQ.1371704898483.5000.ILEusgnajT1sqCWLuzaUt-HFn2KPjYNd38DhI7aRCb9';
var encodedFake = encodedReal.substring(0, encodedReal.length - 1) + 'A';
var decodedReal = cookieSessions.util.decode({cookieName: 'session', secret: 'yo'}, encodedReal);
assert.isObject(decodedReal);
var decodedFake = cookieSessions.util.decode({cookieName: 'session', secret: 'yo'}, encodedFake);
assert.isUndefined(decodedFake);
}

@@ -799,2 +850,3 @@ }

cookieName: 'ooga_booga_momma',
activeDuration: 0,
requestKey: 'ses',

@@ -805,3 +857,3 @@ secret: 'yo'

app.get('/foo', function(req, res) {
self.callback(null, req)
self.callback(null, req);
});

@@ -871,2 +923,216 @@

suite.addBatch({
"missing cookie maxAge": {
topic: function() {
var self = this;
var app = express.createServer();
app.use(cookieSessions({
cookieName: 'session',
duration: 50000,
activeDuration: 0,
secret: 'yo'
}));
app.get("/foo", function(req, res) {
req.session.foo = 'foobar';
res.send("hello");
});
var browser = tobi.createBrowser(app);
browser.get("/foo", function(res, $) {
self.callback(null, res);
});
},
"still has an expires attribute": function(err, res) {
assert.match(res.headers['set-cookie'][0], /expires/, "cookie is a session cookie");
},
"which roughly matches the session duration": function(err, res) {
var expiryValue = res.headers['set-cookie'][0].replace(/^.*expires=([^;]+);.*$/, "$1");
var expiryDate = new Date(expiryValue);
var cookieDuration = expiryDate.getTime() - Date.now();
assert(Math.abs(50000 - cookieDuration) < 1500, "expiry is pretty far from the specified duration");
}
},
"changing the duration": {
topic: function() {
var self = this;
var app = express.createServer();
app.use(cookieSessions({
cookieName: 'session',
duration: 500,
activeDuration: 0,
secret: 'yo'
}));
app.get("/foo", function(req, res) {
req.session.foo = 'foobar';
res.send("hello");
});
app.get("/bar", function(req, res) {
req.session.setDuration(5000);
res.send("bar");
});
var browser = tobi.createBrowser(app);
browser.get("/foo", function(res, $) {
setTimeout(function () {
browser.get("/bar", function(res, $) {
self.callback(null, res);
});
}, 200);
});
},
"updates the cookie expiry": function(err, res) {
var expiryValue = res.headers['set-cookie'][0].replace(/^.*expires=([^;]+);.*$/, "$1");
var expiryDate = new Date(expiryValue);
var cookieDuration = expiryDate.getTime() - Date.now();
assert(Math.abs(cookieDuration - 5000) < 1000, "expiry is pretty far from the specified duration");
}
},
"active user with session close to expiration": {
topic: function() {
var app = express.createServer();
var self = this;
app.use(cookieSessions({
cookieName: 'session',
duration: 300,
activeDuration: 500,
secret: 'yo'
}));
app.get("/foo", function(req, res) {
req.session.foo = 'foobar';
res.send("hello");
});
app.get("/bar", function(req, res) {
req.session.bar = 'baz';
res.send('hi');
});
app.get("/baz", function(req, res) {
res.json({ "msg": req.session.foo + req.session.bar });
});
var browser = tobi.createBrowser(app);
browser.get("/foo", function() {
browser.get("/bar", function() {
setTimeout(function () {
browser.get("/baz", function(res, first) {
setTimeout(function() {
browser.get('/baz', function(res, second) {
self.callback(null, first, second);
});
}, 1000);
});
}, 400);
});
});
},
"extends session duration": function(err, extended, tooLate) {
assert.equal(extended.msg, 'foobarbaz');
assert.equal(tooLate.msg, null);
}
}
});
var shared_browser1;
var shared_browser2;
suite.addBatch({
"non-ephemeral cookie": {
topic: function() {
var self = this;
var app = express.createServer();
app.use(cookieSessions({
cookieName: 'session',
duration: 5000,
secret: 'yo',
cookie: {
ephemeral: false
}
}));
app.get("/foo", function(req, res) {
req.session.foo = 'foobar';
res.send("hello");
});
app.get("/bar", function(req, res) {
req.session.setDuration(6000, true);
res.send("hello");
});
shared_browser1 = tobi.createBrowser(app);
shared_browser1.get("/foo", function(res, $) {
self.callback(null, res);
});
},
"has an expires attribute": function(err, res) {
assert.match(res.headers['set-cookie'][0], /expires/, "cookie is a session cookie");
},
"changing to an ephemeral one": {
topic: function() {
var self = this;
shared_browser1.get("/bar", function(res, $) {
self.callback(null, res);
});
},
"removes its expires attribute": function(err, res) {
assert.strictEqual(res.headers['set-cookie'][0].indexOf('expires='), -1, "cookie is not ephemeral");
}
}
},
"ephemeral cookie": {
topic: function() {
var self = this;
var app = express.createServer();
app.use(cookieSessions({
cookieName: 'session',
duration: 50000,
activeDuration: 0,
secret: 'yo',
cookie: {
ephemeral: true
}
}));
app.get("/foo", function(req, res) {
req.session.foo = 'foobar';
res.send("hello");
});
app.get("/bar", function(req, res) {
req.session.setDuration(6000, false);
res.send("hello");
});
shared_browser2 = tobi.createBrowser(app);
shared_browser2.get("/foo", function(res, $) {
self.callback(null, res);
});
},
"doesn't have an expires attribute": function(err, res) {
assert.strictEqual(res.headers['set-cookie'][0].indexOf('expires='), -1, "cookie is not ephemeral");
},
"changing to an non-ephemeral one": {
topic: function() {
var self = this;
shared_browser2.get("/bar", function(res, $) {
self.callback(null, res);
});
},
"gains an expires attribute": function(err, res) {
assert.match(res.headers['set-cookie'][0], /expires/, "cookie is a session cookie");
}
}
}
});
suite.export(module);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc