Skip to content

Commit 27a65b7

Browse files
author
cobyfrombrooklyn-bot
committed
fix: prevent callback from being called twice on payload claim conflict
When jwt.sign() was called with a callback and the payload had a claim that conflicted with an option (e.g., payload.iss + options.issuer), the callback was invoked twice: once with the error and once with a signed token. This happened because the options-to-payload mapping used forEach(), where 'return failure()' only returned from the forEach callback, not from the outer function. Execution continued to jws.createSign(). Replaced forEach with a for loop so 'return failure()' properly exits the sign function. Fixes #1000
1 parent ed59e76 commit 27a65b7

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

sign.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,17 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
214214
}
215215
}
216216

217-
Object.keys(options_to_payload).forEach(function (key) {
217+
const optionKeys = Object.keys(options_to_payload);
218+
for (let i = 0; i < optionKeys.length; i++) {
219+
const key = optionKeys[i];
218220
const claim = options_to_payload[key];
219221
if (typeof options[key] !== 'undefined') {
220222
if (typeof payload[claim] !== 'undefined') {
221223
return failure(new Error('Bad "options.' + key + '" option. The payload already has an "' + claim + '" property.'));
222224
}
223225
payload[claim] = options[key];
224226
}
225-
});
227+
}
226228

227229
const encoding = options.encoding || 'utf8';
228230

test/async_sign.tests.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,31 @@ describe('signing a token asynchronously', function() {
147147
});
148148
});
149149
});
150+
151+
// Regression test for https://github.com/auth0/node-jsonwebtoken/issues/1000
152+
describe('when payload has a claim that conflicts with options', function () {
153+
it('should call the callback only once with an error', function (done) {
154+
var callCount = 0;
155+
jwt.sign(
156+
{ iss: 'bar', iat: Math.floor(Date.now() / 1000) },
157+
'secret',
158+
{ algorithm: 'HS256', issuer: 'foo' },
159+
function (err, token) {
160+
callCount++;
161+
if (callCount === 1) {
162+
expect(err).to.be.an.instanceof(Error);
163+
expect(err.message).to.match(/payload already has an "iss" property/);
164+
expect(token).to.be.undefined;
165+
// Wait a tick to ensure callback isn't called again
166+
setTimeout(function () {
167+
expect(callCount).to.equal(1);
168+
done();
169+
}, 10);
170+
} else {
171+
done(new Error('Callback was called ' + callCount + ' times, expected once'));
172+
}
173+
}
174+
);
175+
});
176+
});
150177
});

0 commit comments

Comments
 (0)