Skip to content

Commit 60cb403

Browse files
committedOct 1, 2016
add typed error handling
1 parent 9e177c3 commit 60cb403

9 files changed

+547
-80
lines changed
 

‎README.md

+25
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,31 @@ jwt.setNotbefore(); // Remove the exp claim
219219
```
220220

221221

222+
## Error Handling
223+
224+
If an error occurs then one of the following error types will either be thrown
225+
or passed as the first argument in a callback.
226+
227+
Type | Description
228+
----------------------------------------|--------------------------------------
229+
JwtError | Base type. All errors implement this.
230+
JwtParseError | Jwt cannot be parsed.
231+
UnsupportedSigningAlgorithmJwtError | Unsupported signing algorithm.
232+
SigningKeyRequiredJwtError | Signing key is required.
233+
NotActiveJwtParseError | Jwt not active.
234+
ExpiredJwtParseError | Jwt is expired.
235+
SignatureAlgorithmMismatchJwtParseError | Unexpected signature algorithm.
236+
SignatureMismatchJwtParseError | Signature verification failed.
237+
238+
To handle a specific error, simply compare the instance of the error received to one of
239+
the error types above, as shown in the example below:
240+
241+
```javascript
242+
if (err instanceof nJwt.JwtParseError) {
243+
console.log('Unable to parse the provided jwt.');
244+
}
245+
```
246+
222247
## Supported Algorithms
223248

224249
"alg" Value | Algorithm used

‎errors.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
var util = require('util');
2+
3+
function JwtError(message) {
4+
this.name = 'JwtError';
5+
this.message = this.userMessage = message;
6+
}
7+
util.inherits(JwtError, Error);
8+
9+
function JwtParseError(jwtString, parsedHeader, parsedBody, innerError) {
10+
this.name = 'JwtParseError';
11+
this.message = this.userMessage = 'Jwt cannot be parsed';
12+
this.jwtString = jwtString;
13+
this.parsedHeader = parsedHeader;
14+
this.parsedBody = parsedBody;
15+
this.innerError = innerError;
16+
}
17+
util.inherits(JwtParseError, JwtError);
18+
19+
function UnsupportedSigningAlgorithmJwtError() {
20+
UnsupportedSigningAlgorithmJwtError.super_.call(this, 'Unsupported signing algorithm');
21+
this.name = 'UnsupportedSigningAlgorithmJwtError';
22+
}
23+
util.inherits(UnsupportedSigningAlgorithmJwtError, JwtError);
24+
25+
function SigningKeyRequiredJwtError() {
26+
SigningKeyRequiredJwtError.super_.call(this, 'Signing key is required');
27+
this.name = 'SigningKeyRequiredJwtError';
28+
}
29+
util.inherits(SigningKeyRequiredJwtError, JwtError);
30+
31+
function NotActiveJwtParseError(jwtString, parsedHeader, parsedBody) {
32+
NotActiveJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody);
33+
this.name = 'NotActiveJwtParseError';
34+
this.message = this.userMessage = 'Jwt not active';
35+
}
36+
util.inherits(NotActiveJwtParseError, JwtParseError);
37+
38+
function ExpiredJwtParseError(jwtString, parsedHeader, parsedBody) {
39+
ExpiredJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody);
40+
this.name = 'ExpiredJwtParseError';
41+
this.message = this.userMessage = 'Jwt is expired';
42+
}
43+
util.inherits(ExpiredJwtParseError, JwtParseError);
44+
45+
function SignatureAlgorithmMismatchJwtParseError(jwtString, parsedHeader, parsedBody) {
46+
SignatureAlgorithmMismatchJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody);
47+
this.name = 'SignatureAlgorithmMismatchJwtParseError';
48+
this.message = this.userMessage = 'Unexpected signature algorithm';
49+
}
50+
util.inherits(SignatureAlgorithmMismatchJwtParseError, JwtParseError);
51+
52+
function SignatureMismatchJwtParseError(jwtString, parsedHeader, parsedBody, innerError) {
53+
SignatureMismatchJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody, innerError);
54+
this.name = 'SignatureMismatchJwtParseError';
55+
this.message = this.userMessage = 'Signature verification failed';
56+
}
57+
util.inherits(SignatureMismatchJwtParseError, JwtParseError);
58+
59+
module.exports = {
60+
JwtError: JwtError,
61+
JwtParseError: JwtParseError,
62+
UnsupportedSigningAlgorithmJwtError: UnsupportedSigningAlgorithmJwtError,
63+
SigningKeyRequiredJwtError: SigningKeyRequiredJwtError,
64+
NotActiveJwtParseError: NotActiveJwtParseError,
65+
ExpiredJwtParseError: ExpiredJwtParseError,
66+
SignatureAlgorithmMismatchJwtParseError: SignatureAlgorithmMismatchJwtParseError,
67+
SignatureMismatchJwtParseError: SignatureMismatchJwtParseError
68+
};

‎index.js

+30-39
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use strict';
22

3-
var util = require('util');
43
var uuid = require('uuid');
54
var crypto = require('crypto');
65
var ecdsaSigFormatter = require('ecdsa-sig-formatter');
7-
var properties = require('./properties.json');
6+
var errors = require('./errors');
87

98
var algCryptoMap = {
109
HS256: 'SHA256',
@@ -68,22 +67,6 @@ function handleError(cb,err,value){
6867
}
6968
}
7069

71-
function JwtError(message) {
72-
this.name = 'JwtError';
73-
this.message = this.userMessage = message;
74-
}
75-
util.inherits(JwtError, Error);
76-
77-
function JwtParseError(message,jwtString,parsedHeader,parsedBody,innerError) {
78-
this.name = 'JwtParseError';
79-
this.message = this.userMessage = message;
80-
this.jwtString = jwtString;
81-
this.parsedHeader = parsedHeader;
82-
this.parsedBody = parsedBody;
83-
this.innerError = innerError;
84-
}
85-
util.inherits(JwtParseError, Error);
86-
8770
function JwtBody(claims){
8871
if(!(this instanceof JwtBody)){
8972
return new JwtBody(claims);
@@ -194,7 +177,7 @@ Jwt.prototype.setSigningKey = function setSigningKey(key) {
194177
};
195178
Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
196179
if(!this.isSupportedAlg(alg)){
197-
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
180+
throw new errors.UnsupportedSigningAlgorithmJwtError();
198181
}
199182
this.header.alg = alg;
200183
return this;
@@ -207,7 +190,7 @@ Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) {
207190
var signingType = algTypeMap[algorithm];
208191

209192
if (!cryptoAlgName) {
210-
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
193+
throw new errors.UnsupportedSigningAlgorithmJwtError();
211194
}
212195

213196
if (signingType === 'hmac') {
@@ -234,12 +217,13 @@ Jwt.prototype.compact = function compact() {
234217
segments.push(this.body.compact());
235218

236219
if(this.header.alg !== 'none'){
237-
if (this.signingKey) {
238-
this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey);
239-
segments.push(this.signature);
240-
}else{
241-
throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
220+
if (!this.signingKey) {
221+
throw new errors.SigningKeyRequiredJwtError();
242222
}
223+
224+
this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey);
225+
226+
segments.push(this.signature);
243227
}
244228

245229
return segments.join('.');
@@ -278,7 +262,7 @@ Parser.prototype.parse = function parse(jwtString,cb){
278262
var signature;
279263

280264
if(segments.length<2 || segments.length>3){
281-
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null));
265+
return done(new errors.JwtParseError(jwtString));
282266
}
283267

284268
var header = this.safeJsonParse(segments[0]);
@@ -290,10 +274,10 @@ Parser.prototype.parse = function parse(jwtString,cb){
290274
}
291275

292276
if(header instanceof Error){
293-
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null,header));
277+
return done(new errors.JwtParseError(jwtString, null, null, header));
294278
}
295279
if(body instanceof Error){
296-
return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,header,null,body));
280+
return done(new errors.JwtParseError(jwtString, header, null, body));
297281
}
298282
var jwt = new Jwt(body, false);
299283
jwt.setSigningAlgorithm(header.alg);
@@ -312,7 +296,7 @@ function Verifier(){
312296
}
313297
Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) {
314298
if(!this.isSupportedAlg(alg)){
315-
throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG);
299+
throw new errors.UnsupportedSigningAlgorithmJwtError();
316300
}
317301
this.signingAlgorithm = alg;
318302
return this;
@@ -342,15 +326,15 @@ Verifier.prototype.verify = function verify(jwtString,cb){
342326
var signingType = algTypeMap[header.alg];
343327

344328
if (header.alg !== this.signingAlgorithm) {
345-
return done(new JwtParseError(properties.errors.SIGNATURE_ALGORITHM_MISMTACH,jwtString,header,body));
329+
return done(new errors.SignatureAlgorithmMismatchJwtParseError(jwtString, header, body));
346330
}
347331

348332
if (jwt.isExpired()) {
349-
return done(new JwtParseError(properties.errors.EXPIRED,jwtString,header,body));
333+
return done(new errors.ExpiredJwtParseError(jwtString, header, body));
350334
}
351335

352336
if (jwt.isNotBefore()) {
353-
return done(new JwtParseError(properties.errors.NOT_ACTIVE,jwtString,header,body));
337+
return done(new errors.NotActiveJwtParseError(jwtString, header, body));
354338
}
355339

356340
var digstInput = jwt.verificationInput;
@@ -371,7 +355,7 @@ Verifier.prototype.verify = function verify(jwtString,cb){
371355
try {
372356
unescapedSignature = ecdsaSigFormatter.joseToDer(signature, header.alg);
373357
} catch (err) {
374-
return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body,err));
358+
return done(new errors.SignatureMismatchJwtParseError(jwtString, header, body, err));
375359
}
376360
} else {
377361
signatureType = 'base64';
@@ -392,7 +376,7 @@ Verifier.prototype.verify = function verify(jwtString,cb){
392376
newJwt.header = new JwtHeader(header);
393377

394378
if (!verified) {
395-
return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body));
379+
return done(new errors.SignatureMismatchJwtParseError(jwtString, header, body));
396380
}
397381

398382
return done(null, newJwt);
@@ -441,15 +425,22 @@ var jwtLib = {
441425
}else{
442426
jwt = new Jwt(claims);
443427
}
444-
if(alg!=='none' && !secret){
445-
throw new Error(properties.errors.SIGNING_KEY_REQUIRED);
446-
}else{
447-
jwt.setSigningAlgorithm(args.length===3 ? alg : 'HS256');
448-
jwt.setSigningKey(secret);
428+
429+
if(alg !== 'none' && !secret){
430+
throw new errors.SigningKeyRequiredJwtError();
449431
}
432+
433+
jwt.setSigningAlgorithm(args.length===3 ? alg : 'HS256');
434+
jwt.setSigningKey(secret);
450435
jwt.setExpiration((nowEpochSeconds() + (60*60))*1000); // one hour
436+
451437
return jwt;
452438
}
453439
};
454440

441+
// Copy errors onto export object.
442+
for (var key in errors) {
443+
jwtLib[key] = errors[key];
444+
}
445+
455446
module.exports = jwtLib;

‎properties.json

-11
This file was deleted.

‎test/builder.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ var assert = require('chai').assert;
33
var nJwt = require('../');
44
var uuid = require('uuid');
55

6-
var properties = require('../properties.json');
6+
var errors = require('../errors');
77

88
describe('Jwt()',function(){
99
describe('signWith()',function(){
1010
describe('if called with an unsupported algorithm',function(){
1111
it('should throw',function(){
1212
assert.throws(function(){
1313
new nJwt.Jwt().setSigningAlgorithm('unsupported');
14-
},properties.errors.UNSUPPORTED_SIGNING_ALG);
14+
},errors.UnsupportedSigningAlgorithmJwtError);
1515
});
1616
});
1717
});
@@ -20,10 +20,10 @@ describe('Jwt()',function(){
2020

2121
describe('create()',function(){
2222

23-
it('should throw SIGNING_KEY_REQUIRED if passed no options',function(){
23+
it('should throw UnsupportedSigningAlgorithmJwtError if passed no options',function(){
2424
assert.throws(function(){
2525
nJwt.create();
26-
},properties.errors.SIGNING_KEY_REQUIRED);
26+
},errors.SigningKeyRequiredJwtError);
2727
});
2828

2929
it('should create a default token if the scret is the only value',function(){
@@ -33,7 +33,7 @@ describe('create()',function(){
3333
it('should throw if using defaults without a secret key',function(){
3434
assert.throws(function(){
3535
nJwt.create({});
36-
},properties.errors.SIGNING_KEY_REQUIRED);
36+
},errors.SigningKeyRequiredJwtError);
3737
});
3838

3939
it('should not throw if none is specified when omitting the key',function(){
@@ -43,7 +43,6 @@ describe('create()',function(){
4343
});
4444

4545
describe('with a signing key',function(){
46-
4746
it('should return a JWT',function(){
4847
assert(nJwt.create({},uuid()) instanceof nJwt.Jwt);
4948
});

0 commit comments

Comments
 (0)