diff --git a/sign.js b/sign.js index 6041596..64ce548 100644 --- a/sign.js +++ b/sign.js @@ -154,7 +154,12 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) { } if (typeof options.expiresIn !== 'undefined' && typeof payload === 'object') { - payload.exp = timespan(options.expiresIn, timestamp); + try { + payload.exp = timespan(options.expiresIn, timestamp); + } + catch (err) { + return failure(err); + } if (typeof payload.exp === 'undefined') { return failure(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60')); } diff --git a/test/claim-exp.test.js b/test/claim-exp.test.js index dec044a..816d12e 100644 --- a/test/claim-exp.test.js +++ b/test/claim-exp.test.js @@ -9,12 +9,12 @@ const testUtils = require('./test-utils'); const base64UrlEncode = testUtils.base64UrlEncode; const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0'; -function signWithExpiresIn(payload, expiresIn) { +function signWithExpiresIn(expiresIn, payload, callback) { const options = {algorithm: 'none'}; if (expiresIn !== undefined) { options.expiresIn = expiresIn; } - return jwt.sign(payload, undefined, options); + testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('expires', function() { @@ -35,43 +35,68 @@ describe('expires', function() { {}, {foo: 'bar'}, ].forEach((expiresIn) => { - it(`should error with with value ${util.inspect(expiresIn)}`, function () { - expect(() => signWithExpiresIn({}, expiresIn)).to.throw( - '"expiresIn" should be a number of seconds or string representing a timespan' - ); + it(`should error with with value ${util.inspect(expiresIn)}`, function (done) { + signWithExpiresIn(expiresIn, {}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property('message') + .match(/"expiresIn" should be a number of seconds or string representing a timespan/); + }); + }); }); }); // TODO this should throw the same error as other invalid inputs - it(`should error with with value ''`, function () { - expect(() => signWithExpiresIn({}, '')).to.throw( - 'val is not a non-empty string or a valid number. val=""' - ); + it(`should error with with value ''`, function (done) { + signWithExpiresIn('', {}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property('message', 'val is not a non-empty string or a valid number. val=""'); + }); + }); }); // undefined needs special treatment because {} is not the same as {expiresIn: undefined} - it('should error with with value undefined', function () { - expect(() =>jwt.sign({}, undefined, {expiresIn: undefined, algorithm: 'none'})).to.throw( - '"expiresIn" should be a number of seconds or string representing a timespan' - ); + it('should error with with value undefined', function (done) { + testUtils.signJWTHelper({}, undefined, {expiresIn: undefined, algorithm: 'none'}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property( + 'message', + '"expiresIn" should be a number of seconds or string representing a timespan' + ); + }); + }); }); - it ('should error when "exp" is in payload', function() { - expect(() => signWithExpiresIn({exp: 100}, 100)).to.throw( - 'Bad "options.expiresIn" option the payload already has an "exp" property.' - ); + it ('should error when "exp" is in payload', function(done) { + signWithExpiresIn(100, {exp: 100}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property( + 'message', + 'Bad "options.expiresIn" option the payload already has an "exp" property.' + ); + }); + }); }); - it('should error with a string payload', function() { - expect(() => signWithExpiresIn('a string payload', 100)).to.throw( - 'invalid expiresIn option for string payload' - ); + it('should error with a string payload', function(done) { + signWithExpiresIn(100, 'a string payload', (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property('message', 'invalid expiresIn option for string payload'); + }); + }); }); - it('should error with a Buffer payload', function() { - expect(() => signWithExpiresIn(Buffer.from('a Buffer payload'), 100)).to.throw( - 'invalid expiresIn option for object payload' - ); + it('should error with a Buffer payload', function(done) { + signWithExpiresIn(100, Buffer.from('a Buffer payload'), (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property('message', 'invalid expiresIn option for object payload'); + }); + }); }); }); @@ -89,10 +114,13 @@ describe('expires', function() { {}, {foo: 'bar'}, ].forEach((exp) => { - it(`should error with with value ${util.inspect(exp)}`, function () { - expect(() => signWithExpiresIn({exp})).to.throw( - '"exp" should be a number of seconds' - ); + it(`should error with with value ${util.inspect(exp)}`, function (done) { + signWithExpiresIn(undefined, {exp}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(Error); + expect(err).to.have.property('message', '"exp" should be a number of seconds'); + }); + }); }); }); }); @@ -113,13 +141,15 @@ describe('expires', function() { {}, {foo: 'bar'}, ].forEach((exp) => { - it(`should error with with value ${util.inspect(exp)}`, function () { + it(`should error with with value ${util.inspect(exp)}`, function (done) { const encodedPayload = base64UrlEncode(JSON.stringify({exp})); const token = `${noneAlgorithmHeader}.${encodedPayload}.`; - expect(() => jwt.verify(token, undefined)).to.throw( - jwt.JsonWebTokenError, - 'invalid exp value' - ); + testUtils.verifyJWTHelper(token, undefined, {exp}, (err) => { + testUtils.asyncCheck(done, () => { + expect(err).to.be.instanceOf(jwt.JsonWebTokenError); + expect(err).to.have.property('message', 'invalid exp value'); + }); + }); }); }) }); @@ -134,144 +164,190 @@ describe('expires', function() { fakeClock.uninstall(); }); - it('should set correct "exp" with negative number of seconds', function() { - const token = signWithExpiresIn({}, -10); - fakeClock.tick(-10001); - - const decoded = jwt.decode(token); - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(50); + it('should set correct "exp" with negative number of seconds', function(done) { + signWithExpiresIn(-10, {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 50); + }); + }) + }); }); - it('should set correct "exp" with positive number of seconds', function() { - const token = signWithExpiresIn({}, 10); - - const decoded = jwt.decode(token); - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(70); + it('should set correct "exp" with positive number of seconds', function(done) { + signWithExpiresIn(10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 70); + }); + }) + }); }); - it('should set correct "exp" with zero seconds', function() { - const token = signWithExpiresIn({}, 0); - - fakeClock.tick(-1); - - const decoded = jwt.decode(token); - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(60); + it('should set correct "exp" with zero seconds', function(done) { + signWithExpiresIn(0, {}, (e1, token) => { + fakeClock.tick(-1); + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 60); + }); + }) + }); }); - it('should set correct "exp" with negative string timespan', function() { - const token = signWithExpiresIn({}, '-10 s'); - - fakeClock.tick(-10001); - - const decoded = jwt.decode(token); - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(50); + it('should set correct "exp" with negative string timespan', function(done) { + signWithExpiresIn('-10 s', {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 50); + }); + }) + }); }); - it('should set correct "exp" with positive string timespan', function() { - const token = signWithExpiresIn({}, '10 s'); - - fakeClock.tick(-10001); - const decoded = jwt.decode(token); - - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(70); + it('should set correct "exp" with positive string timespan', function(done) { + signWithExpiresIn('10 s', {}, (e1, token) => { + fakeClock.tick(-10001); + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 70); + }); + }) + }); }); - it('should set correct "exp" with zero string timespan', function() { - const token = signWithExpiresIn({}, '0 s'); - - fakeClock.tick(-1); - const decoded = jwt.decode(token); - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(60); + it('should set correct "exp" with zero string timespan', function(done) { + signWithExpiresIn('0 s', {}, (e1, token) => { + fakeClock.tick(-1); + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 60); + }); + }) + }); }); // TODO an exp of -Infinity should fail validation - it('should set null "exp" when given -Infinity', function () { - const token = signWithExpiresIn({exp: -Infinity}); - - const decoded = jwt.decode(token); - expect(decoded.exp).to.be.null; + it('should set null "exp" when given -Infinity', function (done) { + signWithExpiresIn(undefined, {exp: -Infinity}, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).to.be.null; + expect(decoded).to.have.property('exp', null); + }); + }); }); // TODO an exp of Infinity should fail validation - it('should set null "exp" when given value Infinity', function () { - const token = signWithExpiresIn({exp: Infinity}); - - const decoded = jwt.decode(token); - expect(decoded.exp).to.be.null; + it('should set null "exp" when given value Infinity', function (done) { + signWithExpiresIn(undefined, {exp: Infinity}, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).to.be.null; + expect(decoded).to.have.property('exp', null); + }); + }); }); // TODO an exp of NaN should fail validation - it('should set null "exp" when given value NaN', function () { - const token = signWithExpiresIn({exp: NaN}); - - const decoded = jwt.decode(token); - expect(decoded.exp).to.be.null; + it('should set null "exp" when given value NaN', function (done) { + signWithExpiresIn(undefined, {exp: NaN}, (err, token) => { + const decoded = jwt.decode(token); + testUtils.asyncCheck(done, () => { + expect(err).to.be.null; + expect(decoded).to.have.property('exp', null); + }); + }); }); - it('should set correct "exp" when "iat" is passed', function () { - const token = signWithExpiresIn({iat: 80}, -10); - - const decoded = jwt.decode(token); - - const verified = jwt.verify(token, undefined); - expect(decoded).to.deep.equal(verified); - expect(decoded.exp).to.equal(70); + it('should set correct "exp" when "iat" is passed', function (done) { + signWithExpiresIn(-10, {iat: 80}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('exp', 70); + }); + }) + }); }); - it('should verify "exp" using "clockTimestamp"', function () { - const token = signWithExpiresIn({}, 10); - - const verified = jwt.verify(token, undefined, {clockTimestamp: 69}); - expect(verified.iat).to.equal(60); - expect(verified.exp).to.equal(70); + it('should verify "exp" using "clockTimestamp"', function (done) { + signWithExpiresIn(10, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 69}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('iat', 60); + expect(decoded).to.have.property('exp', 70); + }); + }) + }); }); - it('should verify "exp" using "clockTolerance"', function () { - const token = signWithExpiresIn({}, 5); - - fakeClock.tick(10000); - - const verified = jwt.verify(token, undefined, {clockTolerance: 6}); - expect(verified.iat).to.equal(60); - expect(verified.exp).to.equal(65); + it('should verify "exp" using "clockTolerance"', function (done) { + signWithExpiresIn(5, {}, (e1, token) => { + fakeClock.tick(10000); + testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 6}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('iat', 60); + expect(decoded).to.have.property('exp', 65); + }); + }) + }); }); - it('should ignore a expired token when "ignoreExpiration" is true', function () { - const token = signWithExpiresIn({}, '-10 s'); - - const verified = jwt.verify(token, undefined, {ignoreExpiration: true}); - expect(verified.iat).to.equal(60); - expect(verified.exp).to.equal(50); + it('should ignore a expired token when "ignoreExpiration" is true', function (done) { + signWithExpiresIn('-10 s', {}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {ignoreExpiration: true}, (e2, decoded) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.null; + expect(decoded).to.have.property('iat', 60); + expect(decoded).to.have.property('exp', 50); + }); + }) + }); }); - it('should error on verify if "exp" is at current time', function() { - const token = signWithExpiresIn({exp: 60}); - - expect(() => jwt.verify(token, undefined)).to.throw( - jwt.TokenExpiredError, - 'jwt expired' - ); + it('should error on verify if "exp" is at current time', function(done) { + signWithExpiresIn(undefined, {exp: 60}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {}, (e2) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.instanceOf(jwt.TokenExpiredError); + expect(e2).to.have.property('message', 'jwt expired'); + }); + }); + }); }); - it('should error on verify if "exp" is before current time using clockTolerance', function () { - const token = signWithExpiresIn({}, -5); - - expect(() => jwt.verify(token, undefined, {clockTolerance: 5})).to.throw( - jwt.TokenExpiredError, - 'jwt expired' - ); + it('should error on verify if "exp" is before current time using clockTolerance', function (done) { + signWithExpiresIn(-5, {}, (e1, token) => { + testUtils.verifyJWTHelper(token, undefined, {clockTolerance: 5}, (e2) => { + testUtils.asyncCheck(done, () => { + expect(e1).to.be.null; + expect(e2).to.be.instanceOf(jwt.TokenExpiredError); + expect(e2).to.have.property('message', 'jwt expired'); + }); + }); + }); }); }); });