diff --git a/package.json b/package.json index d84013c..2535a39 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "JSON Web Token implementation (symmetric and asymmetric)", "main": "index.js", "scripts": { - "test": "nyc --reporter=html --reporter=text mocha && nsp check && cost-of-modules" + "test:unit": "nyc --reporter=html --reporter=text mocha", + "test": "npm run test:unit && nsp check && cost-of-modules" }, "repository": { "type": "git", diff --git a/test/iat.tests.js b/test/iat.tests.js index 00647f1..953bdb3 100644 --- a/test/iat.tests.js +++ b/test/iat.tests.js @@ -12,13 +12,4 @@ describe('iat', function () { expect(result.exp).to.be.closeTo(iat + expiresIn, 0.2); }); - it('should work with a nbf calculated based on numeric iat', function () { - var dateNow = Math.floor(Date.now() / 1000); - var iat = dateNow - 30; - var notBefore = -50; - var token = jwt.sign({foo: 123, iat: iat}, '123', {notBefore: notBefore}); - var result = jwt.verify(token, '123'); - expect(result.nbf).to.equal(iat + notBefore); - }); - }); diff --git a/test/jwt.asymmetric_signing.tests.js b/test/jwt.asymmetric_signing.tests.js index 287a233..8fd612f 100644 --- a/test/jwt.asymmetric_signing.tests.js +++ b/test/jwt.asymmetric_signing.tests.js @@ -114,57 +114,6 @@ describe('Asymmetric Algorithms', function(){ }); }); - describe('when signing a token with not before', function () { - var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: -10 * 3600 }); - - it('should be valid expiration', function (done) { - jwt.verify(token, pub, function (err, decoded) { - assert.isNotNull(decoded); - assert.isNull(err); - done(); - }); - }); - - it('should be invalid', function (done) { - // not active token - token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: '10m' }); - - jwt.verify(token, pub, function (err, decoded) { - assert.isUndefined(decoded); - assert.isNotNull(err); - assert.equal(err.name, 'NotBeforeError'); - assert.instanceOf(err.date, Date); - assert.instanceOf(err, jwt.NotBeforeError); - done(); - }); - }); - - - it('should valid when date are equals', function (done) { - var fakeClock = sinon.useFakeTimers({now: 1451908031}); - - token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: 0 }); - - jwt.verify(token, pub, function (err, decoded) { - fakeClock.uninstall(); - assert.isNull(err); - assert.isNotNull(decoded); - done(); - }); - }); - - it('should NOT be invalid', function (done) { - // not active token - token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: '10m' }); - - jwt.verify(token, pub, { ignoreNotBefore: true }, function (err, decoded) { - assert.ok(decoded.foo); - assert.equal('bar', decoded.foo); - done(); - }); - }); - }); - describe('when signing a token with audience', function () { var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, audience: 'urn:foo' }); diff --git a/test/nbf.test.js b/test/nbf.test.js new file mode 100644 index 0000000..5c4d898 --- /dev/null +++ b/test/nbf.test.js @@ -0,0 +1,257 @@ +'use strict'; + +const jwt = require('../'); +const expect = require('chai').expect; +const sinon = require('sinon'); +const util = require('util'); + +function base64UrlEncode(str) { + return Buffer.from(str).toString('base64') + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_") + ; +} + +function signWithNoBefore(payload, notBefore) { + const options = {algorithm: 'none'}; + if (notBefore !== undefined) { + options.notBefore = notBefore; + } + return jwt.sign(payload, undefined, options); +} + +const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0'; + +describe('not before', function() { + describe('`jwt.sign` notBefore option validation', function () { + [ + true, + false, + null, + -1.1, + 1.1, + -Infinity, + Infinity, + NaN, + // TODO empty string currently fails + // '', + ' ', + 'invalid', + [], + ['foo'], + {}, + {foo: 'bar'}, + ].forEach((notBefore) => { + it(`should error with with value ${util.inspect(notBefore)}`, function () { + expect(() => signWithNoBefore({}, notBefore)).to.throw( + '"notBefore" should be a number of seconds or string representing a timespan' + ); + }); + }); + + // undefined needs special treatment because {} is not the same as {notBefore: undefined} + it('should error with with value undefined', function () { + expect(() =>jwt.sign({}, undefined, {notBefore: undefined, algorithm: 'none'})).to.throw( + '"notBefore" should be a number of seconds or string representing a timespan' + ); + }); + + it ('should error when "nbf" is in payload', function() { + expect(() => signWithNoBefore({nbf: 100}, 100)).to.throw( + 'Bad "options.notBefore" option the payload already has an "nbf" property.' + ); + }); + + it('should error with a string payload', function() { + expect(() => signWithNoBefore('a string payload', 100)).to.throw( + 'invalid notBefore option for string payload' + ); + }); + + it('should error with a Buffer payload', function() { + expect(() => signWithNoBefore(new Buffer('a Buffer payload'), 100)).to.throw( + 'invalid notBefore option for object payload' + ); + }); + }); + + describe('`jwt.sign` nbf claim validation', function () { + [ + true, + false, + null, + undefined, + // TODO should these fail in validation? + // -Infinity, + // Infinity, + // NaN, + '', + ' ', + 'invalid', + [], + ['foo'], + {}, + {foo: 'bar'}, + ].forEach((nbf) => { + it(`should error with with value ${util.inspect(nbf)}`, function () { + expect(() => signWithNoBefore({nbf})).to.throw( + '"nbf" should be a number of seconds' + ); + }); + }); + }); + + describe('nbf in payload validation', function () { + [ + true, + false, + null, + -Infinity, + Infinity, + NaN, + '', + ' ', + 'invalid', + [], + ['foo'], + {}, + {foo: 'bar'}, + ].forEach((nbf) => { + it(`should error with with value ${util.inspect(nbf)}`, function () { + const encodedPayload = base64UrlEncode(JSON.stringify({nbf})); + const token = `${noneAlgorithmHeader}.${encodedPayload}.`; + expect(() => jwt.verify(token, undefined)).to.throw( + jwt.JsonWebTokenError, + 'invalid nbf value' + ); + }); + }) + }); + + describe('when signing and verifying a token with notBefore option', function () { + let fakeClock; + beforeEach(function() { + fakeClock = sinon.useFakeTimers({now: 60000}); + }); + + afterEach(function() { + fakeClock.uninstall(); + }); + + + it('should set correct "nbf" with negative number of seconds', function() { + const token = signWithNoBefore({}, -10); + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(50); + }); + + it('should set correct "nbf" with positive number of seconds', function() { + const token = signWithNoBefore({}, 10); + + fakeClock.tick(10000); + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(70); + }); + + it('should set correct "nbf" with zero seconds', function() { + const token = signWithNoBefore({}, 0); + + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(60); + }); + + it('should set correct "nbf" with negative string timespan', function() { + const token = signWithNoBefore({}, '-10 s'); + + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(50); + }); + + + it('should set correct "nbf" with positive string timespan', function() { + const token = signWithNoBefore({}, '10 s'); + + fakeClock.tick(10000); + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(70); + }); + + it('should set correct "nbf" with zero string timespan', function() { + const token = signWithNoBefore({}, '0 s'); + + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(60); + }); + + it('should set correct "nbf" when "iat" is passed', function () { + const token = signWithNoBefore({iat: 40}, -10); + + const decoded = jwt.decode(token); + + const verified = jwt.verify(token, undefined); + expect(decoded).to.deep.equal(verified); + expect(decoded.nbf).to.equal(30); + }); + + it('should verify "nbf" using "clockTimestamp"', function () { + const token = signWithNoBefore({}, 10); + + const verified = jwt.verify(token, undefined, {clockTimestamp: 70}); + expect(verified.iat).to.equal(60); + expect(verified.nbf).to.equal(70); + }); + + it('should verify "nbf" using "clockTolerance"', function () { + const token = signWithNoBefore({}, 5); + + const verified = jwt.verify(token, undefined, {clockTolerance: 6}); + expect(verified.iat).to.equal(60); + expect(verified.nbf).to.equal(65); + }); + + it('should ignore a not active token when "ignoreNotBefore" is true', function () { + const token = signWithNoBefore({}, '10 s'); + + const verified = jwt.verify(token, undefined, {ignoreNotBefore: true}); + expect(verified.iat).to.equal(60); + expect(verified.nbf).to.equal(70); + }); + + it('should error on verify if "nbf" is after current time', function() { + const token = signWithNoBefore({nbf: 61}); + + expect(() => jwt.verify(token, undefined)).to.throw( + jwt.NotBeforeError, + 'jwt not active' + ); + }); + + it('should error on verify if "nbf" is after current time using clockTolerance', function () { + const token = signWithNoBefore({}, 5); + + expect(() => jwt.verify(token, undefined, {clockTolerance: 4})).to.throw( + jwt.NotBeforeError, + 'jwt not active' + ); + }); + }); +}); \ No newline at end of file diff --git a/test/schema.tests.js b/test/schema.tests.js index 1f79994..a6a9128 100644 --- a/test/schema.tests.js +++ b/test/schema.tests.js @@ -25,17 +25,6 @@ describe('schema', function() { sign({ expiresIn: 10 }); }); - it('should validate notBefore', function () { - expect(function () { - sign({ notBefore: '1 monkey' }); - }).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/); - expect(function () { - sign({ notBefore: 1.1 }); - }).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/); - sign({ notBefore: '10s' }); - sign({ notBefore: 10 }); - }); - it('should validate audience', function () { expect(function () { sign({ audience: 10 }); @@ -124,13 +113,6 @@ describe('schema', function() { sign({ exp: 10.1 }); }); - it('should validate nbf', function () { - expect(function () { - sign({ nbf: '1 monkey' }); - }).to.throw(/"nbf" should be a number of seconds/); - sign({ nbf: 10.1 }); - }); - }); }); \ No newline at end of file diff --git a/test/verify.tests.js b/test/verify.tests.js index 51d107e..3d728ac 100644 --- a/test/verify.tests.js +++ b/test/verify.tests.js @@ -333,33 +333,6 @@ describe('verify', function() { done(); }); }); - it('should verify valid token with nbf', function (done) { - var token = jwt.sign({ - foo: 'bar', - iat: clockTimestamp, - nbf: clockTimestamp + 1, - exp: clockTimestamp + 2 - }, key); - jwt.verify(token, key, {clockTimestamp: clockTimestamp + 1}, function (err, p) { - assert.isNull(err); - done(); - }); - }); - it('should error on token used before nbf', function (done) { - var token = jwt.sign({ - foo: 'bar', - iat: clockTimestamp, - nbf: clockTimestamp + 1, - exp: clockTimestamp + 2 - }, key); - jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err, p) { - assert.equal(err.name, 'NotBeforeError'); - assert.equal(err.date.constructor.name, 'Date'); - assert.equal(Number(err.date), (clockTimestamp + 1) * 1000); - assert.isUndefined(p); - done(); - }); - }); }); describe('option: maxAge and clockTimestamp', function () {