recaptcha 5.12.3 → 5.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4052ca42cf536d84329b553a058de58f2c3579e0ac2ad1e08ba42fed8ce974b4
4
- data.tar.gz: 90c873c15d0772690ca3da6cf2588669a05c159e4fbaa1d6bdf5d809ad05dfd0
3
+ metadata.gz: f5915ff80a85bae6b80042f1d8a1d3e622470c74d8a037ebf0564260431d2030
4
+ data.tar.gz: c877f9e53cd0d5ba551b0bd2d5cb6e381c6baa5d7e82f8b10d9b11c0d518cd0c
5
5
  SHA512:
6
- metadata.gz: 403d9de96d890bb3f75cfb83c5907f01944d8e88233248ec199fe014e04f7f1386c13cd867c9020a232cd5501faada986f6d3413d3a2369e5d38d9c56a0704b9
7
- data.tar.gz: ce0d965054455acff094e60ccad808fbf836992666f9bd1233684b172e766440ba922a0f8d030803575b3a538945e3273d0ae237ce2527f9e0983b7b76671328
6
+ metadata.gz: 1bac93fe24a496e6d75f6515437ab3ca9c1589a1d6fbc61453833d9747910145138d00a4a8c42f648a3a46449a0dca2629333fa038689ad0e1f44a446bfd84f9
7
+ data.tar.gz: 95f37a636f308264a9e51ca2ca6854facd7ee0d692186272090ed60b02deccb70b00d2d9f3024dedaf040585664d688f163fa0e0ed756b36710856f7093c4d40
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  ## Next
2
2
 
3
+ ## 5.21.2
4
+ * make env fall back to Rails.env if it is unset
5
+
6
+ ## 5.21.1
7
+ * Fix flash not being updated when responding to :turbo_stream requests
8
+
9
+ ## 5.21.0
10
+ * add referer header to #api_verification_entreprise
11
+
12
+ ## 5.20.0
13
+ * turn recpatch reply into a object with logic
14
+
15
+ ## 5.19.0
16
+ * require a minimum lenght of 100 for responses, configured via response_minimum
17
+
18
+ ## 5.18.0
19
+ * Add key setup to v3 example in README
20
+ * Remove unnecessary id from textarea - This was unused and may cause accessability concerns if there is more than one recaptcha on the page due to multiple elements with the same id
21
+ * Update to latest version of rubocop
22
+ * Drop support for Ruby 2.7; add Ruby 3.3
23
+ * Add i18n: de, es, it, pt, pt-BR
24
+ * Added recaptcha_failure_reason
25
+
26
+ ## 5.16.0
27
+ * Allow usage of `options[:turbo]` as well as `options[:turbolinks]` for `recaptcha_v3`
28
+
29
+ ## 5.15.0
30
+ * Add 3.2 to the list of Ruby CI versions
31
+ * Add ability to submit verify_recaptcha via POST with JSON Body with `options[:json] = true`
32
+
33
+ ## 5.14.0
34
+ * drop json dependency
35
+
36
+ ## 5.13.1
37
+ * Permit actions as symbol
38
+
39
+ ## 5.13.0
40
+ * Added option to ignore_no_element.
41
+
3
42
  ## 5.12.3
4
43
  * Remove score fallback for enterprise
5
44
  * Update enterprise tests to v1 assessment schema
data/README.md CHANGED
@@ -78,6 +78,10 @@ export RECAPTCHA_ENTERPRISE_API_KEY = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyup
78
78
  export RECAPTCHA_ENTERPRISE_PROJECT_ID = 'my-project'
79
79
  ```
80
80
 
81
+ _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.
82
+
83
+ `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.
84
+
81
85
  Add `recaptcha_tags` to the forms you want to protect:
82
86
 
