# web-auth/cross-origin-authentication.js

    var urljoin = require('url-join');

    var windowHelper = require('../helper/window');
    var objectHelper = require('../helper/object');
    var RequestBuilder = require('../helper/request-builder');
    var WebMessageHandler = require('./web-message-handler');
    var responseHandler = require('../helper/response-handler');
    var storage = require('../helper/storage');
    var times = require('../helper/times');

    function CrossOriginAuthentication(webAuth, options) {
      this.webAuth = webAuth;
      this.baseOptions = options;
      this.request = new RequestBuilder(options);
      this.webMessageHandler = new WebMessageHandler(webAuth);
    }

    function getFragment(name) {
      var theWindow = windowHelper.getWindow();
      var value = '&' + theWindow.location.hash.substring(1);
      var parts = value.split('&' + name + '=');
      if (parts.length === 2) {
        return parts.pop().split('&').shift();
      }
    }

    function createKey(origin, coId) {
      return ['co/verifier', encodeURIComponent(origin), encodeURIComponent(coId)].join('/');
    }

    /**
     * Logs in the user with username and password using the cross origin authentication (/co/authenticate) flow. You can use either `username` or `email` to identify the user, but `username` will take precedence over `email`.
     * Some browsers might not be able to successfully authenticate if 3rd party cookies are disabled in your browser. [See here for more information.]{@link https://auth0.com/docs/cross-origin-authentication}.
     * After the /co/authenticate call, you'll have to use the {@link parseHash} function at the `redirectUri` specified in the constructor.
     *
     * @method login
     * @param {Object} options options used in the {@link authorize} call after the login_ticket is acquired
     * @param {String} [options.username] Username (mutually exclusive with email)
     * @param {String} [options.email] Email  (mutually exclusive with username)
     * @param {String} options.password Password
     * @param {String} [options.realm] Realm used to authenticate the user, it can be a realm name or a database connection name
     * @param {crossOriginLoginCallback} cb Callback function called only when an authentication error, like invalid username or password, occurs. For other types of errors, there will be a redirect to the `redirectUri`.
     */
    CrossOriginAuthentication.prototype.login = function(options, cb) {
      var _this = this;
      var url = urljoin(this.baseOptions.rootUrl, '/co/authenticate');
      options.username = options.username || options.email;
      delete options.email;

      var authenticateBody = {
        client_id: options.clientID || this.baseOptions.clientID,
        username: options.username
      };
      if (options.password) {
        authenticateBody.password = options.password;
      }
      if (options.otp) {
        authenticateBody.otp = options.otp;
      }
      var realm = options.realm || this.baseOptions.realm;

      if (realm) {
        var credentialType =
          options.credentialType ||
          this.baseOptions.credentialType ||
          'http://auth0.com/oauth/grant-type/password-realm';
        authenticateBody.realm = realm;
        authenticateBody.credential_type = credentialType;
      } else {
        authenticateBody.credential_type = 'password';
      }
      this.request.post(url).withCredentials().send(authenticateBody).end(function(err, data) {
        if (err) {
          var errorObject = (err.response && err.response.body) || {
            error: 'request_error',
            error_description: JSON.stringify(err)
          };
          return responseHandler(cb, { forceLegacyError: true })(errorObject);
        }
        var popupMode = options.popup === true;
        options = objectHelper.blacklist(options, ['password', 'credentialType', 'otp', 'popup']);
        var authorizeOptions = objectHelper
          .merge(options)
          .with({ loginTicket: data.body.login_ticket });
        var key = createKey(_this.baseOptions.rootUrl, data.body.co_id);
        storage.setItem(key, data.body.co_verifier, { expires: times.MINUTES_15 });
        if (popupMode) {
          _this.webMessageHandler.run(
            authorizeOptions,
            responseHandler(cb, { forceLegacyError: true })
          );
        } else {
          _this.webAuth.authorize(authorizeOptions);
        }
      });
    };

    function tryGetVerifier(theWindow, key) {
      try {
        var verifier = storage.getItem(key);
        storage.removeItem(key);
        return verifier || '';
      } catch (e) {
        return '';
      }
    }

    /**
     * Runs the callback code for the cross origin authentication call. This method is meant to be called by the cross origin authentication callback url.
     *
     * @method callback
     */
    CrossOriginAuthentication.prototype.callback = function() {
      var targetOrigin = decodeURIComponent(getFragment('origin'));
      var theWindow = windowHelper.getWindow();

      theWindow.addEventListener('message', function(evt) {
        if (evt.data.type !== 'co_verifier_request') {
          return;
        }
        var key = createKey(evt.origin, evt.data.request.id);
        var verifier = tryGetVerifier(theWindow, key);

        evt.source.postMessage(
          {
            type: 'co_verifier_response',
            response: {
              verifier: verifier
            }
          },
          evt.origin
        );
      });

      theWindow.parent.postMessage({ type: 'ready' }, targetOrigin);
    };

    module.exports = CrossOriginAuthentication;