Skip to content

Commit

Permalink
Refactor tests related to iat and maxAge (#507)
Browse files Browse the repository at this point in the history
This change extracts all tests related to the iat claim and the maxAge
verify option into two test files. Several additional tests are added
that were missing from the existing tests.
  • Loading branch information
MitMaro authored and ziluvatar committed Aug 26, 2018
1 parent 5a7fa23 commit 877bd57
Showing 4 changed files with 343 additions and 186 deletions.
273 changes: 273 additions & 0 deletions test/claim-iat.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');
const testUtils = require('./test-utils');

const base64UrlEncode = testUtils.base64UrlEncode;
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';

function signWithIssueAtSync(issueAt, options) {
const payload = {};
if (issueAt !== undefined) {
payload.iat = issueAt;
}
const opts = Object.assign({algorithm: 'none'}, options);
return jwt.sign(payload, undefined, opts);
}

function signWithIssueAtAsync(issueAt, options, cb) {
const payload = {};
if (issueAt !== undefined) {
payload.iat = issueAt;
}
const opts = Object.assign({algorithm: 'none'}, options);
// async calls require a truthy secret
// see: https://github.com/brianloveswords/node-jws/issues/62
return jwt.sign(payload, 'secret', opts, cb);
}

function verifyWithIssueAtSync(token, maxAge, options) {
const opts = Object.assign({maxAge}, options);
return jwt.verify(token, undefined, opts)
}

function verifyWithIssueAtAsync(token, maxAge, options, cb) {
const opts = Object.assign({maxAge}, options);
return jwt.verify(token, undefined, opts, cb)
}

describe('issue at', function() {
describe('`jwt.sign` "iat" claim validation', function () {
[
true,
false,
null,
'',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((iat) => {
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
expect(() => signWithIssueAtSync(iat, {})).to.throw('"iat" should be a number of seconds');
signWithIssueAtAsync(iat, {}, (err) => {
expect(err.message).to.equal('"iat" should be a number of seconds');
done();
});
});
});

// undefined needs special treatment because {} is not the same as {iat: undefined}
it('should error with iat of undefined', function (done) {
expect(() => jwt.sign({iat: undefined}, undefined, {algorithm: 'none'})).to.throw(
'"iat" should be a number of seconds'
);
jwt.sign({iat: undefined}, undefined, {algorithm: 'none'}, (err) => {
expect(err.message).to.equal('"iat" should be a number of seconds');
done();
});
});
});

describe('"iat" in payload with "maxAge" option validation', function () {
[
true,
false,
null,
undefined,
-Infinity,
Infinity,
NaN,
'',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((iat) => {
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
const encodedPayload = base64UrlEncode(JSON.stringify({iat}));
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
expect(() => verifyWithIssueAtSync(token, '1 min', {})).to.throw(
jwt.JsonWebTokenError, 'iat required when maxAge is specified'
);

verifyWithIssueAtAsync(token, '1 min', {}, (err) => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err.message).to.equal('iat required when maxAge is specified');
done();
});
});
})
});

describe('when signing a token', function () {
let fakeClock;
beforeEach(function () {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function () {
fakeClock.uninstall();
});

[
{
description: 'should default to current time for "iat"',
iat: undefined,
expectedIssueAt: 60,
options: {}
},
{
description: 'should sign with provided time for "iat"',
iat: 100,
expectedIssueAt: 100,
options: {}
},
// TODO an iat of -Infinity should fail validation
{
description: 'should set null "iat" when given -Infinity',
iat: -Infinity,
expectedIssueAt: null,
options: {}
},
// TODO an iat of Infinity should fail validation
{
description: 'should set null "iat" when given Infinity',
iat: Infinity,
expectedIssueAt: null,
options: {}
},
// TODO an iat of NaN should fail validation
{
description: 'should set to current time for "iat" when given value NaN',
iat: NaN,
expectedIssueAt: 60,
options: {}
},
{
description: 'should remove default "iat" with "noTimestamp" option',
iat: undefined,
expectedIssueAt: undefined,
options: {noTimestamp: true}
},
{
description: 'should remove provided "iat" with "noTimestamp" option',
iat: 10,
expectedIssueAt: undefined,
options: {noTimestamp: true}
},
].forEach((testCase) => {
it(testCase.description, function (done) {
const token = signWithIssueAtSync(testCase.iat, testCase.options);
expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt);
signWithIssueAtAsync(testCase.iat, testCase.options, (err, token) => {
// node-jsw catches the error from expect, so we have to wrap it in try/catch and use done(error)
try {
expect(err).to.be.null;
expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt);
done();
}
catch (e) {
done(e);
}
});
});
});
});

