mirror of
https://github.com/linuxserver/core.git
synced 2026-02-20 05:07:19 +08:00
Merge pull request +14833 from c9/ide-rotate-session
Ide rotate session
This commit is contained in:
commit
f4f65c9f3d
28
node_modules/connect-architect/connect.session/decrypt.js
generated
vendored
Normal file
28
node_modules/connect-architect/connect.session/decrypt.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
var utils = require("./session/utils");
|
||||
|
||||
function decrypt(secret, rawCookie) {
|
||||
// ensure secret is available or bail
|
||||
if (!secret) throw new Error('`secret` option required for sessions');
|
||||
|
||||
// secret is always an array of secrets
|
||||
secret = [].concat(secret);
|
||||
|
||||
for (var i = 0; i < secret.length; i++) {
|
||||
var unsignedCookie = utils.parseSignedCookie(rawCookie, secret[i]);
|
||||
|
||||
if (unsignedCookie && unsignedCookie !== rawCookie) {
|
||||
var usedSecret = secret[i];
|
||||
|
||||
return {
|
||||
unsignedCookie: unsignedCookie,
|
||||
usedSecret: usedSecret
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
module.exports.decrypt = decrypt;
|
||||
39
node_modules/connect-architect/connect.session/decrypt_test.js
generated
vendored
Normal file
39
node_modules/connect-architect/connect.session/decrypt_test.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*global describe it before after beforeEach afterEach */
|
||||
"use strict";
|
||||
|
||||
"use server";
|
||||
|
||||
require("c9/inline-mocha")(module);
|
||||
require("c9/setup_paths");
|
||||
|
||||
var Cookie = require("cookie");
|
||||
var assert = require("assert");
|
||||
var ConnectCookie = require("./session/cookie");
|
||||
|
||||
var encrypt = require("./encrypt");
|
||||
var decrypt = require("./decrypt");
|
||||
|
||||
describe("decrypt", function() {
|
||||
|
||||
it("Should decrypt when secret is a string", function(){
|
||||
var sessionID = Math.random().toString(36);
|
||||
var secret = Math.random().toString(36);
|
||||
var cookieVal = encrypt(sessionID, "connect.sid", new ConnectCookie({}), secret);
|
||||
var cookie = Cookie.parse(cookieVal);
|
||||
var val = decrypt.decrypt(secret, cookie["connect.sid"]);
|
||||
|
||||
assert.deepEqual(val, { unsignedCookie: sessionID, usedSecret: secret });
|
||||
});
|
||||
|
||||
it("Should decrypt when secret is an array", function(){
|
||||
var sessionID = Math.random().toString(36);
|
||||
var secret = [Math.random().toString(36), Math.random().toString(36), Math.random().toString(36)];
|
||||
var cookieVal = encrypt(sessionID, "connect.sid", new ConnectCookie({}), secret[1]);
|
||||
var cookie = Cookie.parse(cookieVal);
|
||||
var val = decrypt.decrypt(secret, cookie["connect.sid"]);
|
||||
|
||||
assert.deepEqual(val, { unsignedCookie: sessionID, usedSecret: secret[1] });
|
||||
});
|
||||
});
|
||||
12
node_modules/connect-architect/connect.session/encrypt.js
generated
vendored
Normal file
12
node_modules/connect-architect/connect.session/encrypt.js
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
var signature = require("cookie-signature");
|
||||
|
||||
function encrypt(sessionID, key, cookie, secret) {
|
||||
var val = 's:' + signature.sign(sessionID, secret);
|
||||
val = cookie.serialize(key, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
module.exports = encrypt;
|
||||
3
node_modules/connect-architect/connect.session/session-ext.js
generated
vendored
3
node_modules/connect-architect/connect.session/session-ext.js
generated
vendored
@ -1,6 +1,5 @@
|
||||
var Session = require("connect").session;
|
||||
var Session = require("./session");
|
||||
var assert = require("assert");
|
||||
var error = require("http-error");
|
||||
|
||||
module.exports = function startup(options, imports, register) {
|
||||
|
||||
|
||||
338
node_modules/connect-architect/connect.session/session.js
generated
vendored
Normal file
338
node_modules/connect-architect/connect.session/session.js
generated
vendored
Normal file
@ -0,0 +1,338 @@
|
||||
/**
|
||||
* MVH 2016 Forked from connect/middleware/session
|
||||
* - Added support for multiple secrets
|
||||
* - Remove MemoryStore
|
||||
*/
|
||||
/*!
|
||||
* Connect - session
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Session = require('./session/session')
|
||||
, debug = require('debug')('connect:session')
|
||||
, Cookie = require('./session/cookie')
|
||||
, Store = require('./session/store')
|
||||
, utils = require('./session/utils')
|
||||
, uid = require('uid2')
|
||||
, parse = require('url').parse;
|
||||
|
||||
var decrypt = require("./decrypt");
|
||||
var encrypt = require("./encrypt");
|
||||
var hash = require("./session/hash");
|
||||
|
||||
/**
|
||||
* Expose the middleware.
|
||||
*/
|
||||
|
||||
exports = module.exports = session;
|
||||
|
||||
/**
|
||||
* Expose constructors.
|
||||
*/
|
||||
|
||||
exports.Store = Store;
|
||||
exports.Cookie = Cookie;
|
||||
exports.Session = Session;
|
||||
|
||||
/**
|
||||
* Session:
|
||||
*
|
||||
* Setup session store with the given `options`.
|
||||
*
|
||||
* Session data is _not_ saved in the cookie itself, however
|
||||
* cookies are used, so we must use the [cookieParser()](cookieParser.html)
|
||||
* middleware _before_ `session()`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect()
|
||||
* .use(connect.cookieParser())
|
||||
* .use(connect.session({ secret: 'keyboard cat', key: 'sid', cookie: { secure: true }}))
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `key` cookie name defaulting to `connect.sid`
|
||||
* - `store` session store instance
|
||||
* - `secret` session cookie is signed with this secret to prevent tampering
|
||||
* - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
|
||||
* - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
|
||||
*
|
||||
* Cookie option:
|
||||
*
|
||||
* By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set
|
||||
* so the cookie becomes a browser-session cookie. When the user closes the
|
||||
* browser the cookie (and session) will be removed.
|
||||
*
|
||||
* ## req.session
|
||||
*
|
||||
* To store or access session data, simply use the request property `req.session`,
|
||||
* which is (generally) serialized as JSON by the store, so nested objects
|
||||
* are typically fine. For example below is a user-specific view counter:
|
||||
*
|
||||
* connect()
|
||||
* .use(connect.favicon())
|
||||
* .use(connect.cookieParser())
|
||||
* .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
|
||||
* .use(function(req, res, next){
|
||||
* var sess = req.session;
|
||||
* if (sess.views) {
|
||||
* res.setHeader('Content-Type', 'text/html');
|
||||
* res.write('<p>views: ' + sess.views + '</p>');
|
||||
* res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
|
||||
* res.end();
|
||||
* sess.views++;
|
||||
* } else {
|
||||
* sess.views = 1;
|
||||
* res.end('welcome to the session demo. refresh!');
|
||||
* }
|
||||
* }
|
||||
* )).listen(3000);
|
||||
*
|
||||
* ## Session#regenerate()
|
||||
*
|
||||
* To regenerate the session simply invoke the method, once complete
|
||||
* a new SID and `Session` instance will be initialized at `req.session`.
|
||||
*
|
||||
* req.session.regenerate(function(err){
|
||||
* // will have a new session here
|
||||
* });
|
||||
*
|
||||
* ## Session#destroy()
|
||||
*
|
||||
* Destroys the session, removing `req.session`, will be re-generated next request.
|
||||
*
|
||||
* req.session.destroy(function(err){
|
||||
* // cannot access session here
|
||||
* });
|
||||
*
|
||||
* ## Session#reload()
|
||||
*
|
||||
* Reloads the session data.
|
||||
*
|
||||
* req.session.reload(function(err){
|
||||
* // session updated
|
||||
* });
|
||||
*
|
||||
* ## Session#save()
|
||||
*
|
||||
* Save the session.
|
||||
*
|
||||
* req.session.save(function(err){
|
||||
* // session saved
|
||||
* });
|
||||
*
|
||||
* ## Session#touch()
|
||||
*
|
||||
* Updates the `.maxAge` property. Typically this is
|
||||
* not necessary to call, as the session middleware does this for you.
|
||||
*
|
||||
* ## Session#cookie
|
||||
*
|
||||
* Each session has a unique cookie object accompany it. This allows
|
||||
* you to alter the session cookie per visitor. For example we can
|
||||
* set `req.session.cookie.expires` to `false` to enable the cookie
|
||||
* to remain for only the duration of the user-agent.
|
||||
*
|
||||
* ## Session#maxAge
|
||||
*
|
||||
* Alternatively `req.session.cookie.maxAge` will return the time
|
||||
* remaining in milliseconds, which we may also re-assign a new value
|
||||
* to adjust the `.expires` property appropriately. The following
|
||||
* are essentially equivalent
|
||||
*
|
||||
* var hour = 3600000;
|
||||
* req.session.cookie.expires = new Date(Date.now() + hour);
|
||||
* req.session.cookie.maxAge = hour;
|
||||
*
|
||||
* For example when `maxAge` is set to `60000` (one minute), and 30 seconds
|
||||
* has elapsed it will return `30000` until the current request has completed,
|
||||
* at which time `req.session.touch()` is called to reset `req.session.maxAge`
|
||||
* to its original value.
|
||||
*
|
||||
* req.session.cookie.maxAge;
|
||||
* // => 30000
|
||||
*
|
||||
* Session Store Implementation:
|
||||
*
|
||||
* Every session store _must_ implement the following methods
|
||||
*
|
||||
* - `.get(sid, callback)`
|
||||
* - `.set(sid, session, callback)`
|
||||
* - `.destroy(sid, callback)`
|
||||
*
|
||||
* Recommended methods include, but are not limited to:
|
||||
*
|
||||
* - `.length(callback)`
|
||||
* - `.clear(callback)`
|
||||
*
|
||||
* For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function session(options){
|
||||
var options = options || {}
|
||||
, key = options.key || 'connect.sid'
|
||||
, store = options.store
|
||||
, cookie = options.cookie || {}
|
||||
, trustProxy = options.proxy
|
||||
, storeReady = true
|
||||
, rollingSessions = options.rolling || false;
|
||||
|
||||
// generates the new session
|
||||
store.generate = function(req){
|
||||
req.sessionID = uid(24);
|
||||
req.session = new Session(req);
|
||||
req.session.cookie = new Cookie(cookie);
|
||||
};
|
||||
|
||||
store.on('disconnect', function(){ storeReady = false; });
|
||||
store.on('connect', function(){ storeReady = true; });
|
||||
|
||||
return function session(req, res, next) {
|
||||
// self-awareness
|
||||
if (req.session) return next();
|
||||
|
||||
// Handle connection as if there is no session if
|
||||
// the store has temporarily disconnected etc
|
||||
if (!storeReady) return debug('store is disconnected'), next();
|
||||
|
||||
// pathname mismatch
|
||||
var originalPath = parse(req.originalUrl).pathname;
|
||||
if (0 != originalPath.indexOf(cookie.path || '/')) return next();
|
||||
|
||||
// backwards compatibility for signed cookies
|
||||
// req.secret is passed from the cookie parser middleware
|
||||
var secret = options.secret || req.secret;
|
||||
|
||||
// ensure secret is available or bail
|
||||
if (!secret) throw new Error('`secret` option required for sessions');
|
||||
|
||||
// normalize secret to be an array
|
||||
secret = [].concat(secret);
|
||||
|
||||
var originalHash
|
||||
, originalId;
|
||||
|
||||
// expose store
|
||||
req.sessionStore = store;
|
||||
|
||||
// grab the session cookie value and check the signature
|
||||
var rawCookie = req.cookies[key];
|
||||
|
||||
// get signedCookies for backwards compat with signed cookies
|
||||
var unsignedCookie = req.signedCookies[key];
|
||||
var usedSecret;
|
||||
|
||||
if (!unsignedCookie && rawCookie) {
|
||||
var values = decrypt.decrypt(secret, rawCookie);
|
||||
|
||||
usedSecret = values.usedSecret;
|
||||
unsignedCookie = values.unsignedCookie;
|
||||
}
|
||||
|
||||
// set-cookie
|
||||
res.on('header', function(){
|
||||
if (!req.session) return;
|
||||
|
||||
var cookie = req.session.cookie
|
||||
, proto = (req.headers['x-forwarded-proto'] || '').split(',')[0].toLowerCase().trim()
|
||||
, tls = req.connection.encrypted || (trustProxy && 'https' == proto)
|
||||
, isNew = unsignedCookie != req.sessionID;
|
||||
|
||||
// only send secure cookies via https
|
||||
if (cookie.secure && !tls) return debug('not secured');
|
||||
|
||||
var masterSecret = secret[1] || secret[0];
|
||||
|
||||
// in case of rolling session, always reset the cookie
|
||||
if (usedSecret == masterSecret && !rollingSessions) {
|
||||
// browser-session length cookie
|
||||
if (null == cookie.expires) {
|
||||
if (!isNew) return debug('already set browser-session cookie');
|
||||
// compare hashes and ids
|
||||
} else if (originalHash == hash.hash(req.session) && originalId == req.session.id) {
|
||||
return debug('unmodified session');
|
||||
}
|
||||
}
|
||||
|
||||
var val = encrypt(req.sessionID, key, cookie, masterSecret);
|
||||
debug('set-cookie %s', val);
|
||||
res.setHeader('Set-Cookie', val);
|
||||
});
|
||||
|
||||
// proxy end() to commit the session
|
||||
var end = res.end;
|
||||
res.end = function(data, encoding){
|
||||
res.end = end;
|
||||
if (!req.session) return res.end(data, encoding);
|
||||
debug('saving');
|
||||
req.session.resetMaxAge();
|
||||
req.session.save(function(err){
|
||||
if (err) console.error(err.stack);
|
||||
debug('saved');
|
||||
res.end(data, encoding);
|
||||
});
|
||||
};
|
||||
|
||||
// generate the session
|
||||
function generate() {
|
||||
store.generate(req);
|
||||
}
|
||||
|
||||
// get the sessionID from the cookie
|
||||
req.sessionID = unsignedCookie;
|
||||
|
||||
// generate a session if the browser doesn't send a sessionID
|
||||
if (!req.sessionID) {
|
||||
debug('no SID sent, generating session');
|
||||
generate();
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// generate the session object
|
||||
var pause = utils.pause(req);
|
||||
debug('fetching %s', req.sessionID);
|
||||
store.get(req.sessionID, function(err, sess){
|
||||
// proxy to resume() events
|
||||
var _next = next;
|
||||
next = function(err){
|
||||
_next(err);
|
||||
pause.resume();
|
||||
};
|
||||
|
||||
// error handling
|
||||
if (err) {
|
||||
debug('error %j', err);
|
||||
if ('ENOENT' == err.code) {
|
||||
generate();
|
||||
next();
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
// no session
|
||||
} else if (!sess) {
|
||||
debug('no session found');
|
||||
generate();
|
||||
next();
|
||||
// populate req.session
|
||||
} else {
|
||||
debug('session found');
|
||||
store.createSession(req, sess);
|
||||
originalId = req.sessionID;
|
||||
originalHash = hash.hash(sess);
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
128
node_modules/connect-architect/connect.session/session/cookie.js
generated
vendored
Normal file
128
node_modules/connect-architect/connect.session/session/cookie.js
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
|
||||
/*!
|
||||
* Connect - session - Cookie
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./utils')
|
||||
, cookie = require('cookie');
|
||||
|
||||
/**
|
||||
* Initialize a new `Cookie` with the given `options`.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Cookie = module.exports = function Cookie(options) {
|
||||
this.path = '/';
|
||||
this.maxAge = null;
|
||||
this.httpOnly = true;
|
||||
if (options) utils.merge(this, options);
|
||||
this.originalMaxAge = undefined == this.originalMaxAge
|
||||
? this.maxAge
|
||||
: this.originalMaxAge;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Prototype.
|
||||
*/
|
||||
|
||||
Cookie.prototype = {
|
||||
|
||||
/**
|
||||
* Set expires `date`.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set expires(date) {
|
||||
this._expires = date;
|
||||
this.originalMaxAge = this.maxAge;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get expires `date`.
|
||||
*
|
||||
* @return {Date}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get expires() {
|
||||
return this._expires;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set expires via max-age in `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set maxAge(ms) {
|
||||
this.expires = 'number' == typeof ms
|
||||
? new Date(Date.now() + ms)
|
||||
: ms;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get expires max-age in `ms`.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get maxAge() {
|
||||
return this.expires instanceof Date
|
||||
? this.expires.valueOf() - Date.now()
|
||||
: this.expires;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return cookie data object.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
get data() {
|
||||
return {
|
||||
originalMaxAge: this.originalMaxAge
|
||||
, expires: this._expires
|
||||
, secure: this.secure
|
||||
, httpOnly: this.httpOnly
|
||||
, domain: this.domain
|
||||
, path: this.path
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a serialized cookie string.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
serialize: function(name, val){
|
||||
return cookie.serialize(name, val, this.data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return JSON representation of this cookie.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
toJSON: function(){
|
||||
return this.data;
|
||||
}
|
||||
};
|
||||
18
node_modules/connect-architect/connect.session/session/hash.js
generated
vendored
Normal file
18
node_modules/connect-architect/connect.session/session/hash.js
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
var crc32 = require('buffer-crc32');
|
||||
|
||||
/**
|
||||
* Hash the given `sess` object omitting changes
|
||||
* to `.cookie`.
|
||||
*
|
||||
* @param {Object} sess
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function hash(sess) {
|
||||
return crc32.signed(JSON.stringify(sess, function(key, val){
|
||||
if ('cookie' != key) return val;
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports.hash = hash;
|
||||
129
node_modules/connect-architect/connect.session/session/memory.js
generated
vendored
Normal file
129
node_modules/connect-architect/connect.session/session/memory.js
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
|
||||
/*!
|
||||
* Connect - session - MemoryStore
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Store = require('./store');
|
||||
|
||||
/**
|
||||
* Initialize a new `MemoryStore`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var MemoryStore = module.exports = function MemoryStore() {
|
||||
this.sessions = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from `Store.prototype`.
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.__proto__ = Store.prototype;
|
||||
|
||||
/**
|
||||
* Attempt to fetch session by the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.get = function(sid, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
var expires
|
||||
, sess = self.sessions[sid];
|
||||
if (sess) {
|
||||
sess = JSON.parse(sess);
|
||||
expires = 'string' == typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires)
|
||||
: sess.cookie.expires;
|
||||
if (!expires || new Date < expires) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
self.destroy(sid, fn);
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the given `sess` object associated with the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Session} sess
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.set = function(sid, sess, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
self.sessions[sid] = JSON.stringify(sess);
|
||||
fn && fn();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the session associated with the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.destroy = function(sid, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
delete self.sessions[sid];
|
||||
fn && fn();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke the given callback `fn` with all active sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.all = function(fn){
|
||||
var arr = []
|
||||
, keys = Object.keys(this.sessions);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
arr.push(this.sessions[keys[i]]);
|
||||
}
|
||||
fn(null, arr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.clear = function(fn){
|
||||
this.sessions = {};
|
||||
fn && fn();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch number of sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.length = function(fn){
|
||||
fn(null, Object.keys(this.sessions).length);
|
||||
};
|
||||
116
node_modules/connect-architect/connect.session/session/session.js
generated
vendored
Normal file
116
node_modules/connect-architect/connect.session/session/session.js
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
|
||||
/*!
|
||||
* Connect - session - Session
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
* Create a new `Session` with the given request and `data`.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @param {Object} data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Session = module.exports = function Session(req, data) {
|
||||
Object.defineProperty(this, 'req', { value: req });
|
||||
Object.defineProperty(this, 'id', { value: req.sessionID });
|
||||
if ('object' == typeof data) utils.merge(this, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update reset `.cookie.maxAge` to prevent
|
||||
* the cookie from expiring when the
|
||||
* session is still active.
|
||||
*
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.touch = function(){
|
||||
return this.resetMaxAge();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset `.maxAge` to `.originalMaxAge`.
|
||||
*
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.resetMaxAge = function(){
|
||||
this.cookie.maxAge = this.cookie.originalMaxAge;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the session data with optional callback `fn(err)`.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.save = function(fn){
|
||||
this.req.sessionStore.set(this.id, this, fn || function(){});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-loads the session data _without_ altering
|
||||
* the maxAge properties. Invokes the callback `fn(err)`,
|
||||
* after which time if no exception has occurred the
|
||||
* `req.session` property will be a new `Session` object,
|
||||
* although representing the same session.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.reload = function(fn){
|
||||
var req = this.req
|
||||
, store = this.req.sessionStore;
|
||||
store.get(this.id, function(err, sess){
|
||||
if (err) return fn(err);
|
||||
if (!sess) return fn(new Error('failed to load session'));
|
||||
store.createSession(req, sess);
|
||||
fn();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy `this` session.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.destroy = function(fn){
|
||||
delete this.req.session;
|
||||
this.req.sessionStore.destroy(this.id, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Regenerate this request's session.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.regenerate = function(fn){
|
||||
this.req.sessionStore.regenerate(this.req, fn);
|
||||
return this;
|
||||
};
|
||||
84
node_modules/connect-architect/connect.session/session/store.js
generated
vendored
Normal file
84
node_modules/connect-architect/connect.session/session/store.js
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
/*!
|
||||
* Connect - session - Store
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, Session = require('./session')
|
||||
, Cookie = require('./cookie');
|
||||
|
||||
/**
|
||||
* Initialize abstract `Store`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Store = module.exports = function Store(options){};
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter.prototype`.
|
||||
*/
|
||||
|
||||
Store.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Re-generate the given requests's session.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @return {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.prototype.regenerate = function(req, fn){
|
||||
var self = this;
|
||||
this.destroy(req.sessionID, function(err){
|
||||
self.generate(req);
|
||||
fn(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a `Session` instance via the given `sid`
|
||||
* and invoke the callback `fn(err, sess)`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.prototype.load = function(sid, fn){
|
||||
var self = this;
|
||||
this.get(sid, function(err, sess){
|
||||
if (err) return fn(err);
|
||||
if (!sess) return fn();
|
||||
var req = { sessionID: sid, sessionStore: self };
|
||||
sess = self.createSession(req, sess);
|
||||
fn(null, sess);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create session from JSON `sess` data.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @param {Object} sess
|
||||
* @return {Session}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.createSession = function(req, sess){
|
||||
var expires = sess.cookie.expires
|
||||
, orig = sess.cookie.originalMaxAge;
|
||||
sess.cookie = new Cookie(sess.cookie);
|
||||
if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
|
||||
sess.cookie.originalMaxAge = orig;
|
||||
req.session = new Session(req, sess);
|
||||
return req.session;
|
||||
};
|
||||
408
node_modules/connect-architect/connect.session/session/utils.js
generated
vendored
Normal file
408
node_modules/connect-architect/connect.session/session/utils.js
generated
vendored
Normal file
@ -0,0 +1,408 @@
|
||||
|
||||
/*!
|
||||
* Connect - utils
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, crypto = require('crypto')
|
||||
, parse = require('url').parse
|
||||
, sep = require('path').sep
|
||||
, signature = require('cookie-signature')
|
||||
, nodeVersion = process.versions.node.split('.');
|
||||
|
||||
// pause is broken in node < 0.10
|
||||
exports.brokenPause = parseInt(nodeVersion[0], 10) === 0
|
||||
&& parseInt(nodeVersion[1], 10) < 10;
|
||||
|
||||
/**
|
||||
* Return `true` if the request has a body, otherwise return `false`.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.hasBody = function(req) {
|
||||
var encoding = 'transfer-encoding' in req.headers;
|
||||
var length = 'content-length' in req.headers && req.headers['content-length'] !== '0';
|
||||
return encoding || length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the mime type from the given request's
|
||||
* _Content-Type_ header.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.mime = function(req) {
|
||||
var str = req.headers['content-type'] || '';
|
||||
return str.split(';')[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate an `Error` from the given status `code`
|
||||
* and optional `msg`.
|
||||
*
|
||||
* @param {Number} code
|
||||
* @param {String} msg
|
||||
* @return {Error}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.error = function(code, msg){
|
||||
var err = new Error(msg || http.STATUS_CODES[code]);
|
||||
err.status = code;
|
||||
return err;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return md5 hash of the given string and optional encoding,
|
||||
* defaulting to hex.
|
||||
*
|
||||
* utils.md5('wahoo');
|
||||
* // => "e493298061761236c96b02ea6aa8a2ad"
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {String} encoding
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.md5 = function(str, encoding){
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(str, 'utf8')
|
||||
.digest(encoding || 'hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge object b with object a.
|
||||
*
|
||||
* var a = { foo: 'bar' }
|
||||
* , b = { bar: 'baz' };
|
||||
*
|
||||
* utils.merge(a, b);
|
||||
* // => { foo: 'bar', bar: 'baz' }
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.merge = function(a, b){
|
||||
if (a && b) {
|
||||
for (var key in b) {
|
||||
a[key] = b[key];
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape the given string of `html`.
|
||||
*
|
||||
* @param {String} html
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.escape = function(html){
|
||||
return String(html)
|
||||
.replace(/&(?!\w+;)/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
};
|
||||
|
||||
/**
|
||||
* Sign the given `val` with `secret`.
|
||||
*
|
||||
* @param {String} val
|
||||
* @param {String} secret
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.sign = function(val, secret){
|
||||
console.warn('do not use utils.sign(), use https://github.com/visionmedia/node-cookie-signature')
|
||||
return val + '.' + crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(val)
|
||||
.digest('base64')
|
||||
.replace(/=+$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsign and decode the given `val` with `secret`,
|
||||
* returning `false` if the signature is invalid.
|
||||
*
|
||||
* @param {String} val
|
||||
* @param {String} secret
|
||||
* @return {String|Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.unsign = function(val, secret){
|
||||
console.warn('do not use utils.unsign(), use https://github.com/visionmedia/node-cookie-signature')
|
||||
var str = val.slice(0, val.lastIndexOf('.'));
|
||||
return exports.sign(str, secret) == val
|
||||
? str
|
||||
: false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse signed cookies, returning an object
|
||||
* containing the decoded key/value pairs,
|
||||
* while removing the signed key from `obj`.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseSignedCookies = function(obj, secret){
|
||||
var ret = {};
|
||||
Object.keys(obj).forEach(function(key){
|
||||
var val = obj[key];
|
||||
if (0 == val.indexOf('s:')) {
|
||||
val = signature.unsign(val.slice(2), secret);
|
||||
if (val) {
|
||||
ret[key] = val;
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a signed cookie string, return the decoded value
|
||||
*
|
||||
* @param {String} str signed cookie string
|
||||
* @param {String} secret
|
||||
* @return {String} decoded value
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseSignedCookie = function(str, secret){
|
||||
return 0 == str.indexOf('s:')
|
||||
? signature.unsign(str.slice(2), secret)
|
||||
: str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse JSON cookies.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseJSONCookies = function(obj){
|
||||
Object.keys(obj).forEach(function(key){
|
||||
var val = obj[key];
|
||||
var res = exports.parseJSONCookie(val);
|
||||
if (res) obj[key] = res;
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse JSON cookie string
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Object} Parsed object or null if not json cookie
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseJSONCookie = function(str) {
|
||||
if (0 == str.indexOf('j:')) {
|
||||
try {
|
||||
return JSON.parse(str.slice(2));
|
||||
} catch (err) {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pause `data` and `end` events on the given `obj`.
|
||||
* Middleware performing async tasks _should_ utilize
|
||||
* this utility (or similar), to re-emit data once
|
||||
* the async operation has completed, otherwise these
|
||||
* events may be lost. Pause is only required for
|
||||
* node versions less than 10, and is replaced with
|
||||
* noop's otherwise.
|
||||
*
|
||||
* var pause = utils.pause(req);
|
||||
* fs.readFile(path, function(){
|
||||
* next();
|
||||
* pause.resume();
|
||||
* });
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.pause = exports.brokenPause
|
||||
? require('pause')
|
||||
: function () {
|
||||
return {
|
||||
end: noop,
|
||||
resume: noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip `Content-*` headers from `res`.
|
||||
*
|
||||
* @param {ServerResponse} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.removeContentHeaders = function(res){
|
||||
if (!res._headers) return;
|
||||
Object.keys(res._headers).forEach(function(field){
|
||||
if (0 == field.indexOf('content')) {
|
||||
res.removeHeader(field);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if `req` is a conditional GET request.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.conditionalGET = function(req) {
|
||||
return req.headers['if-modified-since']
|
||||
|| req.headers['if-none-match'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond with 401 "Unauthorized".
|
||||
*
|
||||
* @param {ServerResponse} res
|
||||
* @param {String} realm
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.unauthorized = function(res, realm) {
|
||||
res.statusCode = 401;
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
|
||||
res.end('Unauthorized');
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond with 304 "Not Modified".
|
||||
*
|
||||
* @param {ServerResponse} res
|
||||
* @param {Object} headers
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.notModified = function(res) {
|
||||
exports.removeContentHeaders(res);
|
||||
res.statusCode = 304;
|
||||
res.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an ETag in the form of `"<size>-<mtime>"`
|
||||
* from the given `stat`.
|
||||
*
|
||||
* @param {Object} stat
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.etag = function(stat) {
|
||||
return '"' + stat.size + '-' + Number(stat.mtime) + '"';
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the given Cache-Control `str`.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseCacheControl = function(str){
|
||||
var directives = str.split(',')
|
||||
, obj = {};
|
||||
|
||||
for(var i = 0, len = directives.length; i < len; i++) {
|
||||
var parts = directives[i].split('=')
|
||||
, key = parts.shift().trim()
|
||||
, val = parseInt(parts.shift(), 10);
|
||||
|
||||
obj[key] = isNaN(val) ? true : val;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the `req` url with memoization.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseUrl = function(req){
|
||||
var parsed = req._parsedUrl;
|
||||
if (parsed && parsed.href == req.url) {
|
||||
return parsed;
|
||||
} else {
|
||||
parsed = parse(req.url);
|
||||
|
||||
if (parsed.auth && !parsed.protocol && ~parsed.href.indexOf('//')) {
|
||||
// This parses pathnames, and a strange pathname like //r@e should work
|
||||
parsed = parse(req.url.replace(/@/g, '%40'));
|
||||
}
|
||||
|
||||
return req._parsedUrl = parsed;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse byte `size` string.
|
||||
*
|
||||
* @param {String} size
|
||||
* @return {Number}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseBytes = require('bytes');
|
||||
|
||||
/**
|
||||
* Normalizes the path separator from system separator
|
||||
* to URL separator, aka `/`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.normalizeSlashes = function normalizeSlashes(path) {
|
||||
return path.split(sep).join('/');
|
||||
};
|
||||
|
||||
function noop() {}
|
||||
186
node_modules/connect-architect/connect.session/session_test.js
generated
vendored
Normal file
186
node_modules/connect-architect/connect.session/session_test.js
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*global describe it before after beforeEach afterEach */
|
||||
"use strict";
|
||||
"use server";
|
||||
|
||||
require("c9/inline-mocha")(module);
|
||||
require("c9/setup_paths");
|
||||
|
||||
var assert = require("assert");
|
||||
var ConnectCookie = require("./session/cookie");
|
||||
var Cookie = require("cookie");
|
||||
var session = require("./session");
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
var sinon = require("sinon");
|
||||
|
||||
var Store = require("connect").session.MemoryStore;
|
||||
|
||||
var encrypt = require("./encrypt");
|
||||
var decrypt = require("./decrypt");
|
||||
var hash = require("./session/hash");
|
||||
|
||||
describe("lib/session", function() {
|
||||
|
||||
function mockReq() {
|
||||
var req = new EventEmitter();
|
||||
req.originalUrl = "/";
|
||||
req.cookies = {};
|
||||
req.signedCookies = {};
|
||||
req.headers = {};
|
||||
req.connection = {};
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function mockRes() {
|
||||
var res = new EventEmitter();
|
||||
res.setHeader = function(key, value) {};
|
||||
sinon.spy(res, "setHeader");
|
||||
return res;
|
||||
}
|
||||
|
||||
it("Returns a middleware", function() {
|
||||
var middleware = session({
|
||||
store: new Store()
|
||||
});
|
||||
|
||||
assert.equal(middleware.length, 3, "Has an arity of 3");
|
||||
});
|
||||
|
||||
it("Encrypts a cookie", function(done) {
|
||||
var middleware = session({
|
||||
store: new Store(),
|
||||
secret: "limecat"
|
||||
});
|
||||
|
||||
var res = mockRes();
|
||||
|
||||
middleware(mockReq(), res, function() {
|
||||
res.emit("header");
|
||||
|
||||
assert.ok(res.setHeader.calledOnce);
|
||||
var args = res.setHeader.args[0];
|
||||
|
||||
assert.equal(args[0], "Set-Cookie");
|
||||
assert.ok(/connect.sid=.+?; Path=\/; HttpOnly/.test(args[1]));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Does not create a new cookie if the secret remains the same", function(done) {
|
||||
var sessionID = "123";
|
||||
var secret = "limecat";
|
||||
|
||||
var cookieVal = encrypt(sessionID, "connect.sid", new ConnectCookie({}), secret);
|
||||
|
||||
var req = mockReq();
|
||||
var res = mockRes();
|
||||
var store = new Store();
|
||||
|
||||
var mw = session({
|
||||
secret: ["newsecret", secret],
|
||||
store: store
|
||||
});
|
||||
|
||||
sinon.stub(hash, "hash", function() {
|
||||
return;
|
||||
});
|
||||
|
||||
req.cookies = Cookie.parse(cookieVal);
|
||||
|
||||
mw(req, res, function() {
|
||||
var sessionCalled = 0;
|
||||
var sessionCookie = new ConnectCookie({});
|
||||
|
||||
// force the code down a certain path
|
||||
sessionCookie.expires = new Date(Date.now() + 1000);
|
||||
|
||||
Object.defineProperty(req, "session", {
|
||||
get: function() {
|
||||
sessionCalled++;
|
||||
return {
|
||||
cookie: sessionCookie
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
res.emit("header");
|
||||
|
||||
assert.ok(hash.hash.calledOnce, "we know the hash got called (asserts the code path)");
|
||||
assert.equal(sessionCalled, 4, "session was accessed, this asserts the code-path");
|
||||
hash.hash.restore();
|
||||
assert.ok(res.setHeader.notCalled);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Rotates the secret: it encrypts using the new secret", function(done) {
|
||||
var sessionID = Math.random().toString(36);
|
||||
var secret = [Math.random().toString(36), Math.random().toString(36), Math.random().toString(36)];
|
||||
|
||||
// use the encryption funciton used in session to create a fake cookie
|
||||
var cookie = encrypt(sessionID, "connect.sid", new ConnectCookie({}), secret[2]);
|
||||
var req = mockReq();
|
||||
var res = mockRes();
|
||||
|
||||
var mw = session({
|
||||
secret: secret,
|
||||
store: new Store()
|
||||
});
|
||||
|
||||
req.sessionID = sessionID;
|
||||
req.cookies = Cookie.parse(cookie);
|
||||
|
||||
var decryptSpy = sinon.spy(decrypt, "decrypt");
|
||||
|
||||
// forces code down the path in L262
|
||||
sinon.stub(hash, "hash", function() {
|
||||
return;
|
||||
});
|
||||
|
||||
mw(req, res, function() {
|
||||
// The cookie must have been decrypted by know, using one of the
|
||||
// available secrets.
|
||||
assert.deepEqual(decryptSpy.returnValues[0], {
|
||||
unsignedCookie: sessionID,
|
||||
usedSecret: secret[2]
|
||||
}, "our cookie was decrypted");
|
||||
|
||||
// in the "header" listener we check for the cookie.
|
||||
var sessionCookie = new ConnectCookie({});
|
||||
|
||||
// forces the if on L259
|
||||
sessionCookie.expires = new Date(Date.now() + 1000);
|
||||
|
||||
// we need to mock req.session. The listener assumes this to be
|
||||
// set in a later code path.
|
||||
req.session = {
|
||||
cookie: sessionCookie
|
||||
};
|
||||
|
||||
// kick of the listener
|
||||
res.emit("header");
|
||||
|
||||
// check that L262 was NOT executed
|
||||
assert.ok(hash.hash.notCalled, "hash should be called on L262");
|
||||
|
||||
// we expect we got a fresh cookie
|
||||
assert.ok(res.setHeader.calledOnce, "we expect a new cookie");
|
||||
|
||||
var cookie = res.setHeader.args[0][1];
|
||||
var parsed = Cookie.parse(cookie);
|
||||
|
||||
var val = decrypt.decrypt(secret[1], parsed["connect.sid"]);
|
||||
|
||||
assert.deepEqual(val, {
|
||||
unsignedCookie: req.sessionID,
|
||||
usedSecret: secret[1]
|
||||
});
|
||||
|
||||
hash.hash.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user