Skip to content

Commit 72e8ad4

Browse files
committed
Enhance validator error messages and add tests
1 parent 2d12812 commit 72e8ad4

File tree

4 files changed

+155
-25
lines changed

4 files changed

+155
-25
lines changed

sign.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@ var isString = require('lodash.isstring');
1111
var once = require('lodash.once');
1212

1313
var sign_options_schema = {
14-
expiresIn: function(value) { return isInteger(value) || isString(value); },
15-
notBefore: function(value) { return isInteger(value) || isString(value); },
16-
audience: function(value) { return isString(value) || isArray(value); },
17-
algorithm: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']),
18-
header: isPlainObject,
19-
encoding: isString,
20-
issuer: isString,
21-
subject: isString,
22-
jwtid: isString,
23-
noTimestamp: isBoolean,
24-
keyid: isString
14+
expiresIn: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
15+
notBefore: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
16+
audience: { isValid: function(value) { return isString(value) || isArray(value); }, message: '"audience" must be a string or array' },
17+
algorithm: { isValid: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']), message: '"algorithm" must be a valid string enum value' },
18+
header: { isValid: isPlainObject, message: '"header" must be an object' },
19+
encoding: { isValid: isString, message: '"encoding" must be a string' },
20+
issuer: { isValid: isString, message: '"issuer" must be a string' },
21+
subject: { isValid: isString, message: '"subject" must be a string' },
22+
jwtid: { isValid: isString, message: '"jwtid" must be a string' },
23+
noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' },
24+
keyid: { isValid: isString, message: '"keyid" must be a string' },
2525
};
2626

2727
var registered_claims_schema = {
28-
iat: isNumber,
29-
exp: isNumber,
30-
nbf: isNumber
28+
iat: { isValid: isNumber, message: '"iat" should be a number of seconds' },
29+
exp: { isValid: isNumber, message: '"exp" should be a number of seconds' },
30+
nbf: { isValid: isNumber, message: '"nbf" should be a number of seconds' }
3131
};
3232

3333
function validate(schema, unknown, object) {
@@ -36,14 +36,15 @@ function validate(schema, unknown, object) {
3636
}
3737
Object.keys(object)
3838
.forEach(function(key) {
39-
if (schema[key] == null) {
39+
var validator = schema[key];
40+
if (!validator) {
4041
if (!unknown) {
4142
throw new Error('"' + key + '" is not allowed');
4243
}
4344
return;
4445
}
45-
if (!schema[key](object[key])) {
46-
throw new Error('"' + key + '" is not the correct type');
46+
if (!validator.isValid(object[key])) {
47+
throw new Error(validator.message);
4748
}
4849
});
4950
}

test/expires_format.tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('expires option', function() {
3333
it('should throw if expires is not an string or number', function () {
3434
expect(function () {
3535
jwt.sign({foo: 123}, '123', { expiresIn: { crazy : 213 } });
36-
}).to.throw(/"expiresIn" is not the correct type/);
36+
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
3737
});
3838

3939
it('should throw an error if expiresIn and exp are provided', function () {

test/iat.tests.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,4 @@ describe('iat', function () {
1212
expect(result.exp).to.be.closeTo(iat + expiresIn, 0.2);
1313
});
1414

15-
16-
it('should throw if iat is not a number', function () {
17-
expect(function () {
18-
jwt.sign({foo: 123, iat: 'hello'}, '123');
19-
}).to.throw(/"iat" is not the correct type/);
20-
});
21-
2215
});

test/schema.tests.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
var jwt = require('../index');
2+
var expect = require('chai').expect;
3+
var fs = require('fs');
4+
5+
describe('schema', function() {
6+
7+
describe('sign options', function() {
8+
9+
var cert_rsa_priv = fs.readFileSync(__dirname + '/rsa-private.pem');
10+
var cert_ecdsa_priv = fs.readFileSync(__dirname + '/ecdsa-private.pem');
11+
12+
function sign(options) {
13+
var isEcdsa = options.algorithm && options.algorithm.indexOf('ES') === 0;
14+
jwt.sign({foo: 123}, isEcdsa ? cert_ecdsa_priv : cert_rsa_priv, options);
15+
}
16+
17+
it('should validate expiresIn', function () {
18+
expect(function () {
19+
sign({ expiresIn: '1 monkey' });
20+
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
21+
expect(function () {
22+
sign({ expiresIn: 1.1 });
23+
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
24+
sign({ expiresIn: '10s' });
25+
sign({ expiresIn: 10 });
26+
});
27+
28+
it('should validate notBefore', function () {
29+
expect(function () {
30+
sign({ notBefore: '1 monkey' });
31+
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
32+
expect(function () {
33+
sign({ notBefore: 1.1 });
34+
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
35+
sign({ notBefore: '10s' });
36+
sign({ notBefore: 10 });
37+
});
38+
39+
it('should validate audience', function () {
40+
expect(function () {
41+
sign({ audience: 10 });
42+
}).to.throw(/"audience" must be a string or array/);
43+
sign({ audience: 'urn:foo' });
44+
sign({ audience: ['urn:foo'] });
45+
});
46+
47+
it('should validate algorithm', function () {
48+
expect(function () {
49+
sign({ algorithm: 'foo' });
50+
}).to.throw(/"algorithm" must be a valid string enum value/);
51+
sign({algorithm: 'RS256'});
52+
sign({algorithm: 'RS384'});
53+
sign({algorithm: 'RS512'});
54+
sign({algorithm: 'ES256'});
55+
sign({algorithm: 'ES384'});
56+
sign({algorithm: 'ES512'});
57+
sign({algorithm: 'HS256'});
58+
sign({algorithm: 'HS384'});
59+
sign({algorithm: 'HS512'});
60+
sign({algorithm: 'none'});
61+
});
62+
63+
it('should validate header', function () {
64+
expect(function () {
65+
sign({ header: 'foo' });
66+
}).to.throw(/"header" must be an object/);
67+
sign({header: {}});
68+
});
69+
70+
it('should validate encoding', function () {
71+
expect(function () {
72+
sign({ encoding: 10 });
73+
}).to.throw(/"encoding" must be a string/);
74+
sign({encoding: 'utf8'});
75+
});
76+
77+
it('should validate issuer', function () {
78+
expect(function () {
79+
sign({ issuer: 10 });
80+
}).to.throw(/"issuer" must be a string/);
81+
sign({issuer: 'foo'});
82+
});
83+
84+
it('should validate subject', function () {
85+
expect(function () {
86+
sign({ subject: 10 });
87+
}).to.throw(/"subject" must be a string/);
88+
sign({subject: 'foo'});
89+
});
90+
91+
it('should validate noTimestamp', function () {
92+
expect(function () {
93+
sign({ noTimestamp: 10 });
94+
}).to.throw(/"noTimestamp" must be a boolean/);
95+
sign({noTimestamp: true});
96+
});
97+
98+
it('should validate keyid', function () {
99+
expect(function () {
100+
sign({ keyid: 10 });
101+
}).to.throw(/"keyid" must be a string/);
102+
sign({keyid: 'foo'});
103+
});
104+
105+
});
106+
107+
describe('sign payload registered claims', function() {
108+
109+
function sign(payload) {
110+
jwt.sign(payload, 'foo123');
111+
}
112+
113+
it('should validate iat', function () {
114+
expect(function () {
115+
sign({ iat: '1 monkey' });
116+
}).to.throw(/"iat" should be a number of seconds/);
117+
sign({ iat: 10.1 });
118+
});
119+
120+
it('should validate exp', function () {
121+
expect(function () {
122+
sign({ exp: '1 monkey' });
123+
}).to.throw(/"exp" should be a number of seconds/);
124+
sign({ exp: 10.1 });
125+
});
126+
127+
it('should validate nbf', function () {
128+
expect(function () {
129+
sign({ nbf: '1 monkey' });
130+
}).to.throw(/"nbf" should be a number of seconds/);
131+
sign({ nbf: 10.1 });
132+
});
133+
134+
});
135+
136+
});

0 commit comments

Comments
 (0)