diff --git a/sign.js b/sign.js index 82bf526..0660ae4 100644 --- a/sign.js +++ b/sign.js @@ -214,7 +214,9 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) { } } - Object.keys(options_to_payload).forEach(function (key) { + const optionKeys = Object.keys(options_to_payload); + for (let i = 0; i < optionKeys.length; i++) { + const key = optionKeys[i]; const claim = options_to_payload[key]; if (typeof options[key] !== 'undefined') { if (typeof payload[claim] !== 'undefined') { @@ -222,7 +224,7 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) { } payload[claim] = options[key]; } - }); + } const encoding = options.encoding || 'utf8'; diff --git a/test/async_sign.tests.js b/test/async_sign.tests.js index eb31174..8918328 100644 --- a/test/async_sign.tests.js +++ b/test/async_sign.tests.js @@ -147,4 +147,31 @@ describe('signing a token asynchronously', function() { }); }); }); + + // Regression test for https://github.com/auth0/node-jsonwebtoken/issues/1000 + describe('when payload has a claim that conflicts with options', function () { + it('should call the callback only once with an error', function (done) { + var callCount = 0; + jwt.sign( + { iss: 'bar', iat: Math.floor(Date.now() / 1000) }, + 'secret', + { algorithm: 'HS256', issuer: 'foo' }, + function (err, token) { + callCount++; + if (callCount === 1) { + expect(err).to.be.an.instanceof(Error); + expect(err.message).to.match(/payload already has an "iss" property/); + expect(token).to.be.undefined; + // Wait a tick to ensure callback isn't called again + setTimeout(function () { + expect(callCount).to.equal(1); + done(); + }, 10); + } else { + done(new Error('Callback was called ' + callCount + ' times, expected once')); + } + } + ); + }); + }); });