diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..28a0450 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,84 @@ +{ + "rules": { + "curly": [ + 2 + ], + "eqeqeq": [ + 2 + ], + "indent": [ + 2, + 2, + { + "SwitchCase": 1 + } + ], + "linebreak-style": [ + 2, + "unix" + ], + "new-cap": [ + 2, + { + "properties": false + } + ], + "no-unused-vars": [ + 2, + { + "vars": "all", + "args": "after-used" + } + ], + "no-use-before-define": [ + 2 + ], + "no-trailing-spaces": [ + 2 + ], + "quotes": [ + 2, + "single" + ], + "semi": [ + 2, + "always" + ], + "wrap-iife": [ + 2, + "outside" + ], + "strict": [ + 2, + "global" + ], + "brace-style": 2, + "block-spacing": [ + 2, + "always" + ], + "keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}], + "space-before-blocks": 2, + "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "no-lonely-if": 2, + "array-bracket-spacing": [2, "never"], + "no-spaced-func": [2], + "space-in-parens": [2, "never"], + "space-infix-ops": 2 + }, + "globals": { + "after": true, + "afterEach": true, + "before": true, + "beforeEach": true, + "describe": true, + "it": true + }, + "env": { + "es6": true, + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..d3c91b5 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,4 @@ +test/rsa.pub +test/rsa.priv +test/ecdsa.pub +test/ecdsa.priv \ No newline at end of file diff --git a/README.md b/README.md index b0130c0..1bae314 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,31 @@ jwt.setNotbefore(); // Remove the exp claim ``` +## Error Handling + +If an error occurs then one of the following error types will either be thrown +or passed as the first argument in a callback. + +Type | Description +----------------------------------------|-------------------------------------- +JwtError | Base type. All errors implement this. +JwtParseError | Jwt cannot be parsed. +UnsupportedSigningAlgorithmJwtError | Unsupported signing algorithm. +SigningKeyRequiredJwtError | Signing key is required. +NotActiveJwtParseError | Jwt not active. +ExpiredJwtParseError | Jwt is expired. +SignatureAlgorithmMismatchJwtParseError | Unexpected signature algorithm. +SignatureMismatchJwtParseError | Signature verification failed. + +To handle a specific error, simply compare the instance of the error received to one of +the error types above, as shown in the example below: + +```javascript +if (err instanceof nJwt.JwtParseError) { + console.log('Unable to parse the provided jwt.'); +} +``` + ## Supported Algorithms "alg" Value | Algorithm used diff --git a/index.js b/index.js index 87246ea..e1436de 100644 --- a/index.js +++ b/index.js @@ -1,455 +1,3 @@ 'use strict'; -var util = require('util'); -var uuid = require('uuid'); -var crypto = require('crypto'); -var ecdsaSigFormatter = require('ecdsa-sig-formatter'); -var properties = require('./properties.json'); - -var algCryptoMap = { - HS256: 'SHA256', - HS384: 'SHA384', - HS512: 'SHA512', - RS256: 'RSA-SHA256', - RS384: 'RSA-SHA384', - RS512: 'RSA-SHA512', - ES256: 'RSA-SHA256', - ES384: 'RSA-SHA384', - ES512: 'RSA-SHA512', - none: 'none' -}; - -var algTypeMap = { - HS256: 'hmac', - HS384: 'hmac', - HS512: 'hmac', - RS256: 'sign', - RS384: 'sign', - RS512: 'sign', - ES256: 'sign', - ES384: 'sign', - ES512: 'sign' -}; - -function isECDSA(algorithm) { - return algorithm.indexOf('ES') === 0; -} - -function nowEpochSeconds(){ - return Math.floor(new Date().getTime()/1000); -} - -function base64urlEncode(str) { - return new Buffer(str) - .toString('base64') - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, ''); -} - -function base64urlUnescape(str) { - str += new Array(5 - str.length % 4).join('='); - return str.replace(/\-/g, '+').replace(/_/g, '/'); -} - -function isSupportedAlg(alg){ - return !!algCryptoMap[alg]; -} - -function handleError(cb,err,value){ - if(typeof cb==='function'){ - return process.nextTick(function() { - cb(err,value); - }); - }else if(err){ - throw err; - }else{ - return value; - } -} - -function JwtError(message) { - this.name = 'JwtError'; - this.message = this.userMessage = message; -} -util.inherits(JwtError, Error); - -function JwtParseError(message,jwtString,parsedHeader,parsedBody,innerError) { - this.name = 'JwtParseError'; - this.message = this.userMessage = message; - this.jwtString = jwtString; - this.parsedHeader = parsedHeader; - this.parsedBody = parsedBody; - this.innerError = innerError; -} -util.inherits(JwtParseError, Error); - -function JwtBody(claims){ - if(!(this instanceof JwtBody)){ - return new JwtBody(claims); - } - var self = this; - if(claims){ - Object.keys(claims).forEach(function(k){ - self[k] = claims[k]; - }); - } - return this; -} - -JwtBody.prototype.toJSON = function() { - var self = this; - return Object.keys(self).reduce(function(acc,key){ - acc[key] = self[key]; - return acc; - },{}); -}; -JwtBody.prototype.compact = function compact(){ - return base64urlEncode(JSON.stringify(this)); -}; - -function JwtHeader(header){ - if(!(this instanceof JwtHeader)){ - return new JwtHeader(header); - } - var self = this; - this.typ = header && header.typ || 'JWT'; - this.alg = header && header.alg || 'HS256'; - - if(header){ - return Object.keys(header).reduce(function(acc,key){ - if(self.reservedKeys.indexOf(key)===-1 && header.hasOwnProperty(key)){ - acc[key] = header[key]; - } - return acc; - },this); - }else{ - return this; - } -} -JwtHeader.prototype.reservedKeys = ['typ','alg']; -JwtHeader.prototype.compact = function compact(){ - return base64urlEncode(JSON.stringify(this)); -}; - -function Jwt(claims, enforceDefaultFields){ - if(!(this instanceof Jwt)){ - return new Jwt(claims, enforceDefaultFields); - } - - this.header = new JwtHeader(); - this.body = new JwtBody(claims); - - if (enforceDefaultFields !== false) { - this.setSigningAlgorithm('none'); - - if (!this.body.jti) { - this.setJti(uuid.v4()); - } - - if (!this.body.iat) { - this.setIssuedAt(nowEpochSeconds()); - } - } - - return this; -} -Jwt.prototype.setJti = function setJti(jti) { - this.body.jti = jti; - return this; -}; -Jwt.prototype.setSubject = function setSubject(sub) { - this.body.sub = sub; - return this; -}; -Jwt.prototype.setIssuer = function setIssuer(iss) { - this.body.iss = iss; - return this; -}; -Jwt.prototype.setIssuedAt = function setIssuedAt(iat) { - this.body.iat = iat; - return this; -}; -Jwt.prototype.setExpiration = function setExpiration(exp) { - if(exp){ - this.body.exp = Math.floor((exp instanceof Date ? exp : new Date(exp)).getTime() / 1000); - }else{ - delete this.body.exp; - } - - return this; -}; -Jwt.prototype.setNotBefore = function setNotBefore(nbf) { - if(nbf) { - this.body.nbf = Math.floor((nbf instanceof Date ? nbf : new Date(nbf)).getTime() / 1000); - } else { - delete this.body.nbf; - } - - return this; -}; -Jwt.prototype.setSigningKey = function setSigningKey(key) { - this.signingKey = key; - return this; -}; -Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { - if(!this.isSupportedAlg(alg)){ - throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); - } - this.header.alg = alg; - return this; -}; - -Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) { - var buffer; - var signature; - var cryptoAlgName = algCryptoMap[algorithm]; - var signingType = algTypeMap[algorithm]; - - if (!cryptoAlgName) { - throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); - } - - if (signingType === 'hmac') { - buffer = crypto.createHmac(cryptoAlgName, cryptoInput).update(payload).digest(); - } else { - buffer = crypto.createSign(cryptoAlgName).update(payload).sign(cryptoInput); - } - - if (isECDSA(algorithm)) { - signature = ecdsaSigFormatter.derToJose(buffer, algorithm); - } else { - signature = base64urlEncode(buffer); - } - - return signature; -}; - -Jwt.prototype.isSupportedAlg = isSupportedAlg; - -Jwt.prototype.compact = function compact() { - - var segments = []; - segments.push(this.header.compact()); - segments.push(this.body.compact()); - - if(this.header.alg !== 'none'){ - if (this.signingKey) { - this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey); - segments.push(this.signature); - }else{ - throw new Error(properties.errors.SIGNING_KEY_REQUIRED); - } - } - - return segments.join('.'); -}; - -Jwt.prototype.toString = function(){ - return this.compact(); -}; - -Jwt.prototype.isExpired = function() { - return new Date(this.body.exp*1000) < new Date(); -}; - -Jwt.prototype.isNotBefore = function() { - return new Date(this.body.nbf * 1000) >= new Date(); -}; - -function Parser(options){ - return this; -} - -Parser.prototype.isSupportedAlg = isSupportedAlg; -Parser.prototype.safeJsonParse = function(input) { - var result; - try{ - result = JSON.parse(new Buffer(base64urlUnescape(input),'base64')); - }catch(e){ - return e; - } - return result; -}; -Parser.prototype.parse = function parse(jwtString,cb){ - - var done = handleError.bind(null,cb); - var segments = jwtString.split('.'); - var signature; - - if(segments.length<2 || segments.length>3){ - return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null)); - } - - var header = this.safeJsonParse(segments[0]); - var body = this.safeJsonParse(segments[1]); - - if(segments[2]){ - signature = new Buffer(base64urlUnescape(segments[2]),'base64') - .toString('base64'); - } - - if(header instanceof Error){ - return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,null,null,header)); - } - if(body instanceof Error){ - return done(new JwtParseError(properties.errors.PARSE_ERROR,jwtString,header,null,body)); - } - var jwt = new Jwt(body, false); - jwt.setSigningAlgorithm(header.alg); - jwt.signature = signature; - jwt.verificationInput = segments[0] +'.' + segments[1]; - jwt.header = new JwtHeader(header); - return done(null,jwt); -}; - -function Verifier(){ - if(!(this instanceof Verifier)){ - return new Verifier(); - } - this.setSigningAlgorithm('HS256'); - return this; -} -Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { - if(!this.isSupportedAlg(alg)){ - throw new JwtError(properties.errors.UNSUPPORTED_SIGNING_ALG); - } - this.signingAlgorithm = alg; - return this; -}; -Verifier.prototype.setSigningKey = function setSigningKey(keyStr) { - this.signingKey = keyStr; - return this; -}; -Verifier.prototype.isSupportedAlg = isSupportedAlg; - -Verifier.prototype.verify = function verify(jwtString,cb){ - var jwt; - - var done = handleError.bind(null,cb); - - try { - jwt = new Parser().parse(jwtString); - } catch(e) { - return done(e); - } - - var body = jwt.body; - var header = jwt.header; - var signature = jwt.signature; - - var cryptoAlgName = algCryptoMap[header.alg]; - var signingType = algTypeMap[header.alg]; - - if (header.alg !== this.signingAlgorithm) { - return done(new JwtParseError(properties.errors.SIGNATURE_ALGORITHM_MISMTACH,jwtString,header,body)); - } - - if (jwt.isExpired()) { - return done(new JwtParseError(properties.errors.EXPIRED,jwtString,header,body)); - } - - if (jwt.isNotBefore()) { - return done(new JwtParseError(properties.errors.NOT_ACTIVE,jwtString,header,body)); - } - - var digstInput = jwt.verificationInput; - var verified, digest; - - if( cryptoAlgName==='none') { - verified = true; - } else if(signingType === 'hmac') { - digest = crypto.createHmac(cryptoAlgName, this.signingKey) - .update(digstInput) - .digest('base64'); - verified = signature === digest; - } else { - var unescapedSignature; - var signatureType = undefined; - - if (isECDSA(header.alg)) { - try { - unescapedSignature = ecdsaSigFormatter.joseToDer(signature, header.alg); - } catch (err) { - return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body,err)); - } - } else { - signatureType = 'base64'; - unescapedSignature = base64urlUnescape(signature); - } - - verified = crypto.createVerify(cryptoAlgName) - .update(digstInput) - .verify(this.signingKey, unescapedSignature, signatureType); - } - - var newJwt = new Jwt(body, false); - - newJwt.toString = function () { - return jwtString; - }; - - newJwt.header = new JwtHeader(header); - - if (!verified) { - return done(new JwtParseError(properties.errors.SIGNATURE_MISMTACH,jwtString,header,body)); - } - - return done(null, newJwt); -}; - -var jwtLib = { - Jwt: Jwt, - JwtBody: JwtBody, - JwtHeader: JwtHeader, - Verifier: Verifier, - base64urlEncode: base64urlEncode, - base64urlUnescape:base64urlUnescape, - verify: function(jwtString,secret,alg,cb){ - var args = Array.prototype.slice.call(arguments); - - if(typeof args[args.length-1]==='function'){ - cb = args.pop(); - }else{ - cb = null; - } - - var verifier = new Verifier(); - - if(args.length===3){ - verifier.setSigningAlgorithm(alg); - }else{ - verifier.setSigningAlgorithm('HS256'); - } - - if(args.length===1){ - verifier.setSigningAlgorithm('none'); - }else{ - verifier.setSigningKey(secret); - } - - return verifier.verify(jwtString,cb); - }, - create: function(claims,secret,alg){ - var args = Array.prototype.slice.call(arguments); - var jwt; - if(args.length >= 2){ - jwt = new Jwt(claims); - }else if (args.length===1 && typeof claims === 'string'){ - jwt = new Jwt({}); - secret = claims; - }else{ - jwt = new Jwt(claims); - } - if(alg!=='none' && !secret){ - throw new Error(properties.errors.SIGNING_KEY_REQUIRED); - }else{ - jwt.setSigningAlgorithm(args.length===3 ? alg : 'HS256'); - jwt.setSigningKey(secret); - } - jwt.setExpiration((nowEpochSeconds() + (60*60))*1000); // one hour - return jwt; - } -}; - -module.exports = jwtLib; +module.exports = require('./lib'); \ No newline at end of file diff --git a/lib/enums.js b/lib/enums.js new file mode 100644 index 0000000..47fd45a --- /dev/null +++ b/lib/enums.js @@ -0,0 +1,31 @@ +'use strict'; + +var algCryptoMap = { + HS256: 'SHA256', + HS384: 'SHA384', + HS512: 'SHA512', + RS256: 'RSA-SHA256', + RS384: 'RSA-SHA384', + RS512: 'RSA-SHA512', + ES256: 'RSA-SHA256', + ES384: 'RSA-SHA384', + ES512: 'RSA-SHA512', + none: 'none' +}; + +var algTypeMap = { + HS256: 'hmac', + HS384: 'hmac', + HS512: 'hmac', + RS256: 'sign', + RS384: 'sign', + RS512: 'sign', + ES256: 'sign', + ES384: 'sign', + ES512: 'sign' +}; + +module.exports = { + algCryptoMap: algCryptoMap, + algTypeMap: algTypeMap +}; \ No newline at end of file diff --git a/lib/errors.js b/lib/errors.js new file mode 100644 index 0000000..9a4e2f3 --- /dev/null +++ b/lib/errors.js @@ -0,0 +1,70 @@ +'use strict'; + +var util = require('util'); + +function JwtError(message) { + this.name = 'JwtError'; + this.message = this.userMessage = message; +} +util.inherits(JwtError, Error); + +function JwtParseError(jwtString, parsedHeader, parsedBody, innerError) { + this.name = 'JwtParseError'; + this.message = this.userMessage = 'Jwt cannot be parsed'; + this.jwtString = jwtString; + this.parsedHeader = parsedHeader; + this.parsedBody = parsedBody; + this.innerError = innerError; +} +util.inherits(JwtParseError, JwtError); + +function UnsupportedSigningAlgorithmJwtError() { + UnsupportedSigningAlgorithmJwtError.super_.call(this, 'Unsupported signing algorithm'); + this.name = 'UnsupportedSigningAlgorithmJwtError'; +} +util.inherits(UnsupportedSigningAlgorithmJwtError, JwtError); + +function SigningKeyRequiredJwtError() { + SigningKeyRequiredJwtError.super_.call(this, 'Signing key is required'); + this.name = 'SigningKeyRequiredJwtError'; +} +util.inherits(SigningKeyRequiredJwtError, JwtError); + +function NotActiveJwtParseError(jwtString, parsedHeader, parsedBody) { + NotActiveJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody); + this.name = 'NotActiveJwtParseError'; + this.message = this.userMessage = 'Jwt not active'; +} +util.inherits(NotActiveJwtParseError, JwtParseError); + +function ExpiredJwtParseError(jwtString, parsedHeader, parsedBody) { + ExpiredJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody); + this.name = 'ExpiredJwtParseError'; + this.message = this.userMessage = 'Jwt is expired'; +} +util.inherits(ExpiredJwtParseError, JwtParseError); + +function SignatureAlgorithmMismatchJwtParseError(jwtString, parsedHeader, parsedBody) { + SignatureAlgorithmMismatchJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody); + this.name = 'SignatureAlgorithmMismatchJwtParseError'; + this.message = this.userMessage = 'Unexpected signature algorithm'; +} +util.inherits(SignatureAlgorithmMismatchJwtParseError, JwtParseError); + +function SignatureMismatchJwtParseError(jwtString, parsedHeader, parsedBody, innerError) { + SignatureMismatchJwtParseError.super_.call(this, jwtString, parsedHeader, parsedBody, innerError); + this.name = 'SignatureMismatchJwtParseError'; + this.message = this.userMessage = 'Signature verification failed'; +} +util.inherits(SignatureMismatchJwtParseError, JwtParseError); + +module.exports = { + JwtError: JwtError, + JwtParseError: JwtParseError, + UnsupportedSigningAlgorithmJwtError: UnsupportedSigningAlgorithmJwtError, + SigningKeyRequiredJwtError: SigningKeyRequiredJwtError, + NotActiveJwtParseError: NotActiveJwtParseError, + ExpiredJwtParseError: ExpiredJwtParseError, + SignatureAlgorithmMismatchJwtParseError: SignatureAlgorithmMismatchJwtParseError, + SignatureMismatchJwtParseError: SignatureMismatchJwtParseError +}; \ No newline at end of file diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..e2fb1c0 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,62 @@ +'use strict'; + +var enums = require('./enums'); + +function isECDSA(algorithm) { + return algorithm.indexOf('ES') === 0; +} + +function nowEpochSeconds() { + return Math.floor(new Date().getTime() / 1000); +} + +function base64urlEncode(str) { + return new Buffer(str) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} + +function base64urlUnescape(str) { + str += new Array(5 - str.length % 4).join('='); + return str.replace(/\-/g, '+').replace(/_/g, '/'); +} + +function isSupportedAlg(alg) { + return !!enums.algCryptoMap[alg]; +} + +function handleError(cb, err, value) { + if (typeof cb === 'function') { + return process.nextTick(function () { + cb(err, value); + }); + } else if (err) { + throw err; + } + + return value; +} + +function safeJsonParse(input) { + var result; + + try { + result = JSON.parse(new Buffer(base64urlUnescape(input), 'base64')); + } catch (e) { + return e; + } + + return result; +} + +module.exports = { + isECDSA: isECDSA, + nowEpochSeconds: nowEpochSeconds, + base64urlUnescape: base64urlUnescape, + base64urlEncode: base64urlEncode, + isSupportedAlg: isSupportedAlg, + handleError: handleError, + safeJsonParse: safeJsonParse +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..1901c30 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,74 @@ +'use strict'; + +var errors = require('./errors'); +var helpers = require('./helpers'); +var Jwt = require('./jwt'); +var JwtBody = require('./jwt-body'); +var JwtHeader = require('./jwt-header'); +var Verifier = require('./verifier'); + +var exports = {}; + +exports.Jwt = Jwt; +exports.Verifier = Verifier; +exports.JwtBody = JwtBody; +exports.JwtHeader = JwtHeader; +exports.base64urlEncode = helpers.base64urlEncode; +exports.base64urlUnescape = helpers.base64urlUnescape; + +exports.verify = function verify(jwtString, secret, alg, cb) { + var args = Array.prototype.slice.call(arguments); + + if (typeof args[args.length - 1] === 'function') { + cb = args.pop(); + } else { + cb = null; + } + + var verifier = new Verifier(); + + if (args.length === 3) { + verifier.setSigningAlgorithm(alg); + } else { + verifier.setSigningAlgorithm('HS256'); + } + + if (args.length === 1) { + verifier.setSigningAlgorithm('none'); + } else { + verifier.setSigningKey(secret); + } + + return verifier.verify(jwtString, cb); +}; + +exports.create = function create(claims, secret, alg) { + var jwt; + var args = Array.prototype.slice.call(arguments); + + if (args.length >= 2) { + jwt = new Jwt(claims); + } else if (args.length === 1 && typeof claims === 'string') { + jwt = new Jwt({}); + secret = claims; + } else { + jwt = new Jwt(claims); + } + + if (alg !== 'none' && !secret) { + throw new errors.SigningKeyRequiredJwtError(); + } + + jwt.setSigningAlgorithm(args.length === 3 ? alg : 'HS256'); + jwt.setSigningKey(secret); + jwt.setExpiration((helpers.nowEpochSeconds() + (60 * 60)) * 1000); // one hour + + return jwt; +}; + +// Export all errors. +for (var key in errors) { + exports[key] = errors[key]; +} + +module.exports = exports; \ No newline at end of file diff --git a/lib/jwt-body.js b/lib/jwt-body.js new file mode 100644 index 0000000..d02dd0e --- /dev/null +++ b/lib/jwt-body.js @@ -0,0 +1,30 @@ +'use strict'; + +var helpers = require('./helpers'); + +function JwtBody(claims) { + if (!(this instanceof JwtBody)) { + return new JwtBody(claims); + } + + if (claims) { + var self = this; + Object.keys(claims).forEach(function (k) { + self[k] = claims[k]; + }); + } +} + +JwtBody.prototype.toJSON = function toJSON() { + var self = this; + return Object.keys(self).reduce(function (acc, key) { + acc[key] = self[key]; + return acc; + }, {}); +}; + +JwtBody.prototype.compact = function compact() { + return helpers.base64urlEncode(JSON.stringify(this)); +}; + +module.exports = JwtBody; \ No newline at end of file diff --git a/lib/jwt-header.js b/lib/jwt-header.js new file mode 100644 index 0000000..18f873d --- /dev/null +++ b/lib/jwt-header.js @@ -0,0 +1,30 @@ +'use strict'; + +var helpers = require('./helpers'); + +function JwtHeader(header) { + if (!(this instanceof JwtHeader)) { + return new JwtHeader(header); + } + + this.typ = header && header.typ || 'JWT'; + this.alg = header && header.alg || 'HS256'; + + if (header) { + var self = this; + return Object.keys(header).reduce(function (acc, key) { + if (self.reservedKeys.indexOf(key) === -1 && header.hasOwnProperty(key)) { + acc[key] = header[key]; + } + return acc; + }, this); + } +} + +JwtHeader.prototype.reservedKeys = ['typ', 'alg']; + +JwtHeader.prototype.compact = function compact() { + return helpers.base64urlEncode(JSON.stringify(this)); +}; + +module.exports = JwtHeader; \ No newline at end of file diff --git a/lib/jwt.js b/lib/jwt.js new file mode 100644 index 0000000..600f975 --- /dev/null +++ b/lib/jwt.js @@ -0,0 +1,146 @@ +'use strict'; + +var uuid = require('uuid'); +var crypto = require('crypto'); +var ecdsaSigFormatter = require('ecdsa-sig-formatter'); +var enums = require('./enums'); +var errors = require('./errors'); +var helpers = require('./helpers'); +var JwtHeader = require('./jwt-header'); +var JwtBody = require('./jwt-body'); + +function Jwt(claims, enforceDefaultFields) { + if (!(this instanceof Jwt)) { + return new Jwt(claims, enforceDefaultFields); + } + + this.header = new JwtHeader(); + this.body = new JwtBody(claims); + + if (enforceDefaultFields !== false) { + this.setSigningAlgorithm('none'); + + if (!this.body.jti) { + this.setJti(uuid.v4()); + } + + if (!this.body.iat) { + this.setIssuedAt(helpers.nowEpochSeconds()); + } + } +} + +Jwt.prototype.setJti = function setJti(jti) { + this.body.jti = jti; + return this; +}; + +Jwt.prototype.setSubject = function setSubject(sub) { + this.body.sub = sub; + return this; +}; + +Jwt.prototype.setIssuer = function setIssuer(iss) { + this.body.iss = iss; + return this; +}; + +Jwt.prototype.setIssuedAt = function setIssuedAt(iat) { + this.body.iat = iat; + return this; +}; + +Jwt.prototype.setExpiration = function setExpiration(exp) { + if (exp) { + this.body.exp = Math.floor((exp instanceof Date ? exp : new Date(exp)).getTime() / 1000); + } else { + delete this.body.exp; + } + + return this; +}; + +Jwt.prototype.setNotBefore = function setNotBefore(nbf) { + if (nbf) { + this.body.nbf = Math.floor((nbf instanceof Date ? nbf : new Date(nbf)).getTime() / 1000); + } else { + delete this.body.nbf; + } + + return this; +}; + +Jwt.prototype.setSigningKey = function setSigningKey(key) { + this.signingKey = key; + return this; +}; + +Jwt.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { + if (!helpers.isSupportedAlg(alg)) { + throw new errors.UnsupportedSigningAlgorithmJwtError(); + } + + this.header.alg = alg; + + return this; +}; + +Jwt.prototype.sign = function sign(payload, algorithm, cryptoInput) { + var buffer; + var signature; + var cryptoAlgName = enums.algCryptoMap[algorithm]; + var signingType = enums.algTypeMap[algorithm]; + + if (!cryptoAlgName) { + throw new errors.UnsupportedSigningAlgorithmJwtError(); + } + + if (signingType === 'hmac') { + buffer = crypto.createHmac(cryptoAlgName, cryptoInput).update(payload).digest(); + } else { + buffer = crypto.createSign(cryptoAlgName).update(payload).sign(cryptoInput); + } + + if (helpers.isECDSA(algorithm)) { + signature = ecdsaSigFormatter.derToJose(buffer, algorithm); + } else { + signature = helpers.base64urlEncode(buffer); + } + + return signature; +}; + +Jwt.prototype.isSupportedAlg = helpers.isSupportedAlg; + +Jwt.prototype.compact = function compact() { + var segments = []; + + segments.push(this.header.compact()); + segments.push(this.body.compact()); + + if (this.header.alg !== 'none') { + if (!this.signingKey) { + throw new errors.SigningKeyRequiredJwtError(); + } + + this.signature = this.sign(segments.join('.'), this.header.alg, this.signingKey); + + segments.push(this.signature); + } + + return segments.join('.'); +}; + +Jwt.prototype.toString = function toString() { + return this.compact(); +}; + +Jwt.prototype.isExpired = function isExpired() { + return new Date(this.body.exp * 1000) < new Date(); +}; + +Jwt.prototype.isNotBefore = function isNotBefore() { + return new Date(this.body.nbf * 1000) >= new Date(); +}; + +module.exports = Jwt; \ No newline at end of file diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..a9699a4 --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,48 @@ +'use strict'; + +var errors = require('./errors'); +var helpers = require('./helpers'); +var Jwt = require('./jwt'); +var JwtHeader = require('./jwt-header'); + +function Parser() { +} + +Parser.prototype.isSupportedAlg = helpers.isSupportedAlg; + +Parser.prototype.parse = function parse(jwtString, cb) { + var signature; + var done = helpers.handleError.bind(null, cb); + var segments = jwtString.split('.'); + + if (segments.length < 2 || segments.length > 3) { + return done(new errors.JwtParseError(jwtString)); + } + + var header = helpers.safeJsonParse(segments[0]); + var body = helpers.safeJsonParse(segments[1]); + + if (segments[2]) { + signature = new Buffer(helpers.base64urlUnescape(segments[2]), 'base64') + .toString('base64'); + } + + if (header instanceof Error) { + return done(new errors.JwtParseError(jwtString, null, null, header)); + } + + if (body instanceof Error) { + return done(new errors.JwtParseError(jwtString, header, null, body)); + } + + var jwt = new Jwt(body, false); + + jwt.setSigningAlgorithm(header.alg); + jwt.signature = signature; + jwt.verificationInput = segments[0] + '.' + segments[1]; + jwt.header = new JwtHeader(header); + + return done(null, jwt); +}; + +module.exports = Parser; \ No newline at end of file diff --git a/lib/verifier.js b/lib/verifier.js new file mode 100644 index 0000000..1269d29 --- /dev/null +++ b/lib/verifier.js @@ -0,0 +1,111 @@ +'use strict'; + +var crypto = require('crypto'); +var ecdsaSigFormatter = require('ecdsa-sig-formatter'); +var enums = require('./enums'); +var errors = require('./errors'); +var helpers = require('./helpers'); +var Parser = require('./parser'); +var Jwt = require('./jwt'); +var JwtHeader = require('./jwt-header'); + +function Verifier() { + if (!(this instanceof Verifier)) { + return new Verifier(); + } + + this.setSigningAlgorithm('HS256'); +} + +Verifier.prototype.setSigningAlgorithm = function setSigningAlgorithm(alg) { + if (!helpers.isSupportedAlg(alg)) { + throw new errors.UnsupportedSigningAlgorithmJwtError(); + } + + this.signingAlgorithm = alg; + + return this; +}; + +Verifier.prototype.setSigningKey = function setSigningKey(keyStr) { + this.signingKey = keyStr; + return this; +}; + +Verifier.prototype.isSupportedAlg = helpers.isSupportedAlg; + +Verifier.prototype.verify = function verify(jwtString, cb) { + var jwt; + var done = helpers.handleError.bind(null, cb); + + try { + jwt = new Parser().parse(jwtString); + } catch (e) { + return done(e); + } + + var body = jwt.body; + var header = jwt.header; + var signature = jwt.signature; + + var cryptoAlgName = enums.algCryptoMap[header.alg]; + var signingType = enums.algTypeMap[header.alg]; + + if (header.alg !== this.signingAlgorithm) { + return done(new errors.SignatureAlgorithmMismatchJwtParseError(jwtString, header, body)); + } + + if (jwt.isExpired()) { + return done(new errors.ExpiredJwtParseError(jwtString, header, body)); + } + + if (jwt.isNotBefore()) { + return done(new errors.NotActiveJwtParseError(jwtString, header, body)); + } + + var digstInput = jwt.verificationInput; + var verified, digest; + + if (cryptoAlgName === 'none') { + verified = true; + } else if (signingType === 'hmac') { + digest = crypto.createHmac(cryptoAlgName, this.signingKey) + .update(digstInput) + .digest('base64'); + verified = signature === digest; + } else { + var unescapedSignature; + var signatureType = undefined; + + if (helpers.isECDSA(header.alg)) { + try { + unescapedSignature = ecdsaSigFormatter.joseToDer(signature, header.alg); + } catch (err) { + return done(new errors.SignatureMismatchJwtParseError(jwtString, header, body, err)); + } + } else { + signatureType = 'base64'; + unescapedSignature = helpers.base64urlUnescape(signature); + } + + verified = crypto.createVerify(cryptoAlgName) + .update(digstInput) + .verify(this.signingKey, unescapedSignature, signatureType); + } + + var newJwt = new Jwt(body, false); + + newJwt.toString = function () { + return jwtString; + }; + + newJwt.header = new JwtHeader(header); + + if (!verified) { + return done(new errors.SignatureMismatchJwtParseError(jwtString, header, body)); + } + + return done(null, newJwt); +}; + +module.exports = Verifier; \ No newline at end of file diff --git a/package.json b/package.json index 6c013e3..a5c9ba8 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "JWT Library for Node.js", "main": "index.js", "scripts": { + "lint": "./node_modules/eslint/bin/eslint.js -c .eslintrc lib/** test/** --ignore-path .jshintignore --quiet", "test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec --no-timeouts; cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js; rm -rf ./coverage", "test-watch": "mocha --timeout=5000 --reporter dot --check-leaks -w ./*.js test/ ", "test-debug": "mocha --timeout=5000 --debug --reporter dot --check-leaks -w ./*.js test/ ", @@ -33,6 +34,7 @@ "jsonwebtoken": "^5.0.2", "jwt-simple": "^0.3.0", "mocha": "^2.2.3", - "secure-random": "^1.1.1" + "secure-random": "^1.1.1", + "eslint": "^2.13.1" } } diff --git a/properties.json b/properties.json deleted file mode 100644 index 27045e1..0000000 --- a/properties.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "errors": { - "PARSE_ERROR": "Jwt cannot be parsed", - "EXPIRED": "Jwt is expired", - "UNSUPPORTED_SIGNING_ALG": "Unsupported signing algorithm", - "SIGNING_KEY_REQUIRED": "Signing key is required", - "SIGNATURE_MISMTACH": "Signature verification failed", - "SIGNATURE_ALGORITHM_MISMTACH": "Unexpected signature algorithm", - "NOT_ACTIVE": "Jwt not active" - } -} diff --git a/test/algs.js b/test/algs.js index fec03e6..33a0577 100644 --- a/test/algs.js +++ b/test/algs.js @@ -1,81 +1,91 @@ +'use strict'; + var assert = require('chai').assert; var uuid = require('uuid'); var nJwt = require('../'); var fs = require('fs'); var path = require('path'); -function itShouldBeAValidJwt(jwt){ - assert(nJwt.create({},uuid()) instanceof nJwt.Jwt); - var nowUnix = Math.floor(new Date().getTime()/1000); - assert.equal(nJwt.create({},uuid()).body.iat , nowUnix); +function itShouldBeAValidJwt(jwt) { + assert(nJwt.create({}, uuid()) instanceof nJwt.Jwt); + var nowUnix = Math.floor(new Date().getTime() / 1000); + assert.equal(nJwt.create({}, uuid()).body.iat, nowUnix); assert(jwt.body.jti.match(/[a-zA-Z0-9]+[-]/)); } -function testHmacAlg(alg,done){ +function testHmacAlg(alg, done) { var key = uuid(); var claims = { hello: uuid(), debug: true }; - var jwt = nJwt.create(claims,key,alg); + var jwt = nJwt.create(claims, key, alg); var token = jwt.compact(); itShouldBeAValidJwt(jwt); - nJwt.verify(token,key,alg,function(err,jwt){ - assert.isNull(err,'An unexpcted error was returned'); + nJwt.verify(token, key, alg, function (err, jwt) { + assert.isNull(err, 'An unexpcted error was returned'); itShouldBeAValidJwt(jwt); done(); }); } -function testKeyAlg(alg,keyPair,done){ +function testKeyAlg(alg, keyPair, done) { var claims = { hello: uuid(), debug: true }; - var jwt = nJwt.create(claims,keyPair.private,alg); + var jwt = nJwt.create(claims, keyPair.private, alg); var token = jwt.compact(); itShouldBeAValidJwt(jwt); - nJwt.verify(token,keyPair.public,alg,function(err,jwt){ - assert.isNull(err,'An unexpcted error was returned'); + nJwt.verify(token, keyPair.public, alg, function (err, jwt) { + assert.isNull(err, 'An unexpcted error was returned'); itShouldBeAValidJwt(jwt); done(); }); } var rsaPair = { - public: fs.readFileSync(path.join(__dirname,'rsa.pub'),'utf8'), - private: fs.readFileSync(path.join(__dirname,'rsa.priv'),'utf8') + public: fs.readFileSync(path.join(__dirname, 'rsa.pub'), 'utf8'), + private: fs.readFileSync(path.join(__dirname, 'rsa.priv'), 'utf8') }; var ecdsaPair = { - public: fs.readFileSync(path.join(__dirname,'ecdsa.pub'),'utf8'), - private: fs.readFileSync(path.join(__dirname,'ecdsa.priv'),'utf8') + public: fs.readFileSync(path.join(__dirname, 'ecdsa.pub'), 'utf8'), + private: fs.readFileSync(path.join(__dirname, 'ecdsa.priv'), 'utf8') }; -describe('this library',function () { - it('should support creation and veification of HS256 JWT tokens',function(done){ - testHmacAlg('HS256',done); +describe('this library', function () { + it('should support creation and veification of HS256 JWT tokens', function (done) { + testHmacAlg('HS256', done); }); - it('should support creation and veification of HS384 JWT tokens',function(done){ - testHmacAlg('HS384',done); + + it('should support creation and veification of HS384 JWT tokens', function (done) { + testHmacAlg('HS384', done); }); - it('should support creation and veification of HS512 JWT tokens',function(done){ - testHmacAlg('HS512',done); + + it('should support creation and veification of HS512 JWT tokens', function (done) { + testHmacAlg('HS512', done); }); - it('should support creation and veification of RS256 JWT tokens',function(done){ - testKeyAlg('RS256',rsaPair,done); + + it('should support creation and veification of RS256 JWT tokens', function (done) { + testKeyAlg('RS256', rsaPair, done); }); - it('should support creation and veification of RS384 JWT tokens',function(done){ - testKeyAlg('RS384',rsaPair,done); + + it('should support creation and veification of RS384 JWT tokens', function (done) { + testKeyAlg('RS384', rsaPair, done); }); - it('should support creation and veification of RS512 JWT tokens',function(done){ - testKeyAlg('RS512',rsaPair,done); + + it('should support creation and veification of RS512 JWT tokens', function (done) { + testKeyAlg('RS512', rsaPair, done); }); - it('should support creation and veification of ES256 JWT tokens',function(done){ - testKeyAlg('ES256',ecdsaPair,done); + + it('should support creation and veification of ES256 JWT tokens', function (done) { + testKeyAlg('ES256', ecdsaPair, done); }); - it('should support creation and veification of ES384 JWT tokens',function(done){ - testKeyAlg('ES384',ecdsaPair,done); + + it('should support creation and veification of ES384 JWT tokens', function (done) { + testKeyAlg('ES384', ecdsaPair, done); }); - it('should support creation and veification of ES512 JWT tokens',function(done){ - testKeyAlg('ES512',ecdsaPair,done); + + it('should support creation and veification of ES512 JWT tokens', function (done) { + testKeyAlg('ES512', ecdsaPair, done); }); }); \ No newline at end of file diff --git a/test/builder.js b/test/builder.js index c1a20d2..4c64163 100644 --- a/test/builder.js +++ b/test/builder.js @@ -1,91 +1,87 @@ -var assert = require('chai').assert; +'use strict'; -var nJwt = require('../'); var uuid = require('uuid'); +var assert = require('chai').assert; +var nJwt = require('../'); -var properties = require('../properties.json'); - -describe('Jwt()',function(){ - describe('signWith()',function(){ - describe('if called with an unsupported algorithm',function(){ - it('should throw',function(){ - assert.throws(function(){ +describe('Jwt()', function () { + describe('signWith()', function () { + describe('if called with an unsupported algorithm', function () { + it('should throw', function () { + assert.throws(function () { new nJwt.Jwt().setSigningAlgorithm('unsupported'); - },properties.errors.UNSUPPORTED_SIGNING_ALG); + }, nJwt.UnsupportedSigningAlgorithmJwtError); }); }); }); }); -describe('create()',function(){ - - it('should throw SIGNING_KEY_REQUIRED if passed no options',function(){ - assert.throws(function(){ +describe('create()', function () { + it('should throw UnsupportedSigningAlgorithmJwtError if passed no options', function () { + assert.throws(function () { nJwt.create(); - },properties.errors.SIGNING_KEY_REQUIRED); + }, nJwt.SigningKeyRequiredJwtError); }); - it('should create a default token if the scret is the only value',function(){ + it('should create a default token if the scret is the only value', function () { assert(nJwt.create(uuid()) instanceof nJwt.Jwt); }); - it('should throw if using defaults without a secret key',function(){ - assert.throws(function(){ + it('should throw if using defaults without a secret key', function () { + assert.throws(function () { nJwt.create({}); - },properties.errors.SIGNING_KEY_REQUIRED); + }, nJwt.SigningKeyRequiredJwtError); }); - it('should not throw if none is specified when omitting the key',function(){ - assert.doesNotThrow(function(){ - nJwt.create({},null,'none'); + it('should not throw if none is specified when omitting the key', function () { + assert.doesNotThrow(function () { + nJwt.create({}, null, 'none'); }); }); - describe('with a signing key',function(){ - - it('should return a JWT',function(){ - assert(nJwt.create({},uuid()) instanceof nJwt.Jwt); + describe('with a signing key', function () { + it('should return a JWT', function () { + assert(nJwt.create({}, uuid()) instanceof nJwt.Jwt); }); - it('should use HS256 by default',function(){ - assert.equal(nJwt.create({},uuid()).header.alg,'HS256'); + it('should use HS256 by default', function () { + assert.equal(nJwt.create({}, uuid()).header.alg, 'HS256'); }); - it('should create the iat field',function(){ - var nowUnix = Math.floor(new Date().getTime()/1000); - assert.equal(nJwt.create({},uuid()).body.iat , nowUnix); + it('should create the iat field', function () { + var nowUnix = Math.floor(new Date().getTime() / 1000); + assert.equal(nJwt.create({}, uuid()).body.iat, nowUnix); }); - it('should not overwrite a defined iat field',function(){ - assert.equal(nJwt.create({iat: 1},uuid()).body.iat , 1); + it('should not overwrite a defined iat field', function () { + assert.equal(nJwt.create({iat: 1}, uuid()).body.iat, 1); }); - it('should create the exp field, defaulted to 1 hour',function(){ - var oneHourFromNow = Math.floor(new Date().getTime()/1000) + (60*60); - assert.equal(nJwt.create({},uuid()).body.exp , oneHourFromNow); + it('should create the exp field, defaulted to 1 hour', function () { + var oneHourFromNow = Math.floor(new Date().getTime() / 1000) + (60 * 60); + assert.equal(nJwt.create({}, uuid()).body.exp, oneHourFromNow); }); - it('should not overwrite a defined jti field',function(){ - assert.equal(nJwt.create({jti: 1},uuid()).body.jti , 1); + it('should not overwrite a defined jti field', function () { + assert.equal(nJwt.create({jti: 1}, uuid()).body.jti, 1); }); - it('should create the jti field',function(){ - var jwt = nJwt.create({},uuid()); + it('should create the jti field', function () { + var jwt = nJwt.create({}, uuid()); assert(jwt.body.jti.match(/[a-zA-Z0-9]+[-]/)); }); - }); - }); -describe('base64 URL Encoding',function(){ - it('should do what rfc7515 says',function(){ - var key = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; +describe('base64 URL Encoding', function () { + it('should do what rfc7515 says', function () { + var key = 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow'; var headerString = '{"typ":"JWT",\r\n "alg":"HS256"}'; var payloadString = '{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}'; var compactHeader = nJwt.base64urlEncode(headerString); var compactBody = nJwt.base64urlEncode(payloadString); + assert.equal( compactHeader, 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9' @@ -100,14 +96,10 @@ describe('base64 URL Encoding',function(){ assert.equal( nJwt.Jwt.prototype.sign( - [compactHeader,compactBody].join('.'), - 'HS256',new Buffer(key,'base64') + [compactHeader, compactBody].join('.'), + 'HS256', new Buffer(key, 'base64') ), expectedSignature ); - }); -}); - - - +}); \ No newline at end of file diff --git a/test/errors.js b/test/errors.js new file mode 100644 index 0000000..95f57fa --- /dev/null +++ b/test/errors.js @@ -0,0 +1,397 @@ +'use strict'; + +var assert = require('chai').assert; +var errors = require('../lib/errors'); + +describe('Errors', function () { + describe('JwtError', function () { + describe('when creating a new JwtError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.JwtError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.JwtError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should set the message property', function () { + var fakeMessage = 'c9b9ec16-8c6a-41b8-ba12-eec5bff7d437'; + var error = new errors.JwtError(fakeMessage); + assert.equal(error.message, fakeMessage); + }); + + it('should set the userMessage property', function () { + var fakeMessage = '36e4439b-8036-4ce6-af8c-434e6343fca1'; + var error = new errors.JwtError(fakeMessage); + assert.equal(error.userMessage, fakeMessage); + }); + + it('should set the name property', function () { + var error = new errors.JwtError(); + assert.equal(error.name, 'JwtError'); + }); + + it('should set the message property', function () { + var fakeMessage = '9ac2400c-22f7-413c-8777-97f2ab2ab266'; + var error = new errors.JwtError(fakeMessage); + assert.equal(error.message, fakeMessage); + }); + }); + }); + + describe('UnsupportedSigningAlgorithmJwtError', function () { + describe('when creating a new UnsupportedSigningAlgorithmJwtError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of UnsupportedSigningAlgorithmJwtError', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.instanceOf(error, errors.UnsupportedSigningAlgorithmJwtError); + }); + + it('should set the name property', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.equal(error.name, 'UnsupportedSigningAlgorithmJwtError'); + }); + + it('should set the message property', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.equal(error.message, 'Unsupported signing algorithm'); + }); + + it('should set the userMessage property', function () { + var error = new errors.UnsupportedSigningAlgorithmJwtError(); + assert.equal(error.userMessage, 'Unsupported signing algorithm'); + }); + }); + }); + + describe('SigningKeyRequiredJwtError', function () { + describe('when creating a new SigningKeyRequiredJwtError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of SigningKeyRequiredJwtError', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.instanceOf(error, errors.SigningKeyRequiredJwtError); + }); + + it('should set the name property', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.equal(error.name, 'SigningKeyRequiredJwtError'); + }); + + it('should set the message property', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.equal(error.message, 'Signing key is required'); + }); + + it('should set the userMessage property', function () { + var error = new errors.SigningKeyRequiredJwtError(); + assert.equal(error.userMessage, 'Signing key is required'); + }); + }); + }); + + describe('JwtParseError', function () { + describe('when creating a new JwtParseError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.JwtParseError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.JwtParseError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of JwtParseError', function () { + var error = new errors.JwtParseError(); + assert.instanceOf(error, errors.JwtParseError); + }); + + it('should set the name property', function () { + var error = new errors.JwtParseError(); + assert.equal(error.name, 'JwtParseError'); + }); + + it('should set the message property', function () { + var error = new errors.JwtParseError(); + assert.equal(error.message, 'Jwt cannot be parsed'); + }); + + it('should set the userMessage property', function () { + var error = new errors.JwtParseError(); + assert.equal(error.userMessage, 'Jwt cannot be parsed'); + }); + + it('should set the jwtString property', function () { + var fakeJwtString = 'c23a1805-a8de-4829-aa0e-5977fe7e1447'; + var error = new errors.JwtParseError(fakeJwtString); + assert.equal(error.jwtString, fakeJwtString); + }); + + it('should set the parsedHeader property', function () { + var fakeParsedHeader = '6764e3a7-ea19-49e7-82e8-7f1e9bbf5ccf'; + var error = new errors.JwtParseError(null, fakeParsedHeader); + assert.equal(error.parsedHeader, fakeParsedHeader); + }); + + it('should set the parsedBody property', function () { + var fakeParsedBody = '938fd5d9-72e8-41a7-941a-3df0bcfa88cb'; + var error = new errors.JwtParseError(null, null, fakeParsedBody); + assert.equal(error.parsedBody, fakeParsedBody); + }); + + it('should set the innerError property', function () { + var fakeFnnerError = '07bee824-a6a6-45b5-b844-42407466adc7'; + var error = new errors.JwtParseError(null, null, null, fakeFnnerError); + assert.equal(error.innerError, fakeFnnerError); + }); + }); + }); + + describe('NotActiveJwtParseError', function () { + describe('when creating a new NotActiveJwtParseError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.NotActiveJwtParseError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.NotActiveJwtParseError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of JwtParseError', function () { + var error = new errors.NotActiveJwtParseError(); + assert.instanceOf(error, errors.JwtParseError); + }); + + it('should be an instance of NotActiveJwtParseError', function () { + var error = new errors.NotActiveJwtParseError(); + assert.instanceOf(error, errors.NotActiveJwtParseError); + }); + + it('should set the name property', function () { + var error = new errors.NotActiveJwtParseError(); + assert.equal(error.name, 'NotActiveJwtParseError'); + }); + + it('should set the message property', function () { + var error = new errors.NotActiveJwtParseError(); + assert.equal(error.message, 'Jwt not active'); + }); + + it('should set the userMessage property', function () { + var error = new errors.NotActiveJwtParseError(); + assert.equal(error.userMessage, 'Jwt not active'); + }); + + it('should set the jwtString property', function () { + var fakeJwtString = 'c23a1805-a8de-4829-aa0e-5977fe7e1447'; + var error = new errors.NotActiveJwtParseError(fakeJwtString); + assert.equal(error.jwtString, fakeJwtString); + }); + + it('should set the parsedHeader property', function () { + var fakeParsedHeader = '6764e3a7-ea19-49e7-82e8-7f1e9bbf5ccf'; + var error = new errors.NotActiveJwtParseError(null, fakeParsedHeader); + assert.equal(error.parsedHeader, fakeParsedHeader); + }); + + it('should set the parsedBody property', function () { + var fakeParsedBody = '938fd5d9-72e8-41a7-941a-3df0bcfa88cb'; + var error = new errors.NotActiveJwtParseError(null, null, fakeParsedBody); + assert.equal(error.parsedBody, fakeParsedBody); + }); + }); + }); + + describe('ExpiredJwtParseError', function () { + describe('when creating a new ExpiredJwtParseError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.ExpiredJwtParseError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.ExpiredJwtParseError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of JwtParseError', function () { + var error = new errors.ExpiredJwtParseError(); + assert.instanceOf(error, errors.JwtParseError); + }); + + it('should be an instance of ExpiredJwtParseError', function () { + var error = new errors.ExpiredJwtParseError(); + assert.instanceOf(error, errors.ExpiredJwtParseError); + }); + + it('should set the name property', function () { + var error = new errors.ExpiredJwtParseError(); + assert.equal(error.name, 'ExpiredJwtParseError'); + }); + + it('should set the message property', function () { + var error = new errors.ExpiredJwtParseError(); + assert.equal(error.message, 'Jwt is expired'); + }); + + it('should set the userMessage property', function () { + var error = new errors.ExpiredJwtParseError(); + assert.equal(error.userMessage, 'Jwt is expired'); + }); + + it('should set the jwtString property', function () { + var fakeJwtString = 'c23a1805-a8de-4829-aa0e-5977fe7e1447'; + var error = new errors.ExpiredJwtParseError(fakeJwtString); + assert.equal(error.jwtString, fakeJwtString); + }); + + it('should set the parsedHeader property', function () { + var fakeParsedHeader = '6764e3a7-ea19-49e7-82e8-7f1e9bbf5ccf'; + var error = new errors.ExpiredJwtParseError(null, fakeParsedHeader); + assert.equal(error.parsedHeader, fakeParsedHeader); + }); + + it('should set the parsedBody property', function () { + var fakeParsedBody = '938fd5d9-72e8-41a7-941a-3df0bcfa88cb'; + var error = new errors.ExpiredJwtParseError(null, null, fakeParsedBody); + assert.equal(error.parsedBody, fakeParsedBody); + }); + }); + }); + + describe('SignatureAlgorithmMismatchJwtParseError', function () { + describe('when creating a new SignatureAlgorithmMismatchJwtParseError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of JwtParseError', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.instanceOf(error, errors.JwtParseError); + }); + + it('should be an instance of SignatureAlgorithmMismatchJwtParseError', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.instanceOf(error, errors.SignatureAlgorithmMismatchJwtParseError); + }); + + it('should set the name property', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.equal(error.name, 'SignatureAlgorithmMismatchJwtParseError'); + }); + + it('should set the message property', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.equal(error.message, 'Unexpected signature algorithm'); + }); + + it('should set the userMessage property', function () { + var error = new errors.SignatureAlgorithmMismatchJwtParseError(); + assert.equal(error.userMessage, 'Unexpected signature algorithm'); + }); + + it('should set the jwtString property', function () { + var fakeJwtString = 'c23a1805-a8de-4829-aa0e-5977fe7e1447'; + var error = new errors.SignatureAlgorithmMismatchJwtParseError(fakeJwtString); + assert.equal(error.jwtString, fakeJwtString); + }); + + it('should set the parsedHeader property', function () { + var fakeParsedHeader = '6764e3a7-ea19-49e7-82e8-7f1e9bbf5ccf'; + var error = new errors.SignatureAlgorithmMismatchJwtParseError(null, fakeParsedHeader); + assert.equal(error.parsedHeader, fakeParsedHeader); + }); + + it('should set the parsedBody property', function () { + var fakeParsedBody = '938fd5d9-72e8-41a7-941a-3df0bcfa88cb'; + var error = new errors.SignatureAlgorithmMismatchJwtParseError(null, null, fakeParsedBody); + assert.equal(error.parsedBody, fakeParsedBody); + }); + }); + }); + + describe('SignatureMismatchJwtParseError', function () { + describe('when creating a new SignatureMismatchJwtParseError instance', function () { + it('should be an instance of Error', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.instanceOf(error, Error); + }); + + it('should be an instance of JwtError', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.instanceOf(error, errors.JwtError); + }); + + it('should be an instance of JwtParseError', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.instanceOf(error, errors.JwtParseError); + }); + + it('should be an instance of SignatureMismatchJwtParseError', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.instanceOf(error, errors.SignatureMismatchJwtParseError); + }); + + it('should set the name property', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.equal(error.name, 'SignatureMismatchJwtParseError'); + }); + + it('should set the message property', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.equal(error.message, 'Signature verification failed'); + }); + + it('should set the userMessage property', function () { + var error = new errors.SignatureMismatchJwtParseError(); + assert.equal(error.userMessage, 'Signature verification failed'); + }); + + it('should set the jwtString property', function () { + var fakeJwtString = 'c23a1805-a8de-4829-aa0e-5977fe7e1447'; + var error = new errors.SignatureMismatchJwtParseError(fakeJwtString); + assert.equal(error.jwtString, fakeJwtString); + }); + + it('should set the parsedHeader property', function () { + var fakeParsedHeader = '6764e3a7-ea19-49e7-82e8-7f1e9bbf5ccf'; + var error = new errors.SignatureMismatchJwtParseError(null, fakeParsedHeader); + assert.equal(error.parsedHeader, fakeParsedHeader); + }); + + it('should set the parsedBody property', function () { + var fakeParsedBody = '938fd5d9-72e8-41a7-941a-3df0bcfa88cb'; + var error = new errors.SignatureMismatchJwtParseError(null, null, fakeParsedBody); + assert.equal(error.parsedBody, fakeParsedBody); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/jwt-body.js b/test/jwt-body.js index 7bb02ee..6c1d8d4 100644 --- a/test/jwt-body.js +++ b/test/jwt-body.js @@ -1,7 +1,10 @@ -var assert = require('assert'); +'use strict'; + +var assert = require('chai').assert; var nJwt = require('../'); -describe('JwtBody',function() { - it('should construct itself if called without new',function(){ + +describe('JwtBody', function () { + it('should construct itself if called without new', function () { assert(nJwt.JwtBody() instanceof nJwt.JwtBody); }); }); \ No newline at end of file diff --git a/test/jwt-header.js b/test/jwt-header.js new file mode 100644 index 0000000..7eec846 --- /dev/null +++ b/test/jwt-header.js @@ -0,0 +1,10 @@ +'use strict'; + +var assert = require('chai').assert; +var nJwt = require('../'); + +describe('JwtHeader', function () { + it('should construct itself if called without new', function () { + assert(nJwt.JwtHeader() instanceof nJwt.JwtHeader); + }); +}); \ No newline at end of file diff --git a/test/jwt.js b/test/jwt.js index cecbfb5..c376141 100644 --- a/test/jwt.js +++ b/test/jwt.js @@ -1,45 +1,49 @@ +'use strict'; + var assert = require('chai').assert; var nJwt = require('../'); var uuid = require('uuid'); -var properties = require('../properties.json'); -describe('Jwt',function() { - it('should construct itself if called without new',function(){ +describe('Jwt', function () { + it('should construct itself if called without new', function () { assert(nJwt.Jwt() instanceof nJwt.Jwt); }); - describe('.setSubject()',function(){ - it('should set the sub claim',function(){ + describe('.setSubject()', function () { + it('should set the sub claim', function () { var sub = uuid(); - assert.equal(nJwt.Jwt().setSubject(sub).body.sub,sub); + assert.equal(new nJwt.Jwt().setSubject(sub).body.sub, sub); }); }); - describe('.setIssuer()',function(){ - it('should set the iss claim',function(){ + + describe('.setIssuer()', function () { + it('should set the iss claim', function () { var iss = uuid(); - assert.equal(nJwt.Jwt().setIssuer(iss).body.iss,iss); + assert.equal(new nJwt.Jwt().setIssuer(iss).body.iss, iss); }); }); - describe('.setExpiration()',function(){ - it('should accept Date types and set the exp claim to a UNIX timestamp value',function(){ + describe('.setExpiration()', function () { + it('should accept Date types and set the exp claim to a UNIX timestamp value', function () { var future = new Date('2025'); assert.equal( - nJwt.Jwt().setExpiration(future).body.exp, - future.getTime()/1000 + new nJwt.Jwt().setExpiration(future).body.exp, + future.getTime() / 1000 ); }); - it('should accept milliseconds values and set the exp claim to a UNIX timestamp value',function(){ + + it('should accept milliseconds values and set the exp claim to a UNIX timestamp value', function () { var future = new Date('2025').getTime(); assert.equal( - nJwt.Jwt().setExpiration(future).body.exp, - future/1000 + new nJwt.Jwt().setExpiration(future).body.exp, + future / 1000 ); }); - it('should allow me to remove the exp field',function(){ - var jwt = nJwt.create({},uuid()); - var oneHourFromNow = Math.floor(new Date().getTime()/1000) + (60*60); - assert.equal(nJwt.create({},uuid()).body.exp , oneHourFromNow); + + it('should allow me to remove the exp field', function () { + var jwt = nJwt.create({}, uuid()); + var oneHourFromNow = Math.floor(new Date().getTime() / 1000) + (60 * 60); + assert.equal(nJwt.create({}, uuid()).body.exp, oneHourFromNow); assert.equal(jwt.setExpiration().body.exp, undefined); assert.equal(jwt.setExpiration(false).body.exp, undefined); assert.equal(jwt.setExpiration(null).body.exp, undefined); @@ -47,26 +51,26 @@ describe('Jwt',function() { }); }); - - describe('.setNotBefore()',function(){ - it('should accept Date types and set the nbf claim to a UNIX timestamp value',function(){ + describe('.setNotBefore()', function () { + it('should accept Date types and set the nbf claim to a UNIX timestamp value', function () { var future = new Date('2025'); assert.equal( - nJwt.Jwt().setNotBefore(future).body.nbf, - future.getTime()/1000 + new nJwt.Jwt().setNotBefore(future).body.nbf, + future.getTime() / 1000 ); }); - it('should accept milliseconds values and set the nbf claim to a UNIX timestamp value',function(){ + + it('should accept milliseconds values and set the nbf claim to a UNIX timestamp value', function () { var future = new Date('2025').getTime(); assert.equal( - nJwt.Jwt().setNotBefore(future).body.nbf, - future/1000 + new nJwt.Jwt().setNotBefore(future).body.nbf, + future / 1000 ); }); - it('should allow me to remove the nbf field',function(){ - var jwt = nJwt.create({},uuid()); - var oneHourFromNow = Math.floor(new Date().getTime()/1000) + (60*60); - assert.equal(nJwt.create({},uuid()).body.nbf , undefined); + + it('should allow me to remove the nbf field', function () { + var jwt = nJwt.create({}, uuid()); + assert.equal(nJwt.create({}, uuid()).body.nbf, undefined); assert.equal(jwt.setNotBefore().body.nbf, undefined); assert.equal(jwt.setNotBefore(false).body.nbf, undefined); assert.equal(jwt.setNotBefore(null).body.nbf, undefined); @@ -74,26 +78,26 @@ describe('Jwt',function() { }); }); - - describe('.setSigningAlgorithm()',function(){ - it('should throw if you specify an alg but not a key',function(){ - assert.throws(function(){ - nJwt.Jwt().setSigningAlgorithm('HS256').compact(); - },properties.errors.SIGNING_KEY_REQUIRED); + describe('.setSigningAlgorithm()', function () { + it('should throw if you specify an alg but not a key', function () { + assert.throws(function () { + new nJwt.Jwt().setSigningAlgorithm('HS256').compact(); + }, nJwt.SigningKeyRequiredJwtError); }); }); - describe('.sign()',function(){ - it('should throw if you give it an unknown algoritm',function(){ - assert.throws(function(){ - nJwt.Jwt().sign('hello'); - },properties.errors.UNSUPPORTED_SIGNING_ALG); + describe('.sign()', function () { + it('should throw if you give it an unknown algoritm', function () { + assert.throws(function () { + new nJwt.Jwt().sign('hello'); + }, nJwt.UnsupportedSigningAlgorithmJwtError); }); }); - describe('.toString()',function(){ - it('should return the compacted JWT string',function(){ - var jwt = nJwt.create({},uuid()); - assert.equal(jwt.compact(),jwt.toString()); + + describe('.toString()', function () { + it('should return the compacted JWT string', function () { + var jwt = nJwt.create({}, uuid()); + assert.equal(jwt.compact(), jwt.toString()); }); }); }); diff --git a/test/others.js b/test/others.js index 4a10d36..5502df6 100644 --- a/test/others.js +++ b/test/others.js @@ -1,62 +1,68 @@ +'use strict'; + var assert = require('chai').assert; var uuid = require('uuid'); var nJwt = require('../'); var jsonwebtoken = require('jsonwebtoken'); var jwtSimple = require('jwt-simple'); -describe('this library',function () { - it('should generate tokens that can be verified by jsonwebtoken',function(done){ +describe('this library', function () { + it('should generate tokens that can be verified by jsonwebtoken', function (done) { var key = uuid(); - var claims = {hello:uuid()}; - var jwt = nJwt.create(claims,key); + var claims = {hello: uuid()}; + var jwt = nJwt.create(claims, key); var token = jwt.compact(); - assert.doesNotThrow(function(){ - jsonwebtoken.verify(token,key); + + assert.doesNotThrow(function () { + jsonwebtoken.verify(token, key); }); - jsonwebtoken.verify(token,key,function(err,claimsResult){ - assert.isNull(err,'An unexpcted error was returned'); - assert.equal(jwt.body.hello,claimsResult.hello); - assert.equal(jwt.body.jti,claimsResult.jti); - assert.equal(jwt.body.iat,claimsResult.iat); + + jsonwebtoken.verify(token, key, function (err, claimsResult) { + assert.isNull(err, 'An unexpcted error was returned'); + assert.equal(jwt.body.hello, claimsResult.hello); + assert.equal(jwt.body.jti, claimsResult.jti); + assert.equal(jwt.body.iat, claimsResult.iat); done(); }); }); - it('should be able to verify tokens from jsonwebtoken',function(done){ - var claims = {hello:uuid()}; + it('should be able to verify tokens from jsonwebtoken', function (done) { + var claims = {hello: uuid()}; var key = uuid(); var token = jsonwebtoken.sign(claims, key); - nJwt.verify(token,key,function(err,jwt){ - assert.isNull(err,'An unexpcted error was returned'); - assert.equal(jwt.body.hello,claims.hello); + + nJwt.verify(token, key, function (err, jwt) { + assert.isNull(err, 'An unexpcted error was returned'); + assert.equal(jwt.body.hello, claims.hello); done(); }); }); - it('should generate tokens that can be verified by jwt-simple',function(done){ + it('should generate tokens that can be verified by jwt-simple', function (done) { var key = uuid(); - var claims = {hello:uuid()}; - var jwt = nJwt.create(claims,key); + var claims = {hello: uuid()}; + var jwt = nJwt.create(claims, key); var token = jwt.compact(); var decoded; - assert.doesNotThrow(function(){ + + assert.doesNotThrow(function () { decoded = jwtSimple.decode(token, key); }); - assert.equal(jwt.body.hello,decoded.hello); - assert.equal(jwt.body.jti,decoded.jti); - assert.equal(jwt.body.iat,decoded.iat); + assert.equal(jwt.body.hello, decoded.hello); + assert.equal(jwt.body.jti, decoded.jti); + assert.equal(jwt.body.iat, decoded.iat); done(); - }); - it('should be able to verify tokens from jwt-simple',function(done){ - var claims = {hello:uuid()}; + it('should be able to verify tokens from jwt-simple', function (done) { + var claims = {hello: uuid()}; var key = uuid(); var token = jwtSimple.encode(claims, key); - nJwt.verify(token,key,function(err,jwt){ - assert.isNull(err,'An unexpcted error was returned'); - assert.equal(jwt.body.hello,claims.hello); + + nJwt.verify(token, key, function (err, jwt) { + assert.isNull(err, 'An unexpcted error was returned'); + assert.equal(jwt.body.hello, claims.hello); done(); }); }); diff --git a/test/rsa.js b/test/rsa.js index 5c8802d..b8f55d6 100644 --- a/test/rsa.js +++ b/test/rsa.js @@ -1,91 +1,76 @@ -var assert = require('chai').assert; - -var nJwt = require('../'); - -var properties = require('../properties.json'); +'use strict'; var fs = require('fs'); var path = require('path'); +var assert = require('chai').assert; +var nJwt = require('../'); var pair = { - public: fs.readFileSync(path.join(__dirname,'rsa.pub'),'utf8'), - private: fs.readFileSync(path.join(__dirname,'rsa.priv'),'utf8') + public: fs.readFileSync(path.join(__dirname, 'rsa.pub'), 'utf8'), + private: fs.readFileSync(path.join(__dirname, 'rsa.priv'), 'utf8') }; -describe('JWT Builder',function(){ - describe('when RS256 is specified',function(){ +describe('JWT Builder', function () { + describe('when RS256 is specified', function () { var token = new nJwt.Jwt({}) .setSigningAlgorithm('RS256') .setSigningKey(pair.private) .compact(); - var verifier = new nJwt.Verifier().setSigningKey('RS256',pair.public); - var result; - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); - }); - }); - it('should create the token with the appropriate header values',function(){ + + it('should create the token with the appropriate header values', function () { assert.isNotNull(token); }); }); }); -describe('a token that is signed with an RSA private key',function() { - - var claims = {foo:'bar'}; +describe('a token that is signed with an RSA private key', function () { + var claims = {foo: 'bar'}; var token = new nJwt.Jwt(claims) .setSigningAlgorithm('RS256') .setSigningKey(pair.private) .compact(); - describe('and a verifier that is configurd with the RSA public key',function(){ - + describe('and a verifier that is configurd with the RSA public key', function () { var verifier = new nJwt.Verifier() .setSigningAlgorithm('RS256') .setSigningKey(pair.public); var result; - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; done(); }); }); - it('should validate and return the token payload',function(){ - assert.isNull(result[0],'An unexpected error was returned'); - assert.isObject(result[1],'A result was not returned'); - assert.equal(result[1].body.foo,claims.foo); + it('should validate and return the token payload', function () { + assert.isNull(result[0], 'An unexpected error was returned'); + assert.isObject(result[1], 'A result was not returned'); + assert.equal(result[1].body.foo, claims.foo); }); }); - }); -describe('a token that is signed with an RSA public key but header alg of HS256',function(){ - - var token = new nJwt.Jwt({foo:'bar'}) +describe('a token that is signed with an RSA public key but header alg of HS256', function () { + var token = new nJwt.Jwt({foo: 'bar'}) .setSigningAlgorithm('HS256') .setSigningKey(pair.public) .compact(); - describe('and a verifier configured with RS256 and the same public key for vefification',function(){ - + describe('and a verifier configured with RS256 and the same public key for vefification', function () { var result; - before(function(done){ - nJwt.verify(token,pair.public,'RS256',function(err,res){ - result = [err,res]; + before(function (done) { + nJwt.verify(token, pair.public, 'RS256', function (err, res) { + result = [err, res]; done(); }); }); - it('should return SIGNATURE_ALGORITHM_MISMTACH error',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].message,properties.errors.SIGNATURE_ALGORITHM_MISMTACH); + it('should return SignatureAlgorithmMismatchJwtParseError error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.SignatureAlgorithmMismatchJwtParseError); }); }); - -}); +}); \ No newline at end of file diff --git a/test/verifier.js b/test/verifier.js index de3fd01..c9d7083 100644 --- a/test/verifier.js +++ b/test/verifier.js @@ -1,331 +1,327 @@ +'use strict'; + var fs = require('fs'); var path = require('path'); var uuid = require('uuid'); var secureRandom = require('secure-random'); var assert = require('chai').assert; - var nJwt = require('../'); -var properties = require('../properties.json'); -describe('Verifier',function(){ - it('should construct itself if called without new',function(){ +describe('Verifier', function () { + it('should construct itself if called without new', function () { assert(nJwt.Verifier() instanceof nJwt.Verifier); }); -}); -describe('Verifier().setSigningAlgorithm() ',function(){ - describe('if called with an unsupported algorithm',function(){ + describe('.setSigningAlgorithm()', function () { + describe('if called with an unsupported algorithm', function () { + it('should throw UnsupportedSigningAlgorithmJwtError', function () { + assert.throws(function () { + new nJwt.Verifier().setSigningAlgorithm('unsupported'); + }, nJwt.UnsupportedSigningAlgorithmJwtError); + }); + }); + }); - it('should throw UNSUPPORTED_SIGNING_ALG',function(){ - assert.throws(function(){ - new nJwt.Verifier().setSigningAlgorithm('unsupported'); - },properties.errors.UNSUPPORTED_SIGNING_ALG); + describe('.verify()', function () { + it('should persist the original token to the toString() invocation', function () { + var token = 'eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjMifQ.p6bizskaJLAheVyRhQEMR-60PkH_jtLVYgMy1qTjCoc'; + assert.equal(token, nJwt.verify(token).toString()); }); - }); -}); + it('should not alter the JWT, it should be compact-able as the same token', function () { + var orignalJwt = new nJwt.Jwt({hello: uuid()}, false).setSigningAlgorithm('none'); + var originalToken = orignalJwt.compact(); + var verifiedJwt = nJwt.verify(originalToken); + assert.equal(originalToken, verifiedJwt.compact()); + }); -describe('.verify()',function(){ - it('should persist the original token to the toString() invocation',function(){ - var token = 'eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjMifQ.p6bizskaJLAheVyRhQEMR-60PkH_jtLVYgMy1qTjCoc'; - assert.equal(token,nJwt.verify(token).toString()); - }); + describe('if given only a token', function () { + it('should verify tokens that are alg none', function () { + var claims = {hello: uuid()}; + var token = new nJwt.Jwt(claims) + .setSigningAlgorithm('none') + .compact(); - it('should not alter the JWT, it should be compact-able as the same token',function(){ - var orignalJwt = new nJwt.Jwt({hello: uuid()}, false).setSigningAlgorithm('none'); - var originalToken = orignalJwt.compact(); - var verifiedJwt = nJwt.verify(originalToken); - assert.equal(originalToken, verifiedJwt.compact()); - }); + assert.doesNotThrow(function () { + nJwt.verify(token); + }); + }); - describe('if given only a token',function(){ - it('should verify tokens that are alg none',function(){ - var claims = {hello: uuid()}; - var token = new nJwt.Jwt(claims) - .setSigningAlgorithm('none') - .compact(); - assert.doesNotThrow(function(){ - nJwt.verify(token); + it('should reject tokens that specify an alg', function () { + var claims = {hello: uuid()}; + var key = uuid(); + var token = new nJwt.create(claims, key) + .compact(); + + assert.throws(function () { + nJwt.verify(token); + }, nJwt.SignatureAlgorithmMismatchJwtParseError); }); }); - it('should reject tokens that specify an alg',function(){ - var claims = {hello: uuid()}; - var key = uuid(); - var token = new nJwt.create(claims,key) - .compact(); - assert.throws(function(){ - nJwt.verify(token); - },properties.errors.SIGNATURE_ALGORITHM_MISMTACH); - }); - }); - it('should return PARSE_ERROR if the header is not JSON',function(){ - assert.throws(function(){ - nJwt.verify("noavalidheader.notavalidbody"); - },properties.errors.PARSE_ERROR); - }); + it('should return JwtParseError if the header is not JSON', function () { + assert.throws(function () { + nJwt.verify('noavalidheader.notavalidbody'); + }, nJwt.JwtParseError); + }); - it('should give me the original string on the parse error object',function(done){ - var invalidJwt = 'noavalidheader.notavalidbody'; - nJwt.verify(invalidJwt,function(err){ - assert.equal(err.jwtString, invalidJwt); - done(); + it('should give me the original string on the parse error object', function (done) { + var invalidJwt = 'noavalidheader.notavalidbody'; + nJwt.verify(invalidJwt, function (err) { + assert.equal(err.jwtString, invalidJwt); + done(); + }); }); - }); - it('should return PARSE_ERROR if the body is not JSON',function(){ - var header = nJwt.JwtHeader({type:'JWT',alg:'HS256'}).compact(); - assert.throws(function(){ - nJwt.verify(header+".notavalidbody"); - },properties.errors.PARSE_ERROR); - }); + it('should return JwtParseError if the body is not JSON', function () { + var header = new nJwt.JwtHeader({type: 'JWT', alg: 'HS256'}).compact(); + assert.throws(function () { + nJwt.verify(header + '.notavalidbody'); + }, nJwt.JwtParseError); + }); - it('should give me the parsed header on the error object if the body fails',function(done){ - var header = nJwt.JwtHeader({typ:'JWT',alg:uuid()}); - var invalidJwt = header.compact()+'.notavalidbody'; - nJwt.verify(invalidJwt,function(err){ - assert.equal(err.jwtString, invalidJwt); - assert.equal(err.parsedHeader.alg, header.alg); - done(); + it('should give me the parsed header on the error object if the body fails', function (done) { + var header = new nJwt.JwtHeader({typ: 'JWT', alg: uuid()}); + var invalidJwt = header.compact() + '.notavalidbody'; + nJwt.verify(invalidJwt, function (err) { + assert.equal(err.jwtString, invalidJwt); + assert.equal(err.parsedHeader.alg, header.alg); + done(); + }); }); - }); -}); + it('should support sync usage', function () { + var verifier = new nJwt.Verifier() + .setSigningAlgorithm('none'); + var claims = {hello: uuid()}; + var token = new nJwt.Jwt(claims).compact(); + var verifiedToken; -describe('Verifier().verify() ',function(){ + assert.doesNotThrow(function () { + verifiedToken = verifier.verify(token); + }); - it('should support sync usage',function(){ - var verifier = new nJwt.Verifier() - .setSigningAlgorithm('none'); - var claims = {hello: uuid()}; - var token = new nJwt.Jwt(claims).compact(); - var verifiedToken; - assert.doesNotThrow(function(){ - verifiedToken = verifier.verify(token); - }); + assert(verifiedToken instanceof nJwt.Jwt); + assert.equal(verifiedToken.body.hello, claims.hello); - assert(verifiedToken instanceof nJwt.Jwt); - assert.equal(verifiedToken.body.hello,claims.hello); + assert.throws(function () { + verifiedToken = verifier.verify('invalid token'); + }, nJwt.JwtParseError); - assert.throws(function(){ - verifiedToken = verifier.verify('invalid token'); - },properties.errors.PARSE_ERROR); + }); - }); + it('should return the jwt string, header and body on error objects', function (done) { + var jwt = new nJwt.Jwt({expiredToken: uuid()}) + .setExpiration(new Date().getTime() - 1000); + var token = jwt.compact(); - it('should return the jwt string, header and body on error objects',function(done){ - var jwt = new nJwt.Jwt({expiredToken:uuid()}) - .setExpiration(new Date().getTime()-1000); - var token = jwt.compact(); - nJwt.verify(token,function(err){ - assert.equal(err.jwtString,token); - assert.equal(err.parsedHeader.alg,jwt.header.alg); - assert.equal(err.parsedBody.expiredToken,jwt.body.expiredToken); - assert.equal(err.userMessage,properties.errors.EXPIRED); - done(); + nJwt.verify(token, function (err) { + assert.equal(err.jwtString, token); + assert.equal(err.parsedHeader.alg, jwt.header.alg); + assert.equal(err.parsedBody.expiredToken, jwt.body.expiredToken); + assert.instanceOf(err, nJwt.ExpiredJwtParseError); + done(); + }); }); - }); - it('should return the jwt string, header and body on error objects with not active message',function(done){ - var jwt = new nJwt.Jwt({notActiveToken:uuid()}) - .setNotBefore(new Date().getTime()+1000); - var token = jwt.compact(); - nJwt.verify(token,function(err){ - assert.equal(err.jwtString,token); - assert.equal(err.parsedHeader.alg,jwt.header.alg); - assert.equal(err.parsedBody.notActiveToken,jwt.body.notActiveToken); - assert.equal(err.userMessage,properties.errors.NOT_ACTIVE); - done(); + it('should return the jwt string, header and body on error objects with not active message', function (done) { + var jwt = new nJwt.Jwt({notActiveToken: uuid()}) + .setNotBefore(new Date().getTime() + 1000); + var token = jwt.compact(); + nJwt.verify(token, function (err) { + assert.equal(err.jwtString, token); + assert.equal(err.parsedHeader.alg, jwt.header.alg); + assert.equal(err.parsedBody.notActiveToken, jwt.body.notActiveToken); + assert.instanceOf(err, nJwt.NotActiveJwtParseError); + done(); + }); }); - }); - it('should return the jwt string, header and body with null error objects',function(done){ - var jwt = new nJwt.Jwt({notActiveToken:uuid()}); - var token = jwt.compact(); - nJwt.verify(token,function(err){ - assert.isNull(err); - assert.isNotNull(token); - done(); + it('should return the jwt string, header and body with null error objects', function (done) { + var jwt = new nJwt.Jwt({notActiveToken: uuid()}); + var token = jwt.compact(); + nJwt.verify(token, function (err) { + assert.isNull(err); + assert.isNotNull(token); + done(); + }); }); - }); - describe('when configured to expect no verification',function(){ - var verifier = new nJwt.Verifier() - .setSigningAlgorithm('none'); + describe('when configured to expect no verification', function () { + var verifier = new nJwt.Verifier() + .setSigningAlgorithm('none'); - var claims = {hello: uuid()}; + var claims = {hello: uuid()}; - describe('and given an unsigned token',function(){ - var result; - var token = new nJwt.Jwt(claims).compact(); + describe('and given an unsigned token', function () { + var result; + var token = new nJwt.Jwt(claims).compact(); - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return the JWT object',function(){ - assert.isNull(result[0],'An unexpcted error was returned'); - assert.equal(result[1].body.hello,claims.hello); + it('should return the JWT object', function () { + assert.isNull(result[0], 'An unexpcted error was returned'); + assert.equal(result[1].body.hello, claims.hello); + }); }); - }); - - describe('and given an expired token',function(){ - var result; - var jwt = new nJwt.Jwt({expiredToken:'x'}) - .setExpiration(new Date().getTime()-1000); + describe('and given an expired token', function () { + var result; + var jwt = new nJwt.Jwt({expiredToken: 'x'}) + .setExpiration(new Date().getTime() - (10 * 1000)); - before(function(done){ - verifier.verify(jwt.compact(),function(err,res){ - result = [err,res]; - done(); + before(function (done) { + verifier.verify(jwt.compact(), function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return EXPIRED',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.EXPIRED); + it('should return ExpiredJwtParseError error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.ExpiredJwtParseError); + }); }); - }); - describe('and given a not active token',function(){ - var result; - var jwt = new nJwt.Jwt({notActiveToken:'x'}) - .setNotBefore(new Date().getTime()+1000); + describe('and given a not active token', function () { + var result; + var jwt = new nJwt.Jwt({notActiveToken: 'x'}) + .setNotBefore(new Date().getTime() + (10 * 1000)); - - before(function(done){ - verifier.verify(jwt.compact(),function(err,res){ - result = [err,res]; - done(); + before(function (done) { + verifier.verify(jwt.compact(), function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return NOT_ACTIVE',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.NOT_ACTIVE); + it('should return NotActiveJwtParseError error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.NotActiveJwtParseError); + }); }); - }); - - describe('and given an signed token',function(){ - var result; - var token = new nJwt.Jwt({foo:'bar'}) - .setSigningAlgorithm('HS256') - .setSigningKey('foo') - .compact(); - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); + describe('and given an signed token', function () { + var result; + var token = new nJwt.Jwt({foo: 'bar'}) + .setSigningAlgorithm('HS256') + .setSigningKey('foo') + .compact(); + + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return an unexpected algorithm error',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.SIGNATURE_ALGORITHM_MISMTACH); + it('should return an unexpected algorithm error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.SignatureAlgorithmMismatchJwtParseError); + }); }); }); - }); - - describe('when configured to expect signature verification',function(){ - var verifier = new nJwt.Verifier() - .setSigningAlgorithm('HS256') - .setSigningKey('hello'); - - describe('and given an unsigned token',function(){ + describe('when configured to expect signature verification', function () { + var verifier = new nJwt.Verifier() + .setSigningAlgorithm('HS256') + .setSigningKey('hello'); - var result; - var token = new nJwt.Jwt({foo:'bar'}).compact(); + describe('and given an unsigned token', function () { + var result; + var token = new nJwt.Jwt({foo: 'bar'}).compact(); - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return SIGNATURE_ALGORITHM_MISMTACH',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.SIGNATURE_ALGORITHM_MISMTACH); + it('should return SignatureAlgorithmMismatchJwtParseError error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.SignatureAlgorithmMismatchJwtParseError); + }); }); }); - }); + describe('when configured to expect signature verification', function () { + var key = secureRandom(256, {type: 'Buffer'}); - describe('when configured to expect signature verification',function(){ - var key = secureRandom(256,{type: 'Buffer'}); + var verifier = new nJwt.Verifier() + .setSigningAlgorithm('HS256') + .setSigningKey(key); - var verifier = new nJwt.Verifier() - .setSigningAlgorithm('HS256') - .setSigningKey(key); + var claims = {hello: uuid()}; - var claims = {hello:uuid()}; + describe('and given a token that was signed with the same key', function () { + var result; - describe('and given a token that was signed with the same key',function(){ - var result; - var token = new nJwt.Jwt(claims) - .setSigningAlgorithm('HS256') - .setSigningKey(key) - .compact(); + var token = new nJwt.Jwt(claims) + .setSigningAlgorithm('HS256') + .setSigningKey(key) + .compact(); - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return the JWT object',function(){ - assert.isNull(result[0],'An unexpcted error was returned'); - assert.equal(result[1].body.hello,claims.hello); + it('should return the JWT object', function () { + assert.isNull(result[0], 'An unexpcted error was returned'); + assert.equal(result[1].body.hello, claims.hello); + }); }); - }); - describe('and given a token that was signed with a different key',function(){ - var result; - var token = new nJwt.Jwt(claims) - .setSigningAlgorithm('HS256') - .setSigningKey('not the same key') - .compact(); + describe('and given a token that was signed with a different key', function () { + var result; - before(function(done){ - verifier.verify(token,function(err,res){ - result = [err,res]; - done(); + var token = new nJwt.Jwt(claims) + .setSigningAlgorithm('HS256') + .setSigningKey('not the same key') + .compact(); + + before(function (done) { + verifier.verify(token, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return SIGNATURE_MISMTACH',function(){ - assert.isNotNull(result[0],'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.SIGNATURE_MISMTACH); + it('should return SignatureMismatchJwtParseError error', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.SignatureMismatchJwtParseError); + }); }); }); - }); - describe('when verifying an invalid ECDSA token', function () { - var result = null; - var ecdsaPublicKey = fs.readFileSync(path.join(__dirname,'ecdsa.pub'),'utf8'); - var invalidToken = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.82wXTCDa4VHEAaDlq7PyqOqNbMGwDiXSt_n1nKGH43w'; + describe('when verifying an invalid ECDSA token', function () { + var result = null; + var ecdsaPublicKey = fs.readFileSync(path.join(__dirname, 'ecdsa.pub'), 'utf8'); + var invalidToken = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.82wXTCDa4VHEAaDlq7PyqOqNbMGwDiXSt_n1nKGH43w'; - before(function(done){ - var verifier = new nJwt.Verifier() - .setSigningAlgorithm('ES512') - .setSigningKey(ecdsaPublicKey); + before(function (done) { + var verifier = new nJwt.Verifier() + .setSigningAlgorithm('ES512') + .setSigningKey(ecdsaPublicKey); - verifier.verify(invalidToken, function(err,res){ - result = [err,res]; - done(); + verifier.verify(invalidToken, function (err, res) { + result = [err, res]; + done(); + }); }); - }); - it('should return SIGNATURE_MISMTACH',function(){ - assert.isNotNull(result[0], 'An error was not returned'); - assert.equal(result[0].userMessage,properties.errors.SIGNATURE_MISMTACH); + it('should return SignatureMismatchJwtParseError', function () { + assert.isNotNull(result[0], 'An error was not returned'); + assert.instanceOf(result[0], nJwt.SignatureMismatchJwtParseError); + }); }); }); });