From abe963e818a6d495e412a12b345dc6d647d9cdc6 Mon Sep 17 00:00:00 2001 From: David Pate Date: Fri, 16 Oct 2015 14:20:06 -0400 Subject: [PATCH 1/3] Add the ability to provide a secret function which is invoked with the request object to create a dynamic secret. --- README.md | 2 +- index.js | 13 +++++++++-- test/cookieParser.js | 52 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9f37b6d..fd73440 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ app.use(cookieParser()) ### cookieParser(secret, options) -- `secret` a string or array used for signing cookies. This is optional and if not specified, will not parse signed cookies. If a string is provided, this is used as the secret. If an array is provided, an attempt will be made to unsign the cookie with each secret in order. +- `secret` a string, array of strings, or a function used for signing cookies. This is optional and if not specified, will not parse signed cookies. If a string is provided, this is used as the secret. If an array is provided, an attempt will be made to unsign the cookie with each secret in order. If a string is provided, the return value will be used just like providing a string or array of strings. - `options` an object that is passed to `cookie.parse` as the second option. See [cookie](https://www.npmjs.org/package/cookie) for more information. - `decode` a function to decode the value of the cookie diff --git a/index.js b/index.js index 15960ff..b3bec4d 100644 --- a/index.js +++ b/index.js @@ -42,8 +42,17 @@ function cookieParser(secret, options) { var cookies = req.headers.cookie; var secrets = !secret || Array.isArray(secret) - ? (secret || []) - : [secret]; + ? (secret || []) + : [secret]; + + if (typeof secret === 'function') { + var secretValue = secret(req); + if (Array.isArray(secretValue)) { + secrets = secretValue; + } else { + secrets = [secretValue]; + } + } req.secret = secrets[0]; req.cookies = Object.create(null); diff --git a/test/cookieParser.js b/test/cookieParser.js index 4942a54..c928c9f 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -108,6 +108,56 @@ describe('cookieParser()', function(){ }) }) + describe('when a secret function is given', function(){ + var functionServer = createServer(function(req) { + return 'Danger Zone!' + req.headers.host; + }); + functionServer.listen(); + var val = signature.sign('foobarbaz', 'Danger Zone!test.example.com'); + + it('should populate req.signedCookies', function(done){ + request(functionServer) + .get('/signed') + .set('Cookie', 'foo=s:' + val) + .set('Host', 'test.example.com') + .expect(200, '{"foo":"foobarbaz"}', done); + }); + + it('should remove the signed value from req.cookies', function(done){ + request(functionServer) + .get('/') + .set('Cookie', 'foo=s:' + val) + .set('Host', 'test.example.com') + .expect(200, '{}', done); + }); + + it('should omit invalid signatures', function(done){ + request(server) + .get('/signed') + .set('Cookie', 'foo=' + val + '3') + .set('Host', 'test.example.com') + .expect(200, '{}', function(err){ + if (err) return done(err); + request(server) + .get('/') + .set('Cookie', 'foo=' + val + '3') + .expect(200, '{"foo":"foobarbaz.mmVdlaLQaBJElM9B0MsMt1Ou6FrS9TkL9LcBoF8sJ0M3"}', done); + }); + }); + + it('should try multiple secrets', function(done){ + var multipleSecretsServer = createServer(function(req) { + return ['keyboard cat', 'Danger Zone!' + req.headers.host]; + }); + multipleSecretsServer.listen(); + request(multipleSecretsServer) + .get('/signed') + .set('Cookie', 'foo=s:' + val) + .set('Host', 'test.example.com') + .expect(200, '{"foo":"foobarbaz"}', done); + }); + }); + describe('when multiple secrets are given', function () { it('should populate req.signedCookies', function (done) { request(createServer(['keyboard cat', 'nyan cat'])) @@ -291,4 +341,4 @@ function createServer(secret) { res.end(JSON.stringify(cookies)) }) }) -} +} \ No newline at end of file From e676a191ccfd96441bffcef1ffe67161d3278e9c Mon Sep 17 00:00:00 2001 From: David Pate Date: Thu, 18 Aug 2016 13:12:24 -0400 Subject: [PATCH 2/3] Switch the test example over to a rotating key via a simple `setInterval`. While not the best or most complete example it shows one such method of using this functionality. --- test/cookieParser.js | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/test/cookieParser.js b/test/cookieParser.js index c928c9f..f1f3a82 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -109,16 +109,29 @@ describe('cookieParser()', function(){ }) describe('when a secret function is given', function(){ + var rotatingSecretKey; + + function rotateKey() { + rotatingSecretKey = Math.random() + ''; + } + + // Initial rotation. + rotateKey(); + var rotateKeyIntervalId = setInterval(rotateKey, 500); + var functionServer = createServer(function(req) { - return 'Danger Zone!' + req.headers.host; + return rotatingSecretKey; }); functionServer.listen(); - var val = signature.sign('foobarbaz', 'Danger Zone!test.example.com'); + + after(function() { + clearInterval(rotateKeyIntervalId); + }); it('should populate req.signedCookies', function(done){ request(functionServer) .get('/signed') - .set('Cookie', 'foo=s:' + val) + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) .set('Host', 'test.example.com') .expect(200, '{"foo":"foobarbaz"}', done); }); @@ -126,33 +139,34 @@ describe('cookieParser()', function(){ it('should remove the signed value from req.cookies', function(done){ request(functionServer) .get('/') - .set('Cookie', 'foo=s:' + val) + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) .set('Host', 'test.example.com') .expect(200, '{}', done); }); it('should omit invalid signatures', function(done){ + var signedValue = signature.sign('foobarbaz', rotatingSecretKey); request(server) .get('/signed') - .set('Cookie', 'foo=' + val + '3') + .set('Cookie', 'foo=' + signedValue + '3') .set('Host', 'test.example.com') .expect(200, '{}', function(err){ if (err) return done(err); request(server) .get('/') - .set('Cookie', 'foo=' + val + '3') - .expect(200, '{"foo":"foobarbaz.mmVdlaLQaBJElM9B0MsMt1Ou6FrS9TkL9LcBoF8sJ0M3"}', done); + .set('Cookie', 'foo=' + signedValue + '3') + .expect(200, '{"foo":"' + signedValue + '3"}', done); }); }); it('should try multiple secrets', function(done){ var multipleSecretsServer = createServer(function(req) { - return ['keyboard cat', 'Danger Zone!' + req.headers.host]; + return ['keyboard cat', rotatingSecretKey]; }); multipleSecretsServer.listen(); request(multipleSecretsServer) .get('/signed') - .set('Cookie', 'foo=s:' + val) + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) .set('Host', 'test.example.com') .expect(200, '{"foo":"foobarbaz"}', done); }); From f139decaa876403d422ef0517afcc8fa5c14226e Mon Sep 17 00:00:00 2001 From: David Pate Date: Thu, 18 Aug 2016 13:26:42 -0400 Subject: [PATCH 3/3] Remove now unnecessary setting of `Host` header. --- test/cookieParser.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/cookieParser.js b/test/cookieParser.js index f1f3a82..3b8f721 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -119,7 +119,7 @@ describe('cookieParser()', function(){ rotateKey(); var rotateKeyIntervalId = setInterval(rotateKey, 500); - var functionServer = createServer(function(req) { + var functionServer = createServer(function() { return rotatingSecretKey; }); functionServer.listen(); @@ -132,7 +132,6 @@ describe('cookieParser()', function(){ request(functionServer) .get('/signed') .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) - .set('Host', 'test.example.com') .expect(200, '{"foo":"foobarbaz"}', done); }); @@ -140,7 +139,6 @@ describe('cookieParser()', function(){ request(functionServer) .get('/') .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) - .set('Host', 'test.example.com') .expect(200, '{}', done); }); @@ -149,7 +147,6 @@ describe('cookieParser()', function(){ request(server) .get('/signed') .set('Cookie', 'foo=' + signedValue + '3') - .set('Host', 'test.example.com') .expect(200, '{}', function(err){ if (err) return done(err); request(server) @@ -160,14 +157,13 @@ describe('cookieParser()', function(){ }); it('should try multiple secrets', function(done){ - var multipleSecretsServer = createServer(function(req) { + var multipleSecretsServer = createServer(function() { return ['keyboard cat', rotatingSecretKey]; }); multipleSecretsServer.listen(); request(multipleSecretsServer) .get('/signed') .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) - .set('Host', 'test.example.com') .expect(200, '{"foo":"foobarbaz"}', done); }); });