Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tests related to iat and maxAge #507

Merged
merged 1 commit into from
Aug 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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' });
Expand Down
Loading