<commit_before>/**
  AuthGateway Middleware
  @description Exposes an API to create a user token schema and registers
    the required middleware for verifying and unpacking user data into the req
    object, also exposes an API for generating user tokens with a given payload
  @author tylerFowler
**/
'use strict';

const crypto  = require('crypto');
const _       = require('underscore');
const Promise = require('bluebird');
const JWT     = Promise.promisifyAll(require('jsonwebtoken'));
const RoleMgr = require('./role_manager');

module.exports = exports = AuthGW;

// secondary components
let middleware = exports.Middleware = {};

/**
  @constructor AuthGW
  @desc Constructor function that creates a new gateway manager instance with
    the configured settings
  @param { String[] } roles - list of all possible user roles from lowest
    access level to highest access level :optional
  @param { String } authorityName - serves as the JWT 'issuer' name
  @param { Object[] } dataSchema - defines the keys to r/w to/from tokens
  @param { Object } opts
  @returns AuthGW instance
**/
function AuthGW(roles, authorityName, dataSchema, opts) {
  if (!Array.isArray(roles)) roles = [roles];

  this.roles = roles;
  this.authorityName = authorityName;

  // if required is omitted default to false
  this.dataSchema = dataSchema.map(s => _.defaults(s, { required: false }));

  this.opts = Object.assign({
    tokenHeader: 'x-access-token',
    tokenSecret: crypto.randomBytes(24).toString('hex'),
    tokenExpiredCode: 419
  }, opts);

  this.RoleManager = new RoleMgr(roles);
}

/**
  @name AuthGW#createToken
  @desc Creates a new auth token with the given role & data
  @param { Object } data written to the token, must have keys required by schema
  @param { String } role must be one of the roles given to this authgw instance
  @param { Number } expiry time of the token in minutes
  @returns { String } token contains the raw, encrypted token
  @throws InvalidRoleError if the given role is not listed
**/
AuthGw.prototype.createToken = function createToken(data, role, expiry) {
  if (!_.contains(this.roles, role))
    throw new AuthGWError.InvalidRoleError(role);

  return JWT.sign(
    { data, role },
    this.opts.tokenSecret,
    {
      algorithm: 'HS256', // HMAC w/ SHA-256
      expiresIn: expiry * 60,
      issuer: this.authorityName
    }
  );
};

/**
  @name AuthGW#verifyAuthToken
  @desc Verifies that a given token is valid, unexpired, and trusted
  @param { String } token is the encrypted auth token<commit_msg>Use async fn for token creation<commit_after>/**
  AuthGateway Middleware
  @description Exposes an API to create a user token schema and registers
    the required middleware for verifying and unpacking user data into the req
    object, also exposes an API for generating user tokens with a given payload
  @author tylerFowler
**/
'use strict';

const crypto  = require('crypto');
const _       = require('underscore');
const Promise = require('bluebird');
const JWT     = Promise.promisifyAll(require('jsonwebtoken'));
const RoleMgr = require('./role_manager');

module.exports = exports = AuthGW;

// secondary components
let middleware = exports.Middleware = {};

/**
  @constructor AuthGW
  @desc Constructor function that creates a new gateway manager instance with
    the configured settings
  @param { String[] } roles - list of all possible user roles from lowest
    access level to highest access level :optional
  @param { String } authorityName - serves as the JWT 'issuer' name
  @param { Object[] } dataSchema - defines the keys to r/w to/from tokens
  @param { Object } opts
  @param { String } opts.tokenHeader :default => 'x-access-token'
  @param { String } opts.tokenAlgo :default => 'HS256'
  @param { String|Buffer } opts.tokenSecret :default => 24 random bytes in hex
  @param { Number } opts.tokenExpiredCode :default => 419 (Token Expired)
  @returns AuthGW instance
**/
function AuthGW(roles, authorityName, dataSchema, opts) {
  if (!Array.isArray(roles)) roles = [roles];

  this.roles = roles;
  this.authorityName = authorityName;

  // if required is omitted default to false
  this.dataSchema = dataSchema.map(s => _.defaults(s, { required: false }));

  this.opts = Object.assign({
    tokenHeader: 'x-access-token',
    tokenAlgo: 'HS256', // HMAC w/ SHA-256
    tokenSecret: crypto.randomBytes(24),
    tokenExpiredCode: 419
  }, opts);

  this.opts.tokenSecret = this.opts.tokenSecret.toString('hex');

  this.RoleManager = new RoleMgr(roles);
}

/**
  @name AuthGW#createToken
  @desc Creates a new auth token with the given role & data
  @param { Object } data written to the token, must have keys required by schema
  @param { String } role must be one of the roles given to this authgw instance
  @param { Number } expiry time of the token in minutes
  @returns { Promise<String> } token containing the raw encrypted token
  @throws InvalidRoleError if the given role is not listed
**/
AuthGw.prototype.createToken = function createToken(data, role, expiry) {
  return Promise.attempt(() => {
    if (!_.contains(this.roles, role))
      throw new AuthGWError.InvalidRoleError(role);
  })
  .then(() => JWT.signAsync(
    { data, role },
    this.opts.tokenSecret,
    {
      algorithm: this.opts.tokenAlgo,
      expiresIn: expiry * 60,
      issuer: this.authorityName
    }
  ));
};

/**
  @name AuthGW#verifyAuthToken
  @desc Verifies that a given token is valid, unexpired, and trusted
  @param { String } token is the encrypted auth token
{'fraction_non_alphanumeric': 0.10191543187567763, 'fraction_numerical': 0.006143838091796169, 'mean_word_length': 3.7756686798964623, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4162373', 'n_tokens_mistral': 1641, 'n_tokens_neox': 1582, 'n_words': 586}