function clientSessionFactory(opts) {
if (!opts) {
throw new Error("no options provided, some are required");
}
if (!(opts.secret || (opts.encryptionKey && opts.signatureKey))) {
throw new Error("cannot set up sessions without a secret "+
"or encryptionKey/signatureKey pair");
}
// defaults
opts.cookieName = opts.cookieName || "session_state";
opts.duration = opts.duration || 24*60*60*1000;
opts.activeDuration = 'activeDuration' in opts ?
opts.activeDuration : ACTIVE_DURATION;
var encAlg = opts.encryptionAlgorithm || DEFAULT_ENCRYPTION_ALGO;
encAlg = encAlg.toLowerCase();
if (!ENCRYPTION_ALGORITHMS[encAlg]) {
throw new Error('invalid encryptionAlgorithm, supported are: '+
Object.keys(ENCRYPTION_ALGORITHMS).join(', '));
}
opts.encryptionAlgorithm = encAlg;
var sigAlg = opts.signatureAlgorithm || DEFAULT_SIGNATURE_ALGO;
sigAlg = sigAlg.toLowerCase();
if (!SIGNATURE_ALGORITHMS[sigAlg]) {
throw new Error('invalid signatureAlgorithm, supported are: '+
Object.keys(SIGNATURE_ALGORITHMS).join(', '));
}
opts.signatureAlgorithm = sigAlg;
// set up cookie defaults
opts.cookie = opts.cookie || {};
if (typeof opts.cookie.httpOnly === 'undefined') {
opts.cookie.httpOnly = true;
}
// let's not default to secure just yet,
// as this depends on the socket being secure,
// which is tricky to determine if proxied.
/*
if (typeof(opts.cookie.secure) == 'undefined')
opts.cookie.secure = true;
*/
setupKeys(opts);
keyConstraints(opts);
const propertyName = opts.requestKey || opts.cookieName;
return function clientSession(req, res, next) {
if (propertyName in req) {
return next(); //self aware
}
var cookies = new Cookies(req, res);
var rawSession;
try {
rawSession = new Session(req, res, cookies, opts);
} catch (x) {
// this happens only if there's a big problem
process.nextTick(function() {
next(new Error("client-sessions error: " + x.toString()));
});
return;
}
Object.defineProperty(req, propertyName, {
get: function getSession() {
return rawSession.content;
},
set: function setSession(value) {
if (isObject(value)) {
rawSession.content = value;
} else {
throw new TypeError("cannot set client-session to non-object");
}
}
});
var writeHead = res.writeHead;
res.writeHead = function () {
rawSession.updateCookie();
return writeHead.apply(res, arguments);
};
next();
};
}
n/a
function computeHmac(opts, iv, ciphertext, duration, createdAt) { var hmacAlg = hmacInit(opts.signatureAlgorithm, opts.signatureKey); hmacAlg.update(iv); hmacAlg.update("."); hmacAlg.update(ciphertext); hmacAlg.update("."); hmacAlg.update(createdAt.toString()); hmacAlg.update("."); hmacAlg.update(duration.toString()); return hmacAlg.digest(); }
...
signatureKey: sixtyFourByteKey
};
var iv = new Buffer('01234567890abcdef','binary'); // 128-bits
var ciphertext = new Buffer('0123456789abcdef0123','binary');
var duration = 876543210;
var createdAt = 1234567890;
return cookieSessions.util.computeHmac(
opts, iv, ciphertext, duration, createdAt
).toString('base64');
};
block['equals test vector'] = function(val) {
assert.equal(val, HMAC_EXPECT[algo]);
};
...
function decode(opts, content) { if (!opts.cookieName) { throw new Error("cookieName option required"); } // stop at any time if there's an issue var components = content.split("."); if (components.length !== 5) { return; } setupKeys(opts); var iv; var ciphertext; var hmac; try { iv = base64urldecode(components[0]); ciphertext = base64urldecode(components[1]); hmac = base64urldecode(components[4]); } catch (ignored) { cleanup(); return; } var createdAt = parseInt(components[2], 10); var duration = parseInt(components[3], 10); function cleanup() { if (iv) { zeroBuffer(iv); } if (ciphertext) { zeroBuffer(ciphertext); } if (hmac) { zeroBuffer(hmac); } if (expectedHmac) { // declared below zeroBuffer(expectedHmac); } } // make sure IV is right length if (iv.length !== 16) { cleanup(); return; } // check hmac var expectedHmac = computeHmac(opts, iv, ciphertext, duration, createdAt); if (!constantTimeEquals(hmac, expectedHmac)) { cleanup(); return; } // decrypt var cipher = crypto.createDecipheriv( opts.encryptionAlgorithm, opts.encryptionKey, iv ); var plaintext = cipher.update(ciphertext, 'binary', 'utf8'); plaintext += cipher.final('utf8'); var cookieName = plaintext.substring(0, plaintext.indexOf(COOKIE_NAME_SEP)); if (cookieName !== opts.cookieName) { cleanup(); return; } var result; try { result = { content: JSON.parse( plaintext.substring(plaintext.indexOf(COOKIE_NAME_SEP) + 1) ), createdAt: createdAt, duration: duration }; } catch (ignored) { } cleanup(); return result; }
...
"encode " : function(err, req){
var result = cookieSessions.util.encode({cookieName: 'session', secret: 'yo'}, {foo:'bar'});
var result_arr = result.split(".");
assert.equal(result_arr.length, 5);
},
"encode and decode - is object" : function(err, req){
var encoded = cookieSessions.util.encode({cookieName: 'session', secret: 'yo'}, {foo:'bar'});
var decoded = cookieSessions.util.decode({cookieName: 'session', secret:
x27;yo'}, encoded);
assert.isObject(decoded);
},
"encode and decode - has all values" : function(err, req){
var encoded = cookieSessions.util.encode({cookieName: 'session', secret: 'yo'}, {foo:'bar', bar:
x27;foo'});
var decoded = cookieSessions.util.decode({cookieName: 'session', secret: 'yo'}, encoded);
assert.equal(decoded.content.foo, 'bar');
assert.equal(decoded.content.bar, 'foo');
...
function encode(opts, content, duration, createdAt){ // format will be: // iv.ciphertext.createdAt.duration.hmac if (!opts.cookieName) { throw new Error('cookieName option required'); } else if (String(opts.cookieName).indexOf(COOKIE_NAME_SEP) !== -1) { throw new Error('cookieName cannot include "="'); } setupKeys(opts); duration = duration || 24*60*60*1000; createdAt = createdAt || new Date().getTime(); // generate iv var iv = crypto.randomBytes(16); // encrypt with encryption key var plaintext = new Buffer( opts.cookieName + COOKIE_NAME_SEP + JSON.stringify(content), 'utf8' ); var cipher = crypto.createCipheriv( opts.encryptionAlgorithm, opts.encryptionKey, iv ); var ciphertextStart = forceBuffer(cipher.update(plaintext)); zeroBuffer(plaintext); var ciphertextEnd = forceBuffer(cipher.final()); var ciphertext = Buffer.concat([ciphertextStart, ciphertextEnd]); zeroBuffer(ciphertextStart); zeroBuffer(ciphertextEnd); // hmac it var hmac = computeHmac(opts, iv, ciphertext, duration, createdAt); var result = [ base64urlencode(iv), base64urlencode(ciphertext), createdAt, duration, base64urlencode(hmac) ].join('.'); zeroBuffer(iv); zeroBuffer(ciphertext); zeroBuffer(hmac); return result; }
...
var browser = createBrowser(app);
browser.get("/foo", function(res, $) {
browser.done();
});
},
"encode " : function(err, req){
var result = cookieSessions.util.encode({cookieName: 'session', secret:
x27;yo'}, {foo:'bar'});
var result_arr = result.split(".");
assert.equal(result_arr.length, 5);
},
"encode and decode - is object" : function(err, req){
var encoded = cookieSessions.util.encode({cookieName: 'session', secret: 'yo'}, {foo:'bar'});
var decoded = cookieSessions.util.decode({cookieName: 'session', secret: 'yo'}, encoded);
assert.isObject(decoded);
...