recaptcha-5.21.1/0000755000004100000410000000000015063225121013557 5ustar www-datawww-datarecaptcha-5.21.1/recaptcha.gemspec0000644000004100000410000000433415063225121017062 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: recaptcha 5.21.1 ruby lib Gem::Specification.new do |s| s.name = "recaptcha".freeze s.version = "5.21.1".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "source_code_uri" => "https://github.com/ambethia/recaptcha" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Jason L Perry".freeze] s.date = "2025-09-17" s.description = "Helpers for the reCAPTCHA API".freeze s.email = ["jasper@ambethia.com".freeze] s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "lib/recaptcha.rb".freeze, "lib/recaptcha/adapters/controller_methods.rb".freeze, "lib/recaptcha/adapters/view_methods.rb".freeze, "lib/recaptcha/configuration.rb".freeze, "lib/recaptcha/helpers.rb".freeze, "lib/recaptcha/rails.rb".freeze, "lib/recaptcha/railtie.rb".freeze, "lib/recaptcha/reply.rb".freeze, "lib/recaptcha/version.rb".freeze, "rails/locales/de.yml".freeze, "rails/locales/en.yml".freeze, "rails/locales/es.yml".freeze, "rails/locales/fr.yml".freeze, "rails/locales/it.yml".freeze, "rails/locales/ja.yml".freeze, "rails/locales/nl.yml".freeze, "rails/locales/pt-BR.yml".freeze, "rails/locales/pt.yml".freeze] s.homepage = "http://github.com/ambethia/recaptcha".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.0.0".freeze) s.rubygems_version = "3.4.10".freeze s.summary = "Helpers for the reCAPTCHA API".freeze s.specification_version = 4 s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) end recaptcha-5.21.1/lib/0000755000004100000410000000000015063225121014325 5ustar www-datawww-datarecaptcha-5.21.1/lib/recaptcha.rb0000644000004100000410000001114615063225121016607 0ustar www-datawww-data# frozen_string_literal: true require 'json' require 'net/http' require 'uri' require 'recaptcha/configuration' require 'recaptcha/helpers' require 'recaptcha/reply' require 'recaptcha/adapters/controller_methods' require 'recaptcha/adapters/view_methods' if defined?(Rails) require 'recaptcha/railtie' end module Recaptcha DEFAULT_TIMEOUT = 3 class RecaptchaError < StandardError end class VerifyError < RecaptchaError end # Gives access to the current Configuration. def self.configuration @configuration ||= Configuration.new end # Allows easy setting of multiple configuration options. See Configuration # for all available options. #-- # The temp assignment is only used to get a nicer rdoc. Feel free to remove # this hack. #++ def self.configure config = configuration yield(config) end def self.with_configuration(config) original_config = {} config.each do |key, value| original_config[key] = configuration.send(key) configuration.send("#{key}=", value) end yield if block_given? ensure original_config.each { |key, value| configuration.send("#{key}=", value) } end def self.skip_env?(env) configuration.skip_verify_env.include?(env || configuration.default_env) end def self.invalid_response?(resp) resp.empty? || resp.length > configuration.response_limit || resp.length < configuration.response_minimum end def self.verify_via_api_call(response, options) if Recaptcha.configuration.enterprise verify_via_api_call_enterprise(response, options) else verify_via_api_call_free(response, options) end end def self.verify_via_api_call_enterprise(response, options) site_key = options.fetch(:site_key) { configuration.site_key! } api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! } project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! } query_params = { 'key' => api_key } body = { 'event' => { 'token' => response, 'siteKey' => site_key } } body['event']['expectedAction'] = options[:action] if options.key?(:action) body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip) raw_reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout]) reply = Reply.new(raw_reply, enterprise: true) result = reply.success?(options) options[:with_reply] == true ? [result, reply] : result end def self.verify_via_api_call_free(response, options) secret_key = options.fetch(:secret_key) { configuration.secret_key! } verify_hash = { 'secret' => secret_key, 'response' => response } verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip) raw_reply = api_verification_free(verify_hash, timeout: options[:timeout], json: options[:json]) reply = Reply.new(raw_reply, enterprise: false) result = reply.success?(options) options[:with_reply] == true ? [result, reply] : result end def self.http_client_for(uri:, timeout: nil) timeout ||= DEFAULT_TIMEOUT http = if configuration.proxy proxy_server = URI.parse(configuration.proxy) Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password) else Net::HTTP end instance = http.new(uri.host, uri.port) instance.read_timeout = instance.open_timeout = timeout instance.use_ssl = true if uri.port == 443 instance end def self.api_verification_free(verify_hash, timeout: nil, json: false) if json uri = URI.parse(configuration.verify_url) request = Net::HTTP::Post.new(uri.request_uri) request['Content-Type'] = 'application/json; charset=utf-8' request.body = JSON.generate(verify_hash) else query = URI.encode_www_form(verify_hash) uri = URI.parse("#{configuration.verify_url}?#{query}") request = Net::HTTP::Get.new(uri.request_uri) end http_instance = http_client_for(uri: uri, timeout: timeout) JSON.parse(http_instance.request(request).body) end def self.api_verification_enterprise(query_params, body, project_id, timeout: nil) query = URI.encode_www_form(query_params) uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}") http_instance = http_client_for(uri: uri, timeout: timeout) request = Net::HTTP::Post.new(uri.request_uri) request['Referer'] = Rails.application.default_url_options[:host] if defined? Rails.application request['Content-Type'] = 'application/json; charset=utf-8' request.body = JSON.generate(body) JSON.parse(http_instance.request(request).body) end end recaptcha-5.21.1/lib/recaptcha/0000755000004100000410000000000015063225121016257 5ustar www-datawww-datarecaptcha-5.21.1/lib/recaptcha/configuration.rb0000644000004100000410000000575715063225121021471 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha # This class enables detailed configuration of the recaptcha services. # # By calling # # Recaptcha.configuration # => instance of Recaptcha::Configuration # # or # Recaptcha.configure do |config| # config # => instance of Recaptcha::Configuration # end # # you are able to perform configuration updates. # # Your are able to customize all attributes listed below. All values have # sensitive default and will very likely not need to be changed. # # Please note that the site and secret key for the reCAPTCHA API Access # have no useful default value. The keys may be set via the Shell enviroment # or using this configuration. Settings within this configuration always take # precedence. # # Setting the keys with this Configuration # # Recaptcha.configure do |config| # config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy' # config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx' # end # class Configuration DEFAULTS = { 'free_server_url' => 'https://www.recaptcha.net/recaptcha/api.js', 'enterprise_server_url' => 'https://www.recaptcha.net/recaptcha/enterprise.js', 'free_verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify', 'enterprise_verify_url' => 'https://recaptchaenterprise.googleapis.com/v1/projects' }.freeze attr_accessor( :default_env, :skip_verify_env, :proxy, :secret_key, :site_key, :handle_timeouts_gracefully, :hostname, :enterprise, :enterprise_api_key, :enterprise_project_id, :response_limit, :response_minimum ) attr_writer :api_server_url, :verify_url def initialize # :nodoc: @default_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env) @skip_verify_env = %w[test cucumber] @handle_timeouts_gracefully = true @secret_key = ENV['RECAPTCHA_SECRET_KEY'] @site_key = ENV['RECAPTCHA_SITE_KEY'] @enterprise = ENV['RECAPTCHA_ENTERPRISE'] == 'true' @enterprise_api_key = ENV['RECAPTCHA_ENTERPRISE_API_KEY'] @enterprise_project_id = ENV['RECAPTCHA_ENTERPRISE_PROJECT_ID'] @verify_url = nil @api_server_url = nil @response_limit = 4000 @response_minimum = 100 end def secret_key! secret_key || raise(RecaptchaError, "No secret key specified.") end def site_key! site_key || raise(RecaptchaError, "No site key specified.") end def enterprise_api_key! enterprise_api_key || raise(RecaptchaError, "No Enterprise API key specified.") end def enterprise_project_id! enterprise_project_id || raise(RecaptchaError, "No Enterprise project ID specified.") end def api_server_url @api_server_url || (enterprise ? DEFAULTS.fetch('enterprise_server_url') : DEFAULTS.fetch('free_server_url')) end def verify_url @verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url')) end end end recaptcha-5.21.1/lib/recaptcha/helpers.rb0000644000004100000410000003221015063225121020244 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha module Helpers DEFAULT_ERRORS = { recaptcha_unreachable: 'Oops, we failed to validate your reCAPTCHA response. Please try again.', verification_failed: 'reCAPTCHA verification failed, please try again.' }.freeze 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}]" turbo = options.delete(:turbo) || options.delete(:turbolinks) options[:render] = site_key options[:script_async] ||= false options[:script_defer] ||= false options[:ignore_no_element] = options.key?(:ignore_no_element) ? options[:ignore_no_element] : true 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 turbo options[:onload] = recaptcha_v3_execute_function_name(action) end html, tag_attributes = components(options) if turbo 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 << %(\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 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 << %(
\n) if noscript != false html << <<-HTML HTML end html.respond_to?(:html_safe) ? html.html_safe : html end 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 << %(\n) when :invisible html << %(
\n) when :input html << %(\n) else raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.") end html.respond_to?(:html_safe) ? html.html_safe : html end def self.to_error_message(key) default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown reCAPTCHA error - #{key}" } to_message("recaptcha.errors.#{key}", default) end if defined?(I18n) def self.to_message(key, default) I18n.translate(key, default: default) end else def self.to_message(_key, default) default end end private_class_method 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) options.delete(:ignore_no_element) options.delete(:inline_script) 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 << %(\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 # v3 # 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. private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce <<-HTML // 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, options) if recaptcha_v3_define_default_callback?(callback, action, options)} HTML end private_class_method def self.recaptcha_v3_onload_script(site_key, action, callback, id, options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce <<-HTML 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, options) if recaptcha_v3_define_default_callback?(callback, action, options)} HTML end private_class_method def self.recaptcha_v3_inline_script?(options) !Recaptcha.skip_env?(options[:env]) && options[:script] != false && options[:inline_script] != false end private_class_method def self.recaptcha_v3_define_default_callback(callback, options) <<-HTML var #{callback} = function(id, token) { var element = document.getElementById(id); #{element_check_condition(options)} element.value = token; } HTML end # 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. private_class_method def self.recaptcha_v3_define_default_callback?(callback, action, options) callback == recaptcha_v3_default_callback_name(action) && recaptcha_v3_inline_script?(options) end # 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. def self.recaptcha_v3_execute_function_name(action) "executeRecaptchaFor#{sanitize_action_for_js(action)}" end # Returns the name of an async JavaScript function that executes the reCAPTCHA code. def self.recaptcha_v3_async_execute_function_name(action) "#{recaptcha_v3_execute_function_name(action)}Async" end def self.recaptcha_v3_default_callback_name(action) "setInputWithRecaptchaResponseTokenFor#{sanitize_action_for_js(action)}" end # v2 private_class_method def self.default_callback(options = {}) nonce = options[:nonce] nonce_attr = " nonce='#{nonce}'" if nonce selector_attr = options[:id] ? "##{options[:id]}" : ".g-recaptcha" <<-HTML 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(); } } }; HTML end def self.recaptcha_execute_method_name Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.execute" : "grecaptcha.execute" end def self.recaptcha_ready_method_name Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.ready" : "grecaptcha.ready" end private_class_method def self.default_callback_required?(options) options[:callback] == 'invisibleRecaptchaSubmit' && !Recaptcha.skip_env?(options[:env]) && options[:script] != false && options[:inline_script] != false end # Returns a camelized string that is safe for use in a JavaScript variable/function name. # sanitize_action_for_js('my/action') => 'MyAction' private_class_method def self.sanitize_action_for_js(action) action.to_s.gsub(/\W/, '_').split(/\/|_/).map(&:capitalize).join end # Returns a dasherized string that is safe for use as an HTML ID # dasherize_action('my/action') => 'my-action' private_class_method def self.dasherize_action(action) action.to_s.gsub(/\W/, '-').tr('_', '-') end private_class_method def self.hash_to_query(hash) hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&') end private_class_method def self.element_check_condition(options) options[:ignore_no_element] ? "if (element !== null)" : "" end end end recaptcha-5.21.1/lib/recaptcha/reply.rb0000644000004100000410000000435415063225121017745 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha class Reply def initialize(raw_reply, enterprise:) @raw_reply = raw_reply @enterprise = enterprise end def [](key) @raw_reply[key.to_s] end def success?(options = {}) success.to_s == 'true' && hostname_valid?(options[:hostname]) && action_valid?(options[:action]) && score_above_threshold?(options[:minimum_score]) && score_below_threshold?(options[:maximum_score]) end def token_properties @raw_reply['tokenProperties'] if enterprise? end def success if enterprise? token_properties&.dig('valid') else @raw_reply['success'] end end def hostname if enterprise? token_properties&.dig('hostname') else @raw_reply['hostname'] end end def action if enterprise? token_properties&.dig('action') else @raw_reply['action'] end end def score if enterprise? @raw_reply.dig('riskAnalysis', 'score') else @raw_reply['score'] unless enterprise? end end def error_codes if enterprise? [] else @raw_reply['error-codes'] || [] end end def challenge_ts return @raw_reply['challenge_ts'] unless enterprise? token_properties&.dig('createTime') end def hostname_valid?(validation) validation ||= Recaptcha.configuration.hostname case validation when nil, FalseClass true when String validation == hostname else validation.call(hostname) end end def action_valid?(expected_action) case expected_action when nil, FalseClass true else action == expected_action.to_s end end def score_above_threshold?(minimum_score) !minimum_score || (score && score >= minimum_score) end def score_below_threshold?(maximum_score) !maximum_score || (score && score <= maximum_score) end def enterprise? @enterprise end def to_h @raw_reply end def to_s @raw_reply.to_s end def to_json(*args) @raw_reply.to_json(*args) end end end recaptcha-5.21.1/lib/recaptcha/adapters/0000755000004100000410000000000015063225121020062 5ustar www-datawww-datarecaptcha-5.21.1/lib/recaptcha/adapters/view_methods.rb0000644000004100000410000000174715063225121023115 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha module Adapters module ViewMethods # Renders a [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) script and (by # default) a hidden input to submit the response token. You can also call the functions # directly if you prefer. You can use # `Recaptcha::Helpers.recaptcha_v3_execute_function_name(action)` to get the name of the # function to call. def recaptcha_v3(options = {}) ::Recaptcha::Helpers.recaptcha_v3(options) end # Renders a reCAPTCHA [v2 Checkbox](https://developers.google.com/recaptcha/docs/display) widget def recaptcha_tags(options = {}) ::Recaptcha::Helpers.recaptcha_tags(options) end # Renders a reCAPTCHA v2 [Invisible reCAPTCHA](https://developers.google.com/recaptcha/docs/invisible) def invisible_recaptcha_tags(options = {}) ::Recaptcha::Helpers.invisible_recaptcha_tags(options) end end end end recaptcha-5.21.1/lib/recaptcha/adapters/controller_methods.rb0000644000004100000410000001050515063225121024316 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha module Adapters module ControllerMethods private # Your private API can be specified in the +options+ hash or preferably # using the Configuration. def verify_recaptcha(options = {}) options = {model: options} unless options.is_a? Hash return true if Recaptcha.skip_env?(options[:env]) model = options[:model] attribute = options.fetch(:attribute, :base) recaptcha_response = options[:response] || recaptcha_response_token(options[:action]) begin verified = if Recaptcha.invalid_response?(recaptcha_response) @_recaptcha_failure_reason = if recaptcha_response.nil? "No recaptcha response/param(:action) found." else "Recaptcha response/param(:action) was invalid." end false else unless options[:skip_remote_ip] remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR']) options = options.merge(remote_ip: remoteip.to_s) if remoteip end success, @_recaptcha_reply = Recaptcha.verify_via_api_call(recaptcha_response, options.merge(with_reply: true)) unless success @_recaptcha_failure_reason = if @_recaptcha_reply.score && @_recaptcha_reply.score.to_f < options[:minimum_score].to_f "Recaptcha score didn't exceed the minimum: #{@_recaptcha_reply.score} < #{options[:minimum_score]}." elsif @_recaptcha_reply.error_codes.any? "Recaptcha api call returned with error-codes: #{@_recaptcha_reply.error_codes}." else "Recaptcha failure after api call. Api reply: #{@_recaptcha_reply}." end end success end if verified @_recaptcha_failure_reason = nil flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model true else recaptcha_error( model, attribute, options.fetch(:message) { Recaptcha::Helpers.to_error_message(:verification_failed) } ) false end rescue Timeout::Error @_recaptcha_failure_reason = "Recaptcha server unreachable." if Recaptcha.configuration.handle_timeouts_gracefully recaptcha_error( model, attribute, options.fetch(:message) { Recaptcha::Helpers.to_error_message(:recaptcha_unreachable) } ) false else raise RecaptchaError, 'Recaptcha unreachable.' end rescue StandardError => e raise RecaptchaError, e.message, e.backtrace end end def verify_recaptcha!(options = {}) verify_recaptcha(options) || raise(VerifyError, @_recaptcha_failure_reason) end def recaptcha_reply @_recaptcha_reply if defined?(@_recaptcha_reply) end def recaptcha_failure_reason @_recaptcha_failure_reason end def recaptcha_error(model, attribute, message) if model model.errors.add(attribute, message) elsif recaptcha_flash_supported? flash[:recaptcha_error] = message end end def recaptcha_flash_supported? request.respond_to?(:format) && respond_to?(:flash) && ( request.format == :html || request.format == :turbo_stream ) end # Extracts response token from params. params['g-recaptcha-response-data'] for recaptcha_v3 or # params['g-recaptcha-response'] for recaptcha_tags and invisible_recaptcha_tags and should # either be a string or a hash with the action name(s) as keys. If it is a hash, then `action` # is used as the key. # @return [String] A response token if one was passed in the params; otherwise, `''` def recaptcha_response_token(action = nil) response_param = params['g-recaptcha-response-data'] || params['g-recaptcha-response'] response_param = response_param[action] if action && response_param.respond_to?(:key?) if response_param.is_a?(String) response_param else '' end end end end end recaptcha-5.21.1/lib/recaptcha/rails.rb0000644000004100000410000000014015063225121017711 0ustar www-datawww-data# frozen_string_literal: true # deprecated, but let's not blow everyone up require 'recaptcha' recaptcha-5.21.1/lib/recaptcha/version.rb0000644000004100000410000000011115063225121020262 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha VERSION = '5.21.1' end recaptcha-5.21.1/lib/recaptcha/railtie.rb0000644000004100000410000000146415063225121020242 0ustar www-datawww-data# frozen_string_literal: true module Recaptcha class Railtie < Rails::Railtie ActiveSupport.on_load(:action_view) do include Recaptcha::Adapters::ViewMethods end ActiveSupport.on_load(:action_controller) do include Recaptcha::Adapters::ControllerMethods end initializer 'recaptcha' do |app| Recaptcha::Railtie.instance_eval do pattern = pattern_from app.config.i18n.available_locales add("rails/locales/#{pattern}.yml") end end class << self protected def add(pattern) files = Dir[File.join(File.dirname(__FILE__), '../..', pattern)] I18n.load_path.concat(files) end def pattern_from(args) array = Array(args || []) array.blank? ? '*' : "{#{array.join ','}}" end end end end recaptcha-5.21.1/LICENSE0000644000004100000410000000204015063225121014560 0ustar www-datawww-dataCopyright (c) 2007 Jason L Perry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.recaptcha-5.21.1/README.md0000644000004100000410000007477715063225121015064 0ustar www-datawww-data # reCAPTCHA [![Gem Version](https://badge.fury.io/rb/recaptcha.svg)](https://badge.fury.io/rb/recaptcha) Author: Jason L Perry (http://ambethia.com)
Copyright: Copyright (c) 2007-2013 Jason L Perry
License: [MIT](http://creativecommons.org/licenses/MIT/)
Info: https://github.com/ambethia/recaptcha
Bugs: https://github.com/ambethia/recaptcha/issues
This gem provides helper methods for the [reCAPTCHA API](https://www.google.com/recaptcha). In your views you can use the `recaptcha_tags` method to embed the needed javascript, and you can validate in your controllers with `verify_recaptcha` or `verify_recaptcha!`, which raises an error on failure. # Table of Contents 1. [Obtaining a key](#obtaining-a-key) 2. [Rails Installation](#rails-installation) 3. [Sinatra / Rack / Ruby Installation](#sinatra--rack--ruby-installation) 4. [reCAPTCHA V2 API & Usage](#recaptcha-v2-api-and-usage) - [`recaptcha_tags`](#recaptcha_tags) - [`verify_recaptcha`](#verify_recaptcha) - [`invisible_recaptcha_tags`](#invisible_recaptcha_tags) 5. [reCAPTCHA V3 API & Usage](#recaptcha-v3-api-and-usage) - [`recaptcha_v3`](#recaptcha_v3) - [`verify_recaptcha` (use with v3)](#verify_recaptcha-use-with-v3) - [`recaptcha_reply`](#recaptcha_reply) 6. [I18n Support](#i18n-support) 7. [Testing](#testing) 8. [Alternative API Key Setup](#alternative-api-key-setup) ## Obtaining a key Go to the [reCAPTCHA admin console](https://www.google.com/recaptcha/admin) to obtain a reCAPTCHA API key. The reCAPTCHA type(s) that you choose for your key will determine which methods to use below. | reCAPTCHA type | Methods to use | Description | |----------------------------------------------|----------------|-------------| | v3 | [`recaptcha_v3`](#recaptcha_v3) | Verify requests with a [score](https://developers.google.com/recaptcha/docs/v3#score) | v2 Checkbox
("I'm not a robot" Checkbox) | [`recaptcha_tags`](#recaptcha_tags) | Validate requests with the "I'm not a robot" checkbox | | v2 Invisible
(Invisible reCAPTCHA badge) | [`invisible_recaptcha_tags`](#invisible_recaptcha_tags) | Validate requests in the background | Note: You can _only_ use methods that match your key's type. You cannot use v2 methods with a v3 key or use `recaptcha_tags` with a v2 Invisible key, for example. Otherwise you will get an error like "Invalid key type" or "This site key is not enabled for the invisible captcha." Note: Enter `localhost` or `127.0.0.1` as the domain if using in development with `localhost:3000`. ## Rails Installation **If you are having issues with Rails 7, Turbo, and Stimulus, make sure to check [this Wiki page](https://github.com/ambethia/recaptcha/wiki/Recaptcha-with-Turbo-and-Stimulus)!** ```ruby gem "recaptcha" ``` You can keep keys out of the code base with environment variables or with Rails [secrets](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-secrets).
In development, you can use the [dotenv](https://github.com/bkeepers/dotenv) gem. (Make sure to add it above `gem 'recaptcha'`.) See [Alternative API key setup](#alternative-api-key-setup) for more ways to configure or override keys. See also the [Configuration](https://www.rubydoc.info/github/ambethia/recaptcha/master/Recaptcha/Configuration) documentation. ```shell export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy' export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx' ``` If you have an Enterprise API key: ```shell export RECAPTCHA_ENTERPRISE = 'true' export RECAPTCHA_ENTERPRISE_API_KEY = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA' export RECAPTCHA_ENTERPRISE_PROJECT_ID = 'my-project' ``` _note:_ you'll still have to provide `RECAPTCHA_SITE_KEY`, which will hold the value of your enterprise recaptcha key id. You will not need to provide a `RECAPTCHA_SECRET_KEY`, however. `RECAPTCHA_ENTERPRISE_API_KEY` is the enterprise key of your Google Cloud Project, which you can generate here: https://console.cloud.google.com/apis/credentials. Add `recaptcha_tags` to the forms you want to protect: ```erb <%= form_for @foo do |f| %> # … <%= recaptcha_tags %> # … <% end %> ``` Then, add `verify_recaptcha` logic to each form action that you've protected: ```ruby # app/controllers/users_controller.rb @user = User.new(params[:user].permit(:name)) if verify_recaptcha(model: @user) && @user.save redirect_to @user else render 'new' end ``` Please note that this setup uses [`reCAPTCHA_v2`](#recaptcha-v2-api-and-usage). For a `recaptcha_v3` use, please refer to [`reCAPTCHA_v3 setup`](#examples). ## Sinatra / Rack / Ruby installation See [sinatra demo](/demo/sinatra) for details. - add `gem 'recaptcha'` to `Gemfile` - set env variables - `include Recaptcha::Adapters::ViewMethods` where you need `recaptcha_tags` - `include Recaptcha::Adapters::ControllerMethods` where you need `verify_recaptcha` ## reCAPTCHA v2 API and Usage ### `recaptcha_tags` Use this when your key's reCAPTCHA type is "v2 Checkbox". The following options are available: | Option | Description | |---------------------|-------------| | `:theme` | Specify the theme to be used per the API. Available options: `dark` and `light`. (default: `light`) | | `:ajax` | Render the dynamic AJAX captcha per the API. (default: `false`) | | `:site_key` | Override site API key from configuration | | `:error` | Override the error code returned from the reCAPTCHA API (default: `nil`) | | `:size` | Specify a size (default: `nil`) | | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. Use `content_security_policy_nonce` if you have `config.content_security_policy_nonce_generator` set in Rails. (default: `nil`) | | `:id` | Specify an html id attribute (default: `nil`) | | `:callback` | Optional. Name of success callback function, executed when the user submits a successful response | | `:expired_callback` | Optional. Name of expiration callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. | | `:error_callback` | Optional. Name of error callback function, executed when reCAPTCHA encounters an error (e.g. network connectivity) | | `:noscript` | Include `