Skip to content

Commit 877bd57

Browse files
MitMaroziluvatar
authored andcommitted
Refactor tests related to iat and maxAge (auth0#507)
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.
1 parent 5a7fa23 commit 877bd57

File tree

4 files changed

+343
-186
lines changed

4 files changed

+343
-186
lines changed

test/claim-iat.test.js

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
'use strict';
2+
3+
const jwt = require('../');
4+
const expect = require('chai').expect;
5+
const sinon = require('sinon');
6+
const util = require('util');
7+
const testUtils = require('./test-utils');
8+
9+
const base64UrlEncode = testUtils.base64UrlEncode;
10+
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';
11+
12+
function signWithIssueAtSync(issueAt, options) {
13+
const payload = {};
14+
if (issueAt !== undefined) {
15+
payload.iat = issueAt;
16+
}
17+
const opts = Object.assign({algorithm: 'none'}, options);
18+
return jwt.sign(payload, undefined, opts);
19+
}
20+
21+
function signWithIssueAtAsync(issueAt, options, cb) {
22+
const payload = {};
23+
if (issueAt !== undefined) {
24+
payload.iat = issueAt;
25+
}
26+
const opts = Object.assign({algorithm: 'none'}, options);
27+
// async calls require a truthy secret
28+
// see: https://github.com/brianloveswords/node-jws/issues/62
29+
return jwt.sign(payload, 'secret', opts, cb);
30+
}
31+
32+
function verifyWithIssueAtSync(token, maxAge, options) {
33+
const opts = Object.assign({maxAge}, options);
34+
return jwt.verify(token, undefined, opts)
35+
}
36+
37+
function verifyWithIssueAtAsync(token, maxAge, options, cb) {
38+
const opts = Object.assign({maxAge}, options);
39+
return jwt.verify(token, undefined, opts, cb)
40+
}
41+
42+
describe('issue at', function() {
43+
describe('`jwt.sign` "iat" claim validation', function () {
44+
[
45+
true,
46+
false,
47+
null,
48+
'',
49+
'invalid',
50+
[],
51+
['foo'],
52+
{},
53+
{foo: 'bar'},
54+
].forEach((iat) => {
55+
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
56+
expect(() => signWithIssueAtSync(iat, {})).to.throw('"iat" should be a number of seconds');
57+
signWithIssueAtAsync(iat, {}, (err) => {
58+
expect(err.message).to.equal('"iat" should be a number of seconds');
59+
done();
60+
});
61+
});
62+
});
63+
64+
// undefined needs special treatment because {} is not the same as {iat: undefined}
65+
it('should error with iat of undefined', function (done) {
66+
expect(() => jwt.sign({iat: undefined}, undefined, {algorithm: 'none'})).to.throw(
67+
'"iat" should be a number of seconds'
68+
);
69+
jwt.sign({iat: undefined}, undefined, {algorithm: 'none'}, (err) => {
70+
expect(err.message).to.equal('"iat" should be a number of seconds');
71+
done();
72+
});
73+
});
74+
});
75+
76+
describe('"iat" in payload with "maxAge" option validation', function () {
77+
[
78+
true,
79+
false,
80+
null,
81+
undefined,
82+
-Infinity,
83+
Infinity,
84+
NaN,
85+
'',
86+
'invalid',
87+
[],
88+
['foo'],
89+
{},
90+
{foo: 'bar'},
91+
].forEach((iat) => {
92+
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
93+
const encodedPayload = base64UrlEncode(JSON.stringify({iat}));
94+
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
95+
expect(() => verifyWithIssueAtSync(token, '1 min', {})).to.throw(
96+
jwt.JsonWebTokenError, 'iat required when maxAge is specified'
97+
);
98+
99+
verifyWithIssueAtAsync(token, '1 min', {}, (err) => {
100+
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
101+
expect(err.message).to.equal('iat required when maxAge is specified');
102+
done();
103+
});
104+
});
105+
})
106+
});
107+
108+
describe('when signing a token', function () {
109+
let fakeClock;
110+
beforeEach(function () {
111+
fakeClock = sinon.useFakeTimers({now: 60000});
112+
});
113+
114+
afterEach(function () {
115+
fakeClock.uninstall();
116+
});
117+
118+
[
119+
{
120+
description: 'should default to current time for "iat"',
121+
iat: undefined,
122+
expectedIssueAt: 60,
123+
options: {}
124+
},
125+
{
126+
description: 'should sign with provided time for "iat"',
127+
iat: 100,
128+
expectedIssueAt: 100,
129+
options: {}
130+
},
131+
// TODO an iat of -Infinity should fail validation
132+
{
133+
description: 'should set null "iat" when given -Infinity',
134+
iat: -Infinity,
135+
expectedIssueAt: null,
136+
options: {}
137+
},
138+
// TODO an iat of Infinity should fail validation
139+
{
140+
description: 'should set null "iat" when given Infinity',
141+
iat: Infinity,
142+
expectedIssueAt: null,
143+
options: {}
144+
},
145+
// TODO an iat of NaN should fail validation
146+
{
147+
description: 'should set to current time for "iat" when given value NaN',
148+
iat: NaN,
149+
expectedIssueAt: 60,
150+
options: {}
151+
},
152+
{
153+
description: 'should remove default "iat" with "noTimestamp" option',
154+
iat: undefined,
155+
expectedIssueAt: undefined,
156+
options: {noTimestamp: true}
157+
},
158+
{
159+
description: 'should remove provided "iat" with "noTimestamp" option',
160+
iat: 10,
161+
expectedIssueAt: undefined,
162+
options: {noTimestamp: true}
163+
},
164+
].forEach((testCase) => {
165+
it(testCase.description, function (done) {
166+
const token = signWithIssueAtSync(testCase.iat, testCase.options);
167+
expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt);
168+
signWithIssueAtAsync(testCase.iat, testCase.options, (err, token) => {
169+
// node-jsw catches the error from expect, so we have to wrap it in try/catch and use done(error)
170+
try {
171+
expect(err).to.be.null;
172+
expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt);
173+
done();
174+
}
175+
catch (e) {
176+
done(e);
177+
}
178+
});
179+
});
180+
});
181+
});
182+
183+
describe('when verifying a token', function() {
184+
let token;
185+
let fakeClock;
186+
187+
beforeEach(function() {
188+
fakeClock = sinon.useFakeTimers({now: 60000});
189+
});
190+
191+
afterEach(function () {
192+
fakeClock.uninstall();
193+
});
194+
195+
[
196+
{
197+
description: 'should verify using "iat" before the "maxAge"',
198+
clockAdvance: 10000,
199+
maxAge: 11,
200+
options: {},
201+
},
202+
{
203+
description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp',
204+
clockAdvance: 60000,
205+
maxAge: 11,
206+
options: {clockTimestamp: 70},
207+
},
208+
{
209+
description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"',
210+
clockAdvance: 10000,
211+
maxAge: 9,
212+
options: {clockTimestamp: 2},
213+
},
214+
].forEach((testCase) => {
215+
it(testCase.description, function (done) {
216+
const token = signWithIssueAtSync(undefined, {});
217+
fakeClock.tick(testCase.clockAdvance);
218+
expect(verifyWithIssueAtSync(token, testCase.maxAge, testCase.options)).to.not.throw;
219+
verifyWithIssueAtAsync(token, testCase.maxAge, testCase.options, done)
220+
});
221+
});
222+
223+
[
224+
{
225+
description: 'should throw using "iat" equal to the "maxAge"',
226+
clockAdvance: 10000,
227+
maxAge: 10,
228+
options: {},
229+
expectedError: 'maxAge exceeded',
230+
expectedExpiresAt: 70000,
231+
},
232+
{
233+
description: 'should throw using "iat" after the "maxAge"',
234+
clockAdvance: 10000,
235+
maxAge: 9,
236+
options: {},
237+
expectedError: 'maxAge exceeded',
238+
expectedExpiresAt: 69000,
239+
},
240+
{
241+
description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp',
242+
clockAdvance: 60000,
243+
maxAge: 10,
244+
options: {clockTimestamp: 70},
245+
expectedError: 'maxAge exceeded',
246+
expectedExpiresAt: 70000,
247+
},
248+
{
249+
description: 'should throw using "iat" after the "maxAge" and "clockTolerance',
250+
clockAdvance: 10000,
251+
maxAge: 8,
252+
options: {clockTolerance: 2},
253+
expectedError: 'maxAge exceeded',
254+
expectedExpiresAt: 68000,
255+
},
256+
].forEach((testCase) => {
257+
it(testCase.description, function(done) {
258+
const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt);
259+
token = signWithIssueAtSync(undefined, {});
260+
fakeClock.tick(testCase.clockAdvance);
261+
expect(() => verifyWithIssueAtSync(token, testCase.maxAge, {}))
262+
.to.throw(jwt.TokenExpiredError, testCase.expectedError)
263+
.to.have.property('expiredAt').that.deep.equals(expectedExpiresAtDate);
264+
verifyWithIssueAtAsync(token, testCase.maxAge, {}, (err) => {
265+
expect(err).to.be.instanceOf(jwt.TokenExpiredError);
266+
expect(err.message).to.equal(testCase.expectedError);
267+
expect(err.expiredAt).to.deep.equal(expectedExpiresAtDate);
268+
done();
269+
});
270+
});
271+
});
272+
});
273+
});

