module Recaptcha::Helpers

Constants

DEFAULT_ERRORS

Public Class Methods

invisible_recaptcha_tags(custom) click to toggle source
# File lib/recaptcha/helpers.rb, line 90
def self.invisible_recaptcha_tags(custom)
  options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge(custom)
  text = options.delete(:text)
  html, tag_attributes = components(options.dup)
  html << default_callback(options) if default_callback_required?(options)

  case options[:ui]
  when :button
    html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
  when :invisible
    html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
  when :input
    html << %(<input type="submit" #{tag_attributes} value="#{text}"/>\n)
  else
    raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
  end
  html.respond_to?(:html_safe) ? html.html_safe : html
end
recaptcha_execute_method_name() click to toggle source
# File lib/recaptcha/helpers.rb, line 301
def self.recaptcha_execute_method_name
  Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.execute" : "grecaptcha.execute"
end
recaptcha_ready_method_name() click to toggle source
# File lib/recaptcha/helpers.rb, line 305
def self.recaptcha_ready_method_name
  Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.ready" : "grecaptcha.ready"
end
recaptcha_tags(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 47
    def self.recaptcha_tags(options)
      if options.key?(:stoken)
        raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
      end
      if options.key?(:ssl)
        raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
      end

      noscript = options.delete(:noscript)

      html, tag_attributes, fallback_uri = components(options.dup)
      html << %(<div #{tag_attributes}></div>\n)

      if noscript != false
        html << <<-HTML
          <noscript>
            <div>
              <div style="width: 302px; height: 422px; position: relative;">
                <div style="width: 302px; height: 422px; position: absolute;">
                  <iframe
                    src="#{fallback_uri}"
                    name="ReCAPTCHA"
                    style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
                  </iframe>
                </div>
              </div>
              <div style="width: 300px; height: 60px; border-style: none;
                bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
                background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
                <textarea id="g-recaptcha-response" name="g-recaptcha-response"
                  class="g-recaptcha-response"
                  style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
                  margin: 10px 25px; padding: 0px; resize: none;">
                </textarea>
              </div>
            </div>
          </noscript>
        HTML
      end

      html.respond_to?(:html_safe) ? html.html_safe : html
    end
recaptcha_v3(options = {}) click to toggle source
# File lib/recaptcha/helpers.rb, line 10
def self.recaptcha_v3(options = {})
  site_key = options[:site_key] ||= Recaptcha.configuration.site_key!
  action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required')
  id = options.delete(:id) || "g-recaptcha-response-data-" + dasherize_action(action)
  name = options.delete(:name) || "g-recaptcha-response-data[#{action}]"
  turbolinks = options.delete(:turbolinks)
  options[:render] = site_key
  options[:script_async] ||= false
  options[:script_defer] ||= false
  element = options.delete(:element)
  element = element == false ? false : :input
  if element == :input
    callback = options.delete(:callback) || recaptcha_v3_default_callback_name(action)
  end
  options[:class] = "g-recaptcha-response #{options[:class]}"

  if turbolinks
    options[:onload] = recaptcha_v3_execute_function_name(action)
  end
  html, tag_attributes = components(options)
  if turbolinks
    html << recaptcha_v3_onload_script(site_key, action, callback, id, options)
  elsif recaptcha_v3_inline_script?(options)
    html << recaptcha_v3_inline_script(site_key, action, callback, id, options)
  end
  case element
  when :input
    html << %(<input type="hidden" name="#{name}" id="#{id}" #{tag_attributes}/>\n)
  when false
    # No tag
    nil
  else
    raise(RecaptchaError, "ReCAPTCHA element `#{options[:element]}` is not valid.")
  end
  html.respond_to?(:html_safe) ? html.html_safe : html
end
recaptcha_v3_async_execute_function_name(action) click to toggle source

Returns the name of an async JavaScript function that executes the reCAPTCHA code.

# File lib/recaptcha/helpers.rb, line 263
def self.recaptcha_v3_async_execute_function_name(action)
  "#{recaptcha_v3_execute_function_name(action)}Async"
end
recaptcha_v3_default_callback_name(action) click to toggle source
# File lib/recaptcha/helpers.rb, line 267
def self.recaptcha_v3_default_callback_name(action)
  "setInputWithRecaptchaResponseTokenFor#{sanitize_action_for_js(action)}"
end
recaptcha_v3_execute_function_name(action) click to toggle source

Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls `grecaptcha.execute` or `grecaptcha.enterprise.execute`). You can call it again later to reset it.

# File lib/recaptcha/helpers.rb, line 258
def self.recaptcha_v3_execute_function_name(action)
  "executeRecaptchaFor#{sanitize_action_for_js(action)}"
end
to_error_message(key) click to toggle source
# File lib/recaptcha/helpers.rb, line 109
def self.to_error_message(key)
  default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown reCAPTCHA error - #{key}" }
  to_message("recaptcha.errors.#{key}", default)
end
to_message(key, default) click to toggle source
# File lib/recaptcha/helpers.rb, line 115
def self.to_message(key, default)
  I18n.translate(key, default: default)
end

Private Class Methods

components(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 124
                     def self.components(options)
  html = +''
  attributes = {}
  fallback_uri = +''

  options = options.dup
  env = options.delete(:env)
  class_attribute = options.delete(:class)
  site_key = options.delete(:site_key)
  hl = options.delete(:hl)
  onload = options.delete(:onload)
  render = options.delete(:render)
  script_async = options.delete(:script_async)
  script_defer = options.delete(:script_defer)
  nonce = options.delete(:nonce)
  skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false)
  ui = options.delete(:ui)

  data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
  data_attribute_keys << :tabindex unless ui == :button
  data_attributes = {}
  data_attribute_keys.each do |data_attribute|
    value = options.delete(data_attribute)
    data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
  end

  unless Recaptcha.skip_env?(env)
    site_key ||= Recaptcha.configuration.site_key!
    script_url = Recaptcha.configuration.api_server_url
    query_params = hash_to_query(
      hl: hl,
      onload: onload,
      render: render
    )
    script_url += "?#{query_params}" unless query_params.empty?
    async_attr = "async" if script_async != false
    defer_attr = "defer" if script_defer != false
    nonce_attr = " nonce='#{nonce}'" if nonce
    html << %(<script src="#{script_url}" #{async_attr} #{defer_attr} #{nonce_attr}></script>\n) unless skip_script
    fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
    attributes["data-sitekey"] = site_key
    attributes.merge! data_attributes
  end

  # The remaining options will be added as attributes on the tag.
  attributes["class"] = "g-recaptcha #{class_attribute}"
  tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")

  [html, tag_attributes, fallback_uri]
end
dasherize_action(action) click to toggle source

Returns a dasherized string that is safe for use as an HTML ID dasherize_action('my/action') => 'my-action'

# File lib/recaptcha/helpers.rb, line 324
                     def self.dasherize_action(action)
  action.to_s.gsub(/\W/, '-').tr('_', '-')
end
default_callback(options = {}) click to toggle source

v2

# File lib/recaptcha/helpers.rb, line 273
                         def self.default_callback(options = {})
      nonce = options[:nonce]
      nonce_attr = " nonce='#{nonce}'" if nonce
      selector_attr = options[:id] ? "##{options[:id]}" : ".g-recaptcha"

      <<-HTML
        <script#{nonce_attr}>
          var invisibleRecaptchaSubmit = function () {
            var closestForm = function (ele) {
              var curEle = ele.parentNode;
              while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
                curEle = curEle.parentNode;
              }
              return curEle.nodeName === 'FORM' ? curEle : null
            };

            var el = document.querySelector("#{selector_attr}")
            if (!!el) {
              var form = closestForm(el);
              if (form) {
                form.submit();
              }
            }
          };
        </script>
      HTML
    end
default_callback_required?(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 309
                     def self.default_callback_required?(options)
  options[:callback] == 'invisibleRecaptchaSubmit' &&
  !Recaptcha.skip_env?(options[:env]) &&
  options[:script] != false &&
  options[:inline_script] != false
end
hash_to_query(hash) click to toggle source
# File lib/recaptcha/helpers.rb, line 328
                     def self.hash_to_query(hash)
  hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
end
recaptcha_v3_define_default_callback(callback) click to toggle source
# File lib/recaptcha/helpers.rb, line 238
                         def self.recaptcha_v3_define_default_callback(callback)
      <<-HTML
          var #{callback} = function(id, token) {
            var element = document.getElementById(id);
            element.value = token;
          }
      HTML
    end
recaptcha_v3_define_default_callback?(callback, action, options) click to toggle source

Returns true if we should be adding the default callback. That is, if the given callback name is the default callback name (for the given action) and we are not skipping inline scripts for any reason.

# File lib/recaptcha/helpers.rb, line 250
                     def self.recaptcha_v3_define_default_callback?(callback, action, options)
  callback == recaptcha_v3_default_callback_name(action) &&
  recaptcha_v3_inline_script?(options)
end
recaptcha_v3_inline_script(site_key, action, callback, id, options = {}) click to toggle source

Renders a script that calls `grecaptcha.execute` or `grecaptcha.enterprise.execute` for the given `site_key` and `action` and calls the `callback` with the resulting response token.

# File lib/recaptcha/helpers.rb, line 180
                         def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
      nonce = options[:nonce]
      nonce_attr = " nonce='#{nonce}'" if nonce

      <<-HTML
        <script#{nonce_attr}>
          // Define function so that we can call it again later if we need to reset it
          // This executes reCAPTCHA and then calls our callback.
          function #{recaptcha_v3_execute_function_name(action)}() {
            #{recaptcha_ready_method_name}(function() {
              #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
                #{callback}('#{id}', token)
              });
            });
          };
          // Invoke immediately
          #{recaptcha_v3_execute_function_name(action)}()

          // Async variant so you can await this function from another async function (no need for
          // an explicit callback function then!)
          // Returns a Promise that resolves with the response token.
          async function #{recaptcha_v3_async_execute_function_name(action)}() {
            return new Promise((resolve, reject) => {
             #{recaptcha_ready_method_name}(async function() {
                resolve(await #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}))
              });
            })
          };

          #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
        </script>
      HTML
    end
recaptcha_v3_inline_script?(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 232
                     def self.recaptcha_v3_inline_script?(options)
  !Recaptcha.skip_env?(options[:env]) &&
  options[:script] != false &&
  options[:inline_script] != false
end
recaptcha_v3_onload_script(site_key, action, callback, id, options = {}) click to toggle source
# File lib/recaptcha/helpers.rb, line 214
                         def self.recaptcha_v3_onload_script(site_key, action, callback, id, options = {})
      nonce = options[:nonce]
      nonce_attr = " nonce='#{nonce}'" if nonce

      <<-HTML
        <script#{nonce_attr}>
          function #{recaptcha_v3_execute_function_name(action)}() {
            #{recaptcha_ready_method_name}(function() {
              #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
                #{callback}('#{id}', token)
              });
            });
          };
          #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
        </script>
      HTML
    end
sanitize_action_for_js(action) click to toggle source

Returns a camelized string that is safe for use in a JavaScript variable/function name. sanitize_action_for_js('my/action') => 'MyAction'

# File lib/recaptcha/helpers.rb, line 318
                     def self.sanitize_action_for_js(action)
  action.to_s.gsub(/\W/, '_').split(/\/|_/).map(&:capitalize).join
end