From 3c9ef961785b2d86c435af92ec7b4fce2be304fa Mon Sep 17 00:00:00 2001 From: Alexey Ponomarev Date: Tue, 19 Aug 2014 13:24:16 +0700 Subject: [PATCH 1/5] Added support for headers sid passing. --- README.md | 1 + index.js | 43 ++++++++++++++++++++++++++++++++++++++++--- test/session.js | 25 +++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd1ba7ac..253a3977 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Session data is _not_ saved in the cookie itself, just the session ID. #### Options - `name` - cookie name (formerly known as `key`). (default: `'connect.sid'`) + - `headerName` - optional HTTP header name to pass session ID, e.g. `X-Session-Token`. (default: `undefined`) - `store` - session store instance. - `secret` - session cookie is signed with this secret to prevent tampering. - `cookie` - session cookie settings. diff --git a/index.js b/index.js index a84f60d5..f998af55 100644 --- a/index.js +++ b/index.js @@ -78,6 +78,8 @@ function session(options){ var options = options || {} // name - previously "options.key" , name = options.name || options.key || 'connect.sid' + // optional name of HTTP header to pass session ID, e.g. 'X-Session-Token' + , headerName = options.headerName , store = options.store || new MemoryStore , cookie = options.cookie || {} , trustProxy = options.proxy @@ -155,7 +157,11 @@ function session(options){ req.sessionStore = store; // get the session ID from the cookie - var cookieId = req.sessionID = getcookie(req, name, secret); + var sessionId = req.sessionID = getcookie(req, name, secret); + // if not trying to get session ID from header + if (!sessionId && headerName) { + sessionId = req.sessionID = getHeader(req, headerName, secret); + } // set-cookie onHeaders(res, function(){ @@ -177,6 +183,9 @@ function session(options){ } setcookie(res, name, req.sessionID, secret, cookie.data); + if (headerName) { + setHeader(res, headerName, req.sessionID, secret); + } }); // proxy end() to commit the session @@ -294,7 +303,7 @@ function session(options){ // determine if session should be saved to store function shouldSave(req) { - return cookieId != req.sessionID + return sessionId != req.sessionID ? saveUninitializedSession || isModified(req.session) : resaveSession || isModified(req.session); } @@ -306,7 +315,7 @@ function session(options){ return true; } - return cookieId != req.sessionID + return sessionId != req.sessionID ? saveUninitializedSession || isModified(req.session) : req.session.cookie.expires != null && isModified(req.session); } @@ -490,3 +499,31 @@ function setcookie(res, name, val, secret, options) { res.setHeader('set-cookie', header) } + +function setHeader(res, name, val, secret) { + var signed = 's:' + signature.sign(val, secret); + debug(name + ' %s', signed); + + res.setHeader(name, signed); +} + +function getHeader(req, name, secret) { + var header = req.headers[name.toLowerCase()]; + var val; + + // read from header + if (header) { + if (header.substr(0, 2) === 's:') { + val = signature.unsign(header.slice(2), secret); + + if (val === false) { + debug('header signature invalid'); + val = undefined; + } + } else { + debug('header unsigned') + } + } + + return val; +} diff --git a/test/session.js b/test/session.js index a15f4ef5..7bb3bf9f 100644 --- a/test/session.js +++ b/test/session.js @@ -92,6 +92,27 @@ describe('session()', function(){ }) }) + it('should load session from header sid', function (done) { + var count = 0 + var headerName = 'X-Session-Token'; + var server = createServer({ headerName: headerName }, function (req, res) { + req.session.num = req.session.num || ++count + res.end('session ' + req.session.num) + }); + + request(server) + .get('/') + .expect(200, 'session 1', function (err, res) { + if (err) return done(err) + sidHeader(res, headerName).should.not.be.empty + sidHeader(res, headerName).should.startWith('s:') + request(server) + .get('/') + .set(headerName, sidHeader(res, headerName)) + .expect(200, 'session 1', done) + }) + }) + it('should pass session fetch error', function (done) { var store = new session.MemoryStore() var server = createServer({ store: store }, function (req, res) { @@ -1794,6 +1815,10 @@ function sid(res) { return val } +function sidHeader(res, name) { + return res.headers[name.toLowerCase()] +} + function writePatch() { var ended = false return function addWritePatch(req, res, next) { From 87e4da0111709ec8339abee293999a2dab96f996 Mon Sep 17 00:00:00 2001 From: Alexey Ponomarev Date: Wed, 20 Aug 2014 10:27:06 +0700 Subject: [PATCH 2/5] Option 'headerName' was renamed to 'header'. Lower casing header name was removed from getHeader() function. --- index.js | 11 ++++++++--- test/session.js | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index f998af55..78f077d3 100644 --- a/index.js +++ b/index.js @@ -79,7 +79,7 @@ function session(options){ // name - previously "options.key" , name = options.name || options.key || 'connect.sid' // optional name of HTTP header to pass session ID, e.g. 'X-Session-Token' - , headerName = options.headerName + , headerName = options.header , store = options.store || new MemoryStore , cookie = options.cookie || {} , trustProxy = options.proxy @@ -87,6 +87,11 @@ function session(options){ , rollingSessions = options.rolling || false; var resaveSession = options.resave; var saveUninitializedSession = options.saveUninitialized; + var headerNameNormalized; + if (headerName) { + // lower-case representation of header name to fetch header value from req.headers + headerNameNormalized = headerName.toLowerCase(); + } var generateId = options.genid || generateSessionId; @@ -160,7 +165,7 @@ function session(options){ var sessionId = req.sessionID = getcookie(req, name, secret); // if not trying to get session ID from header if (!sessionId && headerName) { - sessionId = req.sessionID = getHeader(req, headerName, secret); + sessionId = req.sessionID = getHeader(req, headerNameNormalized, secret); } // set-cookie @@ -508,7 +513,7 @@ function setHeader(res, name, val, secret) { } function getHeader(req, name, secret) { - var header = req.headers[name.toLowerCase()]; + var header = req.headers[name]; var val; // read from header diff --git a/test/session.js b/test/session.js index 7bb3bf9f..26119f88 100644 --- a/test/session.js +++ b/test/session.js @@ -94,8 +94,8 @@ describe('session()', function(){ it('should load session from header sid', function (done) { var count = 0 - var headerName = 'X-Session-Token'; - var server = createServer({ headerName: headerName }, function (req, res) { + var header = 'X-Session-Token'; + var server = createServer({ header: header }, function (req, res) { req.session.num = req.session.num || ++count res.end('session ' + req.session.num) }); @@ -104,11 +104,11 @@ describe('session()', function(){ .get('/') .expect(200, 'session 1', function (err, res) { if (err) return done(err) - sidHeader(res, headerName).should.not.be.empty - sidHeader(res, headerName).should.startWith('s:') + sidHeader(res, header).should.not.be.empty + sidHeader(res, header).should.startWith('s:') request(server) .get('/') - .set(headerName, sidHeader(res, headerName)) + .set(header, sidHeader(res, header)) .expect(200, 'session 1', done) }) }) From c11b484da6cd5b695b01d42f45f1ce6d07a0c735 Mon Sep 17 00:00:00 2001 From: Alexey Ponomarev Date: Tue, 23 Sep 2014 17:35:37 +0700 Subject: [PATCH 3/5] Merge remote-tracking branch 'upstream/master' Conflicts: index.js --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 657aac30..dac0dedb 100644 --- a/index.js +++ b/index.js @@ -163,10 +163,10 @@ function session(options){ req.sessionStore = store; // get the session ID from the cookie - var sessionId = req.sessionID = getcookie(req, name, secret); + var cookieId = req.sessionID = getcookie(req, name, secret); // if not trying to get session ID from header - if (!sessionId && headerName) { - sessionId = req.sessionID = getHeader(req, headerNameNormalized, secret); + if (!cookieId && headerName) { + cookieId = req.sessionID = getHeader(req, headerNameNormalized, secret); } // set-cookie @@ -344,7 +344,7 @@ function session(options){ return true; } - return sessionId != req.sessionID + return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) : req.session.cookie.expires != null && isModified(req.session); } From 401d74cc05841e9dabc6a21381acb243a8a355ba Mon Sep 17 00:00:00 2001 From: Alexey Ponomarev Date: Sun, 11 Jan 2015 20:54:33 +0600 Subject: [PATCH 4/5] Now it is possible to specify cookie option as null, so library will not use cookies for authorization at all. --- index.js | 46 +++++++++++++++++++++++++++------------------- session/session.js | 4 +++- test/session.js | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index dac0dedb..e486a70a 100644 --- a/index.js +++ b/index.js @@ -87,6 +87,7 @@ function session(options){ , rollingSessions = options.rolling || false; var resaveSession = options.resave; var saveUninitializedSession = options.saveUninitialized; + var isCookieConfigurationSet = options.cookie !== null; var headerNameNormalized; if (headerName) { // lower-case representation of header name to fetch header value from req.headers @@ -126,7 +127,9 @@ function session(options){ store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); - req.session.cookie = new Cookie(cookie); + if (isCookieConfigurationSet) { + req.session.cookie = new Cookie(cookie); + } }; store.on('disconnect', function(){ storeReady = false; }); @@ -144,9 +147,11 @@ function session(options){ // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); - // pathname mismatch - var originalPath = parseUrl.original(req).pathname; - if (0 != originalPath.indexOf(cookie.path || '/')) return next(); + if (isCookieConfigurationSet) { + // pathname mismatch + var originalPath = parseUrl.original(req).pathname; + if (0 != originalPath.indexOf(cookie.path || '/')) return next(); + } // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware @@ -162,10 +167,13 @@ function session(options){ // expose store req.sessionStore = store; - // get the session ID from the cookie - var cookieId = req.sessionID = getcookie(req, name, secret); - // if not trying to get session ID from header - if (!cookieId && headerName) { + var cookieId; + if (isCookieConfigurationSet) { + // get the session ID from the cookie + cookieId = req.sessionID = getcookie(req, name, secret); + } + if (headerName) { + // get the session ID from the header cookieId = req.sessionID = getHeader(req, headerNameNormalized, secret); } @@ -176,19 +184,19 @@ function session(options){ return; } - var cookie = req.session.cookie; - - // only send secure cookies via https - if (cookie.secure && !issecure(req, trustProxy)) { - debug('not secured'); - return; - } - - if (!shouldSetCookie(req)) { - return; + if (isCookieConfigurationSet) { + var cookie = req.session.cookie; + // only send secure cookies via https + if (cookie.secure && !issecure(req, trustProxy)) { + debug('not secured'); + return; + } + if (!shouldSetCookie(req)) { + return; + } + setcookie(res, name, req.sessionID, secret, cookie.data); } - setcookie(res, name, req.sessionID, secret, cookie.data); if (headerName) { setHeader(res, headerName, req.sessionID, secret); } diff --git a/session/session.js b/session/session.js index 4647f9da..8024f7e2 100644 --- a/session/session.js +++ b/session/session.js @@ -55,7 +55,9 @@ Session.prototype.touch = function(){ */ Session.prototype.resetMaxAge = function(){ - this.cookie.maxAge = this.cookie.originalMaxAge; + if (this.cookie) { + this.cookie.maxAge = this.cookie.originalMaxAge; + } return this; }; diff --git a/test/session.js b/test/session.js index 7e0306c5..315fbb01 100644 --- a/test/session.js +++ b/test/session.js @@ -113,6 +113,21 @@ describe('session()', function(){ }) }) + it('should not respond with cookie if configuration cookie key set as null', function (done) { + var header = 'X-Session-Token'; + var server = createServer({ header: header, cookie: null }, function (req, res) { + res.end('session') + }); + + request(server) + .get('/') + .expect(200, 'session', function (err, res) { + if (err) return done(err) + res.headers.should.not.have.property('set-cookie'); + done(); + }) + }) + it('should pass session fetch error', function (done) { var store = new session.MemoryStore() var server = createServer({ store: store }, function (req, res) { From f14a811d477c7c6873e5e58378bcea1167a0b0f5 Mon Sep 17 00:00:00 2001 From: Alexey Ponomarev Date: Sun, 11 Jan 2015 21:25:43 +0600 Subject: [PATCH 5/5] Updated tests to support new format. --- test/session.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/session.js b/test/session.js index cc9c3d2b..01f2fd03 100644 --- a/test/session.js +++ b/test/session.js @@ -100,10 +100,9 @@ describe('session()', function(){ request(server) .get('/') + .expect(shouldHaveHeader(header)) .expect(200, 'session 1', function (err, res) { if (err) return done(err) - sidHeader(res, header).should.not.be.empty - sidHeader(res, header).should.startWith('s:') request(server) .get('/') .set(header, sidHeader(res, header)) @@ -119,11 +118,8 @@ describe('session()', function(){ request(server) .get('/') - .expect(200, 'session', function (err, res) { - if (err) return done(err) - res.headers.should.not.have.property('set-cookie'); - done(); - }) + .expect(shouldNotHaveHeader('Set-Cookie')) + .expect(200, 'session', done) }) it('should pass session fetch error', function (done) { @@ -1976,6 +1972,12 @@ function shouldNotHaveHeader(header) { } } +function shouldHaveHeader(header) { + return function (res) { + assert.ok(header.toLowerCase() in res.headers, 'should have ' + header + ' header') + } +} + function shouldSetCookie(name) { return function (res) { var header = cookie(res)