test/option-maxAge.test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
const jwt = require('../');
4+
const expect = require('chai').expect;
5+
const sinon = require('sinon');
6+
const util = require('util');
7+
8+
describe('maxAge option', function() {
9+
let token;
10+
11+
let fakeClock;
12+
beforeEach(function() {
13+
fakeClock = sinon.useFakeTimers({now: 60000});
14+
token = jwt.sign({iat: 70}, undefined, {algorithm: 'none'});
15+
});
16+
17+
afterEach(function() {
18+
fakeClock.uninstall();
19+
});
20+
21+
[
22+
{
23+
description: 'should work with a positive string value',
24+
maxAge: '3s',
25+
},
26+
{
27+
description: 'should work with a negative string value',
28+
maxAge: '-3s',
29+
},
30+
{
31+
description: 'should work with a positive numeric value',
32+
maxAge: 3,
33+
},
34+
{
35+
description: 'should work with a negative numeric value',
36+
maxAge: -3,
37+
},
38+
].forEach((testCase) => {
39+
it(testCase.description, function (done) {
40+
expect(jwt.verify(token, undefined, {maxAge: '3s'})).to.not.throw;
41+
jwt.verify(token, undefined, {maxAge: testCase.maxAge}, (err) => {
42+
expect(err).to.be.null;
43+
done();
44+
})
45+
});
46+
});
47+
48+
[
49+
true,
50+
'invalid',
51+
[],
52+
['foo'],
53+
{},
54+
{foo: 'bar'},
55+
].forEach((maxAge) => {
56+
it(`should error with value ${util.inspect(maxAge)}`, function (done) {
57+
expect(() => jwt.verify(token, undefined, {maxAge})).to.throw(
58+
jwt.JsonWebTokenError,
59+
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
60+
);
61+
jwt.verify(token, undefined, {maxAge}, (err) => {
62+
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
63+
expect(err.message).to.equal(
64+
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
65+
);
66+
done();
67+
})
68+
});
69+
});
70+
});

test/schema.tests.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,6 @@ describe('schema', function() {
7373
jwt.sign(payload, 'foo123');
7474
}
7575

76-
it('should validate iat', function () {
77-
expect(function () {
78-
sign({ iat: '1 monkey' });
79-
}).to.throw(/"iat" should be a number of seconds/);
80-
sign({ iat: 10.1 });
81-
});
82-
8376
it('should validate exp', function () {
8477
expect(function () {
8578
sign({ exp: '1 monkey' });

0 commit comments

Comments
 (0)