web-auth/captcha.js

// eslint-disable-next-line no-unused-vars
import Authentication from '../authentication';
import object from '../helper/object';

var noop = function () { };

var defaults = {
  lang: 'en',
  templates: {
    'auth0': function (challenge) {
      var message = challenge.type === 'code' ?
        'Enter the code shown above' :
        'Solve the formula shown above';
      return '<div class="captcha-challenge">\n' +
        '  <img src="' + challenge.image + '" />\n' +
        '  <button type="button" class="captcha-reload">↺</button>\n' +
        '</div>\n' +
        '<input type="text" name="captcha"\n' +
        '  class="form-control captcha-control"\n' +
        '  placeholder="' + message + '" />';
    }
    ,
    'recaptcha_v2': function () {
      return '<div class="recaptcha" ></div><input type="hidden" name="captcha" />';
    }
    ,
    'error': function () {
      return '<div class="error" style="color: red;">Error getting the bot detection challenge. Please contact the system administrator.</div>'
    }
  }
};

function handleAuth0Provider(element, options, challenge, load) {
  element.innerHTML = options.templates[challenge.provider](challenge);
  element.querySelector('.captcha-reload').addEventListener('click', function (e) {
    e.preventDefault();
    load();
  });
}

function injectRecaptchaScript(element, lang, callback) {
  var callbackName = 'recaptchaCallback_' + Math.floor(Math.random() * 1000001);
  window[callbackName] = function () {
    delete window[callbackName];
    callback();
  };
  var script = window.document.createElement('script');
  script.src = 'https://www.google.com/recaptcha/api.js?hl=' + lang + '&onload=' + callbackName;
  script.async = true;
  window.document.body.appendChild(script);
}

function handleRecaptchaProvider(element, options, challenge) {
  var widgetId = element.hasAttribute('data-wid') && element.getAttribute('data-wid');

  function setValue(value) {
    var input = element.querySelector('input[name="captcha"]');
    input.value = value || '';
  }

  if (widgetId) {
    setValue();
    window.grecaptcha.reset(widgetId);
    return;
  }

  element.innerHTML = options.templates[challenge.provider](challenge);

  var recaptchaDiv = element.querySelector('.recaptcha');

  injectRecaptchaScript(element, options.lang, function () {
    widgetId = window.grecaptcha.render(recaptchaDiv, {
      callback: setValue,
      'expired-callback': function () { setValue(); },
      'error-callback': function () { setValue(); },
      sitekey: challenge.siteKey
    });
    element.setAttribute('data-wid', widgetId)
  });
}


/**
 *
 * Renders the captcha challenge in the provided element.
 *
 * @param {Authentication} auth0Client The challenge response from the authentication server
 * @param {HTMLElement} element The element where the captcha needs to be rendered
 * @param {Object} options The configuration options for the captcha
 * @param {Object} [options.templates] An object containaing templates for each captcha provider
 * @param {Function} [options.templates.auth0] template function receiving the challenge and returning an string
 * @param {Function} [options.templates.recaptcha_v2] template function receiving the challenge and returning an string
 * @param {String} [options.lang=en] the ISO code of the language for recaptcha*
 * @param {Function} [callback] an optional callback function
 */
function render(auth0Client, element, options, callback) {
  options = object.merge(defaults).with(options || {});

  function load(done) {
    done = done || noop;
    auth0Client.getChallenge(function (err, challenge) {
      if (err) {
        element.innerHTML = options.templates.error(err);
        return done(err);
      }
      if (!challenge.required) {
        element.style.display = 'none';
        element.innerHTML = '';
        return;
      }
      element.style.display = '';
      if (challenge.provider === 'auth0') {
        handleAuth0Provider(element, options, challenge, load);
      } else if (challenge.provider === 'recaptcha_v2') {
        handleRecaptchaProvider(element, options, challenge);
      }
      done();
    });
  }

  function getValue() {
    var captchaInput = element.querySelector('input[name="captcha"]');
    if (!captchaInput) { return; }
    return captchaInput.value;
  }

  load(callback);

  return {
    reload: load,
    getValue: getValue
  };
}

export default { render: render };