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..3b8f721 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -108,6 +108,66 @@ 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() { + return rotatingSecretKey; + }); + functionServer.listen(); + + after(function() { + clearInterval(rotateKeyIntervalId); + }); + + it('should populate req.signedCookies', function(done){ + request(functionServer) + .get('/signed') + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) + .expect(200, '{"foo":"foobarbaz"}', done); + }); + + it('should remove the signed value from req.cookies', function(done){ + request(functionServer) + .get('/') + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) + .expect(200, '{}', done); + }); + + it('should omit invalid signatures', function(done){ + var signedValue = signature.sign('foobarbaz', rotatingSecretKey); + request(server) + .get('/signed') + .set('Cookie', 'foo=' + signedValue + '3') + .expect(200, '{}', function(err){ + if (err) return done(err); + request(server) + .get('/') + .set('Cookie', 'foo=' + signedValue + '3') + .expect(200, '{"foo":"' + signedValue + '3"}', done); + }); + }); + + it('should try multiple secrets', function(done){ + var multipleSecretsServer = createServer(function() { + return ['keyboard cat', rotatingSecretKey]; + }); + multipleSecretsServer.listen(); + request(multipleSecretsServer) + .get('/signed') + .set('Cookie', 'foo=s:' + signature.sign('foobarbaz', rotatingSecretKey)) + .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 +351,4 @@ function createServer(secret) { res.end(JSON.stringify(cookies)) }) }) -} +} \ No newline at end of file