describe('when verifying a token', function() {
let token;
let fakeClock;

beforeEach(function() {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function () {
fakeClock.uninstall();
});

[
{
description: 'should verify using "iat" before the "maxAge"',
clockAdvance: 10000,
maxAge: 11,
options: {},
},
{
description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp',
clockAdvance: 60000,
maxAge: 11,
options: {clockTimestamp: 70},
},
{
description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"',
clockAdvance: 10000,
maxAge: 9,
options: {clockTimestamp: 2},
},
].forEach((testCase) => {
it(testCase.description, function (done) {
const token = signWithIssueAtSync(undefined, {});
fakeClock.tick(testCase.clockAdvance);
expect(verifyWithIssueAtSync(token, testCase.maxAge, testCase.options)).to.not.throw;
verifyWithIssueAtAsync(token, testCase.maxAge, testCase.options, done)
});
});

[
{
description: 'should throw using "iat" equal to the "maxAge"',
clockAdvance: 10000,
maxAge: 10,
options: {},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 70000,
},
{
description: 'should throw using "iat" after the "maxAge"',
clockAdvance: 10000,
maxAge: 9,
options: {},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 69000,
},
{
description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp',
clockAdvance: 60000,
maxAge: 10,
options: {clockTimestamp: 70},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 70000,
},
{
description: 'should throw using "iat" after the "maxAge" and "clockTolerance',
clockAdvance: 10000,
maxAge: 8,
options: {clockTolerance: 2},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 68000,
},
].forEach((testCase) => {
it(testCase.description, function(done) {
const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt);
token = signWithIssueAtSync(undefined, {});
fakeClock.tick(testCase.clockAdvance);
expect(() => verifyWithIssueAtSync(token, testCase.maxAge, {}))
.to.throw(jwt.TokenExpiredError, testCase.expectedError)
.to.have.property('expiredAt').that.deep.equals(expectedExpiresAtDate);
verifyWithIssueAtAsync(token, testCase.maxAge, {}, (err) => {
expect(err).to.be.instanceOf(jwt.TokenExpiredError);
expect(err.message).to.equal(testCase.expectedError);
expect(err.expiredAt).to.deep.equal(expectedExpiresAtDate);
done();
});
});
});
});
});
70 changes: 70 additions & 0 deletions test/option-maxAge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');

describe('maxAge option', function() {
let token;

let fakeClock;
beforeEach(function() {
fakeClock = sinon.useFakeTimers({now: 60000});
token = jwt.sign({iat: 70}, undefined, {algorithm: 'none'});
});

afterEach(function() {
fakeClock.uninstall();
});

[
{
description: 'should work with a positive string value',
maxAge: '3s',
},
{
description: 'should work with a negative string value',
maxAge: '-3s',
},
{
description: 'should work with a positive numeric value',
maxAge: 3,
},
{
description: 'should work with a negative numeric value',
maxAge: -3,
},
].forEach((testCase) => {
it(testCase.description, function (done) {
expect(jwt.verify(token, undefined, {maxAge: '3s'})).to.not.throw;
jwt.verify(token, undefined, {maxAge: testCase.maxAge}, (err) => {
expect(err).to.be.null;
done();
})
});
});

[
true,
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((maxAge) => {
it(`should error with value ${util.inspect(maxAge)}`, function (done) {
expect(() => jwt.verify(token, undefined, {maxAge})).to.throw(
jwt.JsonWebTokenError,
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
);
jwt.verify(token, undefined, {maxAge}, (err) => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err.message).to.equal(
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
);
done();
})
});
});
});
7 changes: 0 additions & 7 deletions test/schema.tests.js
Original file line number Diff line number Diff line change
@@ -73,13 +73,6 @@ describe('schema', function() {
jwt.sign(payload, 'foo123');
}

it('should validate iat', function () {
expect(function () {
sign({ iat: '1 monkey' });
}).to.throw(/"iat" should be a number of seconds/);
sign({ iat: 10.1 });
});

it('should validate exp', function () {
expect(function () {
sign({ exp: '1 monkey' });
Loading

0 comments on commit 877bd57

Please sign in to comment.