Source: channel-auth.js

'use strict'

var inherits = require('inherits')
var request = require('request')
var BaseChannelAuth = require('./base-channel-auth')
var PermanentAuthenticationError = require('./permanent-authentication-error')
var TemporaryAuthenticationError = require('./temporary-authentication-error')
var util = require('./util')

var LOGIN_PATH_FRAGMENT = '/identity/v1/login'

/**
 * @classdesc Authentication class for use with channel requests.
 * @param {String} base - Base URL to forward authentication requests to.
 * @param {String} username - User name to supply for request authentication.
 * @param {String} password - Password to supply for request authentication.
 * @param {Object} [options] - Additional options to supply for request
 *   authentication.
 * @param {String} [options.key] - Optional client private keys in PEM format.
 *   See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options}.
 * @param {String} [options.cert] - Optional client cert chains in PEM format.
 *   See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options}.
 * @param {String} [options.ca] - Optionally override the trusted CA
 *   certificates used to validate the authentication server. Any string can
 *   contain multiple PEM CAs concatenated together.
 *   See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options}.
 * @param {String} [options.passphrase] - Optional shared passphrase used for a
 *   single private key. See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options}.
 * @param {Boolean} [options.rejectUnauthorized=true] - If not false, the server
 *   certificate is verified against the list of supplied CAs. See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_connect_options_callback}.
 * @param {Function} [options.checkServerIdentity] - A callback function to
 *   be used when checking the server's hostname against the certificate.
 *   See
 *   {@link https://nodejs.org/api/tls.html#tls_tls_connect_options_callback}.
 * @implements {BaseChannelAuth}
 * @constructor
 */
function ChannelAuth (base, username, password, options) {
  BaseChannelAuth.call(this)
  this._loginRequest = request.defaults(
    util.addTlsOptions({
      baseUrl: base,
      uri: LOGIN_PATH_FRAGMENT
    }, options)
  )

  this._username = username
  this._password = password
  this._token = null

  /**
   * Append the current token to the `bearer` property in the supplied
   * `requestOptions` object.
   * @param {Object} requestOptions - The request options.
   * @param {BaseChannelAuth~authCallback} callback - A callback to invoke
   *   with the modified `requestOptions`.
   * @private
   */
  this._addBearerAuthToken = function (requestOptions, callback) {
    requestOptions.auth = {bearer: this._token}
    callback(null, requestOptions)
  }
}

inherits(ChannelAuth, BaseChannelAuth)

/**
 * Authenticate the user for an HTTP channel request. The supplied callback
 * should be invoked with the results of the authentication attempt. See
 * {@link BaseChannelAuth~authCallback} for more information on the
 * content provided to the callback.
 * @param {Object} requestOptions - Options included in the HTTP channel
 *   request.
 * @param {BaseChannelAuth~authCallback} callback - Callback function
 *   invoked with the results of the authentication attempt.
 */
ChannelAuth.prototype.authenticate = function (requestOptions, callback) {
  var that = this
  if (this._token) {
    // Token was acquired previously, so use it for the request
    this._addBearerAuthToken(requestOptions, callback)
  } else {
    // Token was not acquired previously, so make a login request to get
    // a token.
    this._loginRequest.get(
      {
        auth: {
          user: this._username,
          password: this._password
        },
        json: true
      },
      function (error, response, body) {
        if (error) {
          callback(new TemporaryAuthenticationError(
            'Unexpected error: ' + error.message
          ))
        } else if (response.statusCode === 200) {
          if (body.AuthorizationToken) {
            // Token was acquired successfully, so set it into the options
            // for the original request and invoke the request callback to
            // continue.
            that._token = body.AuthorizationToken
            that._addBearerAuthToken(requestOptions, callback)
          } else {
            callback(new PermanentAuthenticationError(
              'Unable to locate AuthorizationToken in login response'
            ))
          }
        } else if ([401, 403].indexOf(response.statusCode) >= 0) {
          callback(new PermanentAuthenticationError(
            'Unauthorized ' + response.statusCode + ': ' + body
          ))
        } else {
          callback(new TemporaryAuthenticationError(
            'Unexpected status code ' + response.statusCode + ': ' +
            JSON.stringify(body)
          ))
        }
      }
    )
  }
}

/**
 * Purge any credentials cached from a previous authentication.
 */
ChannelAuth.prototype.reset = function () {
  this._token = null
}

module.exports = ChannelAuth