83
87
  ```erb
@@ -126,7 +130,7 @@ The following options are available:
126
130
  | `:site_key` | Override site API key from configuration |
127
131
  | `:error` | Override the error code returned from the reCAPTCHA API (default: `nil`) |
128
132
  | `:size` | Specify a size (default: `nil`) |
129
- | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
133
+ | `: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`) |
130
134
  | `:id` | Specify an html id attribute (default: `nil`) |
131
135
  | `:callback` | Optional. Name of success callback function, executed when the user submits a successful response |
132
136
  | `:expired_callback` | Optional. Name of expiration callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. |
@@ -181,6 +185,7 @@ Some of the options available:
181
185
  | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
182
186
  | `:hostname` | Expected hostname or a callable that validates the hostname, see [domain validation](https://developers.google.com/recaptcha/docs/domain_validation) and [hostname](https://developers.google.com/recaptcha/docs/verify#api-response) docs. (default: `nil`, but can be changed by setting `config.hostname`)
183
187
  | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
188
+ | `:json` | Boolean; defaults to false; if true, will submit the verification request by POST with the request data in JSON
184
189
 
185
190
 
186
191
  ### `invisible_recaptcha_tags`
@@ -203,7 +208,7 @@ It also accepts most of the options that `recaptcha_tags` accepts, including the
203
208
  | Option | Description |
204
209
  |---------------------|-------------|
205
210
  | `:site_key` | Override site API key from configuration |
206
- | `:nonce` | Optional. Sets nonce attribute for script tag. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
211
+ | `:nonce` | Optional. Sets nonce attribute for script tag. 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`) |
207
212
  | `:id` | Specify an html id attribute (default: `nil`) |
208
213
  | `:script` | Same as setting both `:inline_script` and `:external_script`. If you only need one or the other, use `:inline_script` and `:external_script` instead. |
209
214
  | `:callback` | Optional. Name of success callback function, executed when the user submits a successful response |
@@ -304,6 +309,14 @@ With v3, you can let all users log in without any intervention at all if their s
304
309
  threshold, and only show a v2 checkbox recaptcha challenge (fall back to v2) if it is below the
305
310
  threshold:
306
311
 
