all files / src/ JWTValidator.js

100% Statements 34/34
100% Branches 32/32
100% Functions 7/7
100% Lines 34/34
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                21×   21× 19×     21×             32×       32× 32×       32× 32×       32× 32×       34×             34×         32×           30× 30×               26×     26× 16×     16×       26×     26×        
'use strict';
 
var _ = require('underscore'),
    jwt = require('jwt-simple'),
    Class = require('class.extend'),
    APIError = require('./APIError'),
    BEARER_REGEX = /^Bearer /,
    INVALID_TOKEN_MSG = 'Invalid authorization token';
 
function createError(title, detail, headerFieldName) {
   var err = new APIError(title, detail);
 
   if (headerFieldName) {
      err.addSource(APIError.LOC_HEADER, headerFieldName);
   }
 
   return err;
}
 
function invalidFieldResp(field, headerFieldName) {
   return createError(INVALID_TOKEN_MSG, 'Invalid "' + field + '" value in the token.', headerFieldName);
}
 
module.exports = Class.extend({
 
   init: function(publicKey) {
      this._publicKey = publicKey;
   },
 
   issuer: function(issuer) {
      this._issuer = issuer;
      return this;
   },
 
   audience: function(audience) {
      this._audience = audience;
      return this;
   },
 
   revocation: function(revokedIDs) {
      this._revokedIDs = revokedIDs;
      return this;
   },
 
   validate: function(rawTokenString, isBearerFormat, headerFieldName) {
      var errors = [],
          tokenString = (isBearerFormat ? (rawTokenString || '').replace(BEARER_REGEX, '') : rawTokenString),
          token;
 
      // These first several types of errors must stop the flow of the rest of the
      // validation ... they can not be compounded because they result in no token to
      // actually validate.
      if (_.isEmpty(tokenString)) {
         return {
            errors: [ createError('No token supplied', undefined, headerFieldName) ],
         };
      }
 
      if (isBearerFormat && !BEARER_REGEX.test(rawTokenString)) {
         return {
            // eslint-disable-next-line max-len
            errors: [ createError((headerFieldName ? (headerFieldName + ' header') : 'Bearer token') + ' not in correct format', undefined, headerFieldName) ],
         };
      }
 
      try {
         token = jwt.decode(tokenString, this._publicKey, false, 'RS256');
      } catch(err) {
         return {
            errors: [ createError(INVALID_TOKEN_MSG, err.message, headerFieldName) ],
         };
      }
 
      // The rest of these errors can be built up into an array of errors so that you can
      // report all the errors that were found with the decoded token.
      if (!_.isEmpty(this._issuer) && token.iss !== this._issuer) {
         errors.push(invalidFieldResp('iss', headerFieldName));
      }
 
      if (!_.isEmpty(this._audience)) {
         if (_.isArray(token.aud) && !_.contains(token.aud, this._audience)) {
            errors.push(invalidFieldResp('aud', headerFieldName));
         }
 
         if (!_.isArray(token.aud) && token.aud !== this._audience) {
            errors.push(invalidFieldResp('aud', headerFieldName));
         }
      }
 
      if (!_.isEmpty(this._revokedIDs) && _.contains(this._revokedIDs, token.jti)) {
         errors.push(createError(INVALID_TOKEN_MSG, 'Token has been revoked', headerFieldName));
      }
 
      return { errors: errors, token: token };
   },
 
});