312
+ This example sets v2 keys through environment variables. For more information on how to set up keys, please refer to the [documentation here](#alternative-api-key-setup).
313
+
314
+ ```bash
315
+ # .env
316
+ RECAPTCHA_SITE_KEY=6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy
317
+ RECAPTCHA_SECRET_KEY=6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx
318
+ ```
319
+
307
320
  ```erb
308
321
  …
309
322
  <% if @show_checkbox_recaptcha %>
@@ -418,15 +431,17 @@ but only accepts the following options:
418
431
  | Option | Description |
419
432
  |---------------------|-------------|
420
433
  | `:site_key` | Override site API key |
421
- | `:action` | The name of the [reCAPTCHA action](https://developers.google.com/recaptcha/docs/v3#actions). Actions may only contain alphanumeric characters and slashes, and must not be user-specific. |
422
- | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
434
+ | `:action` | The name of the [reCAPTCHA action](https://developers.google.com/recaptcha/docs/v3#actions). Actions are not case-sensitive and may only contain alphanumeric characters, slashes, and underscores, and must not be user-specific. |
435
+ | `: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`) |
423
436
  | `:callback` | Name of callback function to call with the token. When `element` is `:input`, this defaults to a function named `setInputWithRecaptchaResponseTokenFor#{sanitize_action(action)}` that sets the value of the hidden input to the token. |
424
437
  | `:id` | Specify a unique `id` attribute for the `<input>` element if using `element: :input`. (default: `"g-recaptcha-response-data-"` + `action`) |
425
438
  | `:name` | Specify a unique `name` attribute for the `<input>` element if using `element: :input`. (default: `g-recaptcha-response-data[action]`) |
426
439
  | `:script` | Same as setting both `:inline_script` and `:external_script`. (default: `true`). |
427
440
  | `:inline_script` | If `true`, adds an inline script tag that calls `grecaptcha.execute` for the given `site_key` and `action` and calls the `callback` with the resulting response token. Pass `false` if you want to handle calling `grecaptcha.execute` yourself. (default: `true`) |
428
441
  | `:element` | The element to render, if any (default: `:input`)<br/>`:input`: Renders a hidden `<input type="hidden">` tag. The value of this will be set to the response token by the default `setInputWithRecaptchaResponseTokenFor{action}` callback.<br/>`false`: Doesn't render any tag. You'll have to add a custom callback that does something with the token. |
429
- | `:turbolinks` | If `true`, calls the js function which executes reCAPTCHA after all the dependencies have been loaded. This cannot be used with the js param `:onload`. This makes reCAPTCHAv3 usable with turbolinks. |
442
+ | `:turbo` | If `true`, calls the js function which executes reCAPTCHA after all the dependencies have been loaded. This cannot be used with the js param `:onload`. This makes reCAPTCHAv3 usable with turbo. |
443
+ | `:turbolinks` | Alias of `:turbo`. Will be deprecated soon. |
444
+ | `:ignore_no_element` | If `true`, adds null element checker for forms that can be removed from the page by javascript like modals with forms. (default: true) |
430
445
 
431
446
  [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
432
447
 
@@ -473,7 +488,7 @@ are passed as a hash under `params['g-recaptcha-response-data']` with the action
473
488
  It is recommended to pass `external_script: false` on all but one of the calls to
474
489
  `recaptcha` since you only need to include the script tag once for a given `site_key`.
475
490
 
476
- ## `recaptcha_reply`
491
+ ## `recaptcha_reply` and `recaptcha_failure_reason`
477
492
 
478
493
  After `verify_recaptcha` has been called, you can call `recaptcha_reply` to get the raw reply from recaptcha. This can allow you to get the exact score returned by recaptcha should you need it.
479
494
 
@@ -489,6 +504,8 @@ end
489
504
 
490
505
  `recaptcha_reply` will return `nil` if the the reply was not yet fetched.
491
506
 
507
+ `recaptcha_failure_reason` will return information if verification failed. E.g. if params was wrong or api resulted some error-codes.
508
+
492
509
  ## I18n support
493
510
 
494
511
  reCAPTCHA supports the I18n gem (it comes with English translations)
@@ -572,6 +589,7 @@ Recaptcha.configure do |config|
572
589
  config.verify_url = 'https://hcaptcha.com/siteverify'
573
590
  config.api_server_url = 'https://hcaptcha.com/1/api.js'
574
591
  config.response_limit = 100000
592
+ config.response_minimum = 100
575
593
  end
576
594
  ```
577
595
 
@@ -17,6 +17,11 @@ module Recaptcha
17
17
 
18
18
  begin
19
19
  verified = if Recaptcha.invalid_response?(recaptcha_response)
20
+ @_recaptcha_failure_reason = if recaptcha_response.nil?
21
+ "No recaptcha response/param(:action) found."
22
+ else
23
+ "Recaptcha response/param(:action) was invalid."
24
+ end
20
25
  false
21
26
  else
22
27
  unless options[:skip_remote_ip]
@@ -26,10 +31,23 @@ module Recaptcha
26
31
 
27
32
  success, @_recaptcha_reply =
28
33
  Recaptcha.verify_via_api_call(recaptcha_response, options.merge(with_reply: true))
34
+
35
+ unless success
36
+ @_recaptcha_failure_reason =
37
+ if @_recaptcha_reply.score && @_recaptcha_reply.score.to_f < options[:minimum_score].to_f
38
+ "Recaptcha score didn't exceed the minimum: #{@_recaptcha_reply.score} < #{options[:minimum_score]}."
39
+ elsif @_recaptcha_reply.error_codes.any?
40
+ "Recaptcha api call returned with error-codes: #{@_recaptcha_reply.error_codes}."
41
+ else
42
+ "Recaptcha failure after api call. Api reply: #{@_recaptcha_reply}."
43
+ end
44
+ end
45
+
29
46
  success
30
47
  end
31
48
 
32
49
  if verified
50
+ @_recaptcha_failure_reason = nil
33
51
  flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
34
52
  true
35
53
  else
@@ -41,6 +59,7 @@ module Recaptcha
41
59
  false
42
60
  end
43
61
  rescue Timeout::Error
62
+ @_recaptcha_failure_reason = "Recaptcha server unreachable."
44
63
  if Recaptcha.configuration.handle_timeouts_gracefully
45
64
  recaptcha_error(
46
65
  model,
@@ -57,13 +76,17 @@ module Recaptcha
57
76
  end
58
77
 
59
78
  def verify_recaptcha!(options = {})
60
- verify_recaptcha(options) || raise(VerifyError)
79
+ verify_recaptcha(options) || raise(VerifyError, @_recaptcha_failure_reason)
61
80
  end
62
81
 
63
82
  def recaptcha_reply
64
83
  @_recaptcha_reply if defined?(@_recaptcha_reply)
65
84
  end
66
85
 
86
+ def recaptcha_failure_reason
87
+ @_recaptcha_failure_reason
88
+ end
89
+
67
90
  def recaptcha_error(model, attribute, message)
68
91
  if model
69
92
  model.errors.add(attribute, message)
@@ -73,7 +96,9 @@ module Recaptcha
73
96
  end
74
97
 
75
98
  def recaptcha_flash_supported?
76
- request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
99
+ request.respond_to?(:format) && respond_to?(:flash) && (
100
+ request.format == :html || request.format == :turbo_stream
101
+ )
77
102
  end
78
103
 
79
104
  # Extracts response token from params. params['g-recaptcha-response-data'] for recaptcha_v3 or
@@ -37,8 +37,10 @@ module Recaptcha
37
37
  'enterprise_verify_url' => 'https://recaptchaenterprise.googleapis.com/v1/projects'
38
38
  }.freeze
39
39
 
40
- attr_accessor :default_env, :skip_verify_env, :proxy, :secret_key, :site_key, :handle_timeouts_gracefully,
41
- :hostname, :enterprise, :enterprise_api_key, :enterprise_project_id, :response_limit
40
+ attr_accessor(
41
+ :default_env, :skip_verify_env, :proxy, :secret_key, :site_key, :handle_timeouts_gracefully,
42
+ :hostname, :enterprise, :enterprise_api_key, :enterprise_project_id, :response_limit, :response_minimum
43
+ )
42
44
  attr_writer :api_server_url, :verify_url
43
45
 
44
46
  def initialize # :nodoc:
@@ -57,6 +59,7 @@ module Recaptcha
57
59
  @api_server_url = nil
58
60
 
59
61
  @response_limit = 4000
62
+ @response_minimum = 100
60
63
  end
61
64
 
62
65
  def secret_key!
@@ -12,10 +12,11 @@ module Recaptcha
12
12
  action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required')
13
13
  id = options.delete(:id) || "g-recaptcha-response-data-#{dasherize_action(action)}"
14
14
  name = options.delete(:name) || "g-recaptcha-response-data[#{action}]"
15
- turbolinks = options.delete(:turbolinks)
15
+ turbo = options.delete(:turbo) || options.delete(:turbolinks)
16
16
  options[:render] = site_key
17
17
  options[:script_async] ||= false
18
18
  options[:script_defer] ||= false
19
+ options[:ignore_no_element] = options.key?(:ignore_no_element) ? options[:ignore_no_element] : true
19
20
  element = options.delete(:element)
20
21
  element = element == false ? false : :input
21
22
  if element == :input
@@ -23,11 +24,11 @@ module Recaptcha
23
24
  end
24
25
  options[:class] = "g-recaptcha-response #{options[:class]}"
25
26
 
26
- if turbolinks
27
+ if turbo
27
28
  options[:onload] = recaptcha_v3_execute_function_name(action)
28
29
  end
29
30
  html, tag_attributes = components(options)
30
- if turbolinks
31
+ if turbo
31
32
  html << recaptcha_v3_onload_script(site_key, action, callback, id, options)
32
33
  elsif recaptcha_v3_inline_script?(options)
33
34
  html << recaptcha_v3_inline_script(site_key, action, callback, id, options)
@@ -73,7 +74,7 @@ module Recaptcha
73
74
  <div style="width: 300px; height: 60px; border-style: none;
74
75
  bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
75
76
  background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
76
- <textarea id="g-recaptcha-response" name="g-recaptcha-response"
77
+ <textarea name="g-recaptcha-response"
77
78
  class="g-recaptcha-response"
78
79
  style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
79
80
  margin: 10px 25px; padding: 0px; resize: none;">
@@ -138,6 +139,8 @@ module Recaptcha
138
139
  nonce = options.delete(:nonce)
139
140
  skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false)
140
141
  ui = options.delete(:ui)
142
+ options.delete(:ignore_no_element)
143
+ options.delete(:inline_script)
141
144
 
142
145
  data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
143
146
  data_attribute_keys << :tabindex unless ui == :button
@@ -206,7 +209,7 @@ module Recaptcha
206
209
  })
207
210
  };
208
211
 
209
- #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
212
+ #{recaptcha_v3_define_default_callback(callback, options) if recaptcha_v3_define_default_callback?(callback, action, options)}
210
213
  </script>
211
214
  HTML
212
215
  end
@@ -224,7 +227,7 @@ module Recaptcha
224
227
  });
225
228
  });
226
229
  };
227
- #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
230
+ #{recaptcha_v3_define_default_callback(callback, options) if recaptcha_v3_define_default_callback?(callback, action, options)}
228
231
  </script>
229
232
  HTML
230
233
  end
@@ -235,12 +238,12 @@ module Recaptcha
235
238
  options[:inline_script] != false
236
239
  end
237
240
 
238
- private_class_method def self.recaptcha_v3_define_default_callback(callback)
241
+ private_class_method def self.recaptcha_v3_define_default_callback(callback, options)
239
242
  <<-HTML
240
- var #{callback} = function(id, token) {
241
- var element = document.getElementById(id);
242
- element.value = token;
243
- }
243
+ var #{callback} = function(id, token) {
244
+ var element = document.getElementById(id);
245
+ #{element_check_condition(options)} element.value = token;
246
+ }
244
247
  HTML
245
248
  end
246
249
 
@@ -328,5 +331,9 @@ module Recaptcha
328
331
  private_class_method def self.hash_to_query(hash)
329
332
  hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
330
333
  end
334
+
335
+ private_class_method def self.element_check_condition(options)
336
+ options[:ignore_no_element] ? "if (element !== null)" : ""
337
+ end
331
338
  end
332
339
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recaptcha
4
+ class Reply
5
+ def initialize(raw_reply, enterprise:)
6
+ @raw_reply = raw_reply
7
+ @enterprise = enterprise
8
+ end
9
+
10
+ def [](key)
11
+ @raw_reply[key.to_s]
12
+ end
13
+
14
+ def success?(options = {})
15
+ success.to_s == 'true' &&
16
+ hostname_valid?(options[:hostname]) &&
17
+ action_valid?(options[:action]) &&
18
+ score_above_threshold?(options[:minimum_score]) &&
19
+ score_below_threshold?(options[:maximum_score])
20
+ end
21
+
22
+ def token_properties
23
+ @raw_reply['tokenProperties'] if enterprise?
24
+ end
25
+
26
+ def success
27
+ if enterprise?
28
+ token_properties&.dig('valid')
29
+ else
30
+ @raw_reply['success']
31
+ end
32
+ end
33
+
34
+ def hostname
35
+ if enterprise?
36
+ token_properties&.dig('hostname')
37
+ else
38
+ @raw_reply['hostname']
39
+ end
40
+ end
41
+
42
+ def action
43
+ if enterprise?
44
+ token_properties&.dig('action')
45
+ else
46
+ @raw_reply['action']
47
+ end
48
+ end
49
+
50
+ def score
51
+ if enterprise?
52
+ @raw_reply.dig('riskAnalysis', 'score')
53
+ else
54
+ @raw_reply['score'] unless enterprise?
55
+ end
56
+ end
57
+
58
+ def error_codes
59
+ if enterprise?
60
+ []
61
+ else
62
+ @raw_reply['error-codes'] || []
63
+ end
64
+ end
65
+
66
+ def challenge_ts
67
+ return @raw_reply['challenge_ts'] unless enterprise?
68
+
69
+ token_properties&.dig('createTime')
70
+ end
71
+
72
+ def hostname_valid?(validation)
73
+ validation ||= Recaptcha.configuration.hostname
74
+
75
+ case validation
76
+ when nil, FalseClass
77
+ true
78
+ when String
79
+ validation == hostname
80
+ else
81
+ validation.call(hostname)
82
+ end
83
+ end
84
+
85
+ def action_valid?(expected_action)
86
+ case expected_action
87
+ when nil, FalseClass
88
+ true
89
+ else
90
+ action == expected_action.to_s
91
+ end
92
+ end
93
+
94
+ def score_above_threshold?(minimum_score)
95
+ !minimum_score || (score && score >= minimum_score)
96
+ end
97
+
98
+ def score_below_threshold?(maximum_score)
99
+ !maximum_score || (score && score <= maximum_score)
100
+ end
101
+
102
+ def enterprise?
103
+ @enterprise
104
+ end
105
+
106
+ def to_h
107
+ @raw_reply
108
+ end
109
+
110
+ def to_s
111
+ @raw_reply.to_s
112
+ end
113
+
114
+ def to_json(*args)
115
+ @raw_reply.to_json(*args)
116
+ end
117
+ end
118
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Recaptcha
4
- VERSION = '5.12.3'
4
+ VERSION = '5.21.2'
5
5
  end
data/lib/recaptcha.rb CHANGED
@@ -6,6 +6,7 @@ require 'uri'
6
6
 
7
7
  require 'recaptcha/configuration'
8
8
  require 'recaptcha/helpers'
9
+ require 'recaptcha/reply'
9
10
  require 'recaptcha/adapters/controller_methods'
10
11
  require 'recaptcha/adapters/view_methods'
11
12
  if defined?(Rails)
@@ -51,11 +52,13 @@ module Recaptcha
51
52
  end
52
53
 
53
54
  def self.skip_env?(env)
54
- configuration.skip_verify_env.include?(env || configuration.default_env)
55
+ resolved = env || configuration.default_env ||
56
+ (defined?(Rails.env) && Rails.env.to_s) # default_env is sometimes not set during initialization
57
+ configuration.skip_verify_env.include?(resolved)
55
58
  end
56
59
 
57
60
  def self.invalid_response?(resp)
58
- resp.empty? || resp.length > configuration.response_limit
61
+ resp.empty? || resp.length > configuration.response_limit || resp.length < configuration.response_minimum
59
62
  end
60
63
 
61
64
  def self.verify_via_api_call(response, options)
@@ -76,21 +79,10 @@ module Recaptcha
76
79
  body['event']['expectedAction'] = options[:action] if options.key?(:action)
77
80
  body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
78
81
 
79
- reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
80
- score = reply.dig('riskAnalysis', 'score')
81
- token_properties = reply['tokenProperties']
82
- success = !token_properties.nil? &&
83
- token_properties['valid'].to_s == 'true' &&
84
- hostname_valid?(token_properties['hostname'], options[:hostname]) &&
85
- action_valid?(token_properties['action'], options[:action]) &&
86
- score_above_threshold?(score, options[:minimum_score]) &&
87
- score_below_threshold?(score, options[:maximum_score])
88
-
89
- if options[:with_reply] == true
90
- [success, reply]
91
- else
92
- success
93
- end
82
+ raw_reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
83
+ reply = Reply.new(raw_reply, enterprise: true)
84
+ result = reply.success?(options)
85
+ options[:with_reply] == true ? [result, reply] : result
94
86
  end
95
87
 
96
88
  def self.verify_via_api_call_free(response, options)
@@ -98,43 +90,10 @@ module Recaptcha
98
90
  verify_hash = { 'secret' => secret_key, 'response' => response }
99
91
  verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
100
92
 
101
- reply = api_verification_free(verify_hash, timeout: options[:timeout])
102
- success = reply['success'].to_s == 'true' &&
103
- hostname_valid?(reply['hostname'], options[:hostname]) &&
104
- action_valid?(reply['action'], options[:action]) &&
105
- score_above_threshold?(reply['score'], options[:minimum_score]) &&
106
- score_below_threshold?(reply['score'], options[:maximum_score])
107
-
108
- if options[:with_reply] == true
109
- [success, reply]
110
- else
111
- success
112
- end
113
- end
114
-
115
- def self.hostname_valid?(hostname, validation)
116
- validation ||= configuration.hostname
117
-
118
- case validation
119
- when nil, FalseClass then true
120
- when String then validation == hostname
121
- else validation.call(hostname)
122
- end
123
- end
124
-
125
- def self.action_valid?(action, expected_action)
126
- case expected_action
127
- when nil, FalseClass then true
128
- else action == expected_action
129
- end
130
- end
131
-
132
- def self.score_above_threshold?(score, minimum_score)
133
- !minimum_score || (score && score >= minimum_score)
134
- end
135
-
136
- def self.score_below_threshold?(score, maximum_score)
137
- !maximum_score || (score && score <= maximum_score)
93
+ raw_reply = api_verification_free(verify_hash, timeout: options[:timeout], json: options[:json])
94
+ reply = Reply.new(raw_reply, enterprise: false)
95
+ result = reply.success?(options)
96
+ options[:with_reply] == true ? [result, reply] : result
138
97
  end
139
98
 
140
99
  def self.http_client_for(uri:, timeout: nil)
@@ -152,11 +111,18 @@ module Recaptcha
152
111
  instance
153
112
  end
154
113
 
155
- def self.api_verification_free(verify_hash, timeout: nil)
156
- query = URI.encode_www_form(verify_hash)
157
- uri = URI.parse("#{configuration.verify_url}?#{query}")
114
+ def self.api_verification_free(verify_hash, timeout: nil, json: false)
115
+ if json
116
+ uri = URI.parse(configuration.verify_url)
117
+ request = Net::HTTP::Post.new(uri.request_uri)
118
+ request['Content-Type'] = 'application/json; charset=utf-8'
119
+ request.body = JSON.generate(verify_hash)
120
+ else
121
+ query = URI.encode_www_form(verify_hash)
122
+ uri = URI.parse("#{configuration.verify_url}?#{query}")
123
+ request = Net::HTTP::Get.new(uri.request_uri)
124
+ end
158
125
  http_instance = http_client_for(uri: uri, timeout: timeout)
159
- request = Net::HTTP::Get.new(uri.request_uri)
160
126
  JSON.parse(http_instance.request(request).body)
161
127
  end
162
128
 
@@ -165,6 +131,7 @@ module Recaptcha
165
131
  uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
166
132
  http_instance = http_client_for(uri: uri, timeout: timeout)
167
133
  request = Net::HTTP::Post.new(uri.request_uri)
134
+ request['Referer'] = Rails.application.default_url_options[:host] if defined? Rails.application
168
135
  request['Content-Type'] = 'application/json; charset=utf-8'
169
136
  request.body = JSON.generate(body)
170
137
  JSON.parse(http_instance.request(request).body)
@@ -0,0 +1,5 @@
1
+ de:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: Die reCAPTCHA-Überprüfung ist fehlgeschlagen, bitte versuchen Sie es erneut.
5
+ recaptcha_unreachable: Oops, wir konnten Ihre reCAPTCHA-Antwort nicht validieren. Bitte versuchen Sie es erneut.
@@ -0,0 +1,5 @@
1
+ es:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: La verificación de reCAPTCHA falló, por favor intente de nuevo.
5
+ recaptcha_unreachable: Ups, no pudimos validar su respuesta de reCAPTCHA. Por favor intente de nuevo.
@@ -0,0 +1,5 @@
1
+ it:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: La verifica reCAPTCHA non è riuscita, si prega di riprovare.
5
+ recaptcha_unreachable: Ops, non siamo riusciti a convalidare la tua risposta reCAPTCHA. Per favore riprova.
@@ -0,0 +1,5 @@
1
+ pt-BR:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: A verificação do reCAPTCHA falhou, por favor, tente novamente.
5
+ recaptcha_unreachable: Oops, não conseguimos validar sua resposta do reCAPTCHA. Por favor, tente novamente.
@@ -0,0 +1,5 @@
1
+ pt:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: A verificação do reCAPTCHA falhou, por favor, tente novamente.
5
+ recaptcha_unreachable: Oops, não conseguimos validar sua resposta do reCAPTCHA. Por favor, tente novamente.
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recaptcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.12.3
4
+ version: 5.21.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason L Perry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-05 00:00:00.000000000 Z
11
+ date: 2026-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: json
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: mocha
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -153,16 +139,23 @@ files:
153
139
  - lib/recaptcha/helpers.rb
154
140
  - lib/recaptcha/rails.rb
155
141
  - lib/recaptcha/railtie.rb
142
+ - lib/recaptcha/reply.rb
156
143
  - lib/recaptcha/version.rb
144
+ - rails/locales/de.yml
157
145
  - rails/locales/en.yml
146
+ - rails/locales/es.yml
158
147
  - rails/locales/fr.yml
148
+ - rails/locales/it.yml
159
149
  - rails/locales/ja.yml
160
150
  - rails/locales/nl.yml
151
+ - rails/locales/pt-BR.yml
152
+ - rails/locales/pt.yml
161
153
  homepage: http://github.com/ambethia/recaptcha
162
154
  licenses:
163
155
  - MIT
164
156
  metadata:
165
157
  source_code_uri: https://github.com/ambethia/recaptcha
158
+ rubygems_mfa_required: 'true'
166
159
  post_install_message:
167
160
  rdoc_options: []
168
161
  require_paths:
@@ -171,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
164
  requirements:
172
165
  - - ">="
173
166
  - !ruby/object:Gem::Version
174
- version: 2.7.0
167
+ version: 3.0.0
175
168
  required_rubygems_version: !ruby/object:Gem::Requirement
176
169
  requirements:
177
170
  - - ">="
178
171
  - !ruby/object:Gem::Version
179
172
  version: '0'
180
173
  requirements: []
181
- rubygems_version: 3.3.3
174
+ rubygems_version: 3.4.10
182
175
  signing_key:
183
176
  specification_version: 4
184
177
  summary: Helpers for the reCAPTCHA API