recaptcha 5.5.0 → 5.14.0

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: ba681f9321310ca16bb41dc622bf7051c30df10ec000582f9dd8d29310e21136
4
- data.tar.gz: 8d9a3a4a7adb2ea830f17ace469e6b475d4d5c633c6988cd04dd56a7fd70e414
3
+ metadata.gz: a3e8441f1c05e97da5513f6b1b42ab0ee020d50b2d06196e4f83100251157c7b
4
+ data.tar.gz: c911c40cfe25f413bf9d7ae6a10cce7170856fc45975b455872397c8827db3f6
5
5
  SHA512:
6
- metadata.gz: efba5551493debe23617c41db429c224f51a4d0253274cf6fe28c5e384d78f4bd374ba3ad3132c2009cb2df9813266ac3a90eabd9a019aa7d48ee3999f2451e3
7
- data.tar.gz: 1dfd4f2281c8f6902b1c70ca0d272d0d2f671d9062669f147cfb38416ee771e7348ff131c8fd957c1e8674b7d83ad3f3c79f6c306a7779638b4481000534f695
6
+ metadata.gz: bbf6a050959ee4b95693bc336a4ce6f1bad590aefd62ec6f2a3e70f65affc22a44b04902cee5e695a7ea4922b0558e1a63c36b6a430f16094cba416f1d1a7e9f
7
+ data.tar.gz: 928673a8221ffe3bcce8d577dce30a405f3b1bdad4cdfc80c15f64044390149134ce4b8d48e8ce18cc28b42c4945dba2641b0982fef009d22e64e79fb117cd66
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  ## Next
2
2
 
3
+ ## 5.14.0
4
+ * drop json dependency
5
+
6
+ ## 5.13.1
7
+ * Permit actions as symbol
8
+
9
+ ## 5.13.0
10
+ * Added option to ignore_no_element.
11
+
12
+ ## 5.12.3
13
+ * Remove score fallback for enterprise
14
+ * Update enterprise tests to v1 assessment schema
15
+
16
+ ## 5.12.2
17
+ * Fix minimum score for enterprise
18
+
19
+ ## 5.12.1
20
+ * Fix Japanese locale
21
+
22
+ ## 5.12.0
23
+ * Added Japanese locale
24
+
25
+ ## 5.11.0
26
+ * Added Dutch locale
27
+
28
+ ## 5.10.1
29
+ * Fix enterprise_verify_url #415
30
+
31
+ ## 5.10.0
32
+ * Drop ruby 2.4 2.5 2.6
33
+ * Add maxiumm score support for hcaptcha
34
+
35
+ ## 5.9.0
36
+ * Gracefully handle invalid params
37
+
38
+ ## 5.8.1
39
+ * Allow configuring response limit
40
+
41
+ ## 5.8.0
42
+ * Add support for the enterprise API
43
+
44
+ ## 5.7.0
45
+ * french locale
46
+ * drop ruby 2.3
47
+
48
+ ## 5.6.0
49
+ * Allow multiple invisible recaptchas on a single page by setting custom selector
50
+
51
+ ## 5.5.0
52
+ * add `recaptcha_reply` controller method for better debugging/inspection
53
+
54
+ ## 5.4.1
55
+ * fix v2 vs 'data' postfix
56
+
57
+ ## 5.4.0
58
+ * added 'data' postfix to g-recaptcha-response attribute name to avoid collisions
59
+
3
60
  ## 5.3.0
4
61
  * turbolinks support
5
62
 
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  # reCAPTCHA
2
3
  [![Gem Version](https://badge.fury.io/rb/recaptcha.svg)](https://badge.fury.io/rb/recaptcha)
3
4
 
@@ -12,6 +13,23 @@ views you can use the `recaptcha_tags` method to embed the needed javascript, an
12
13
  in your controllers with `verify_recaptcha` or `verify_recaptcha!`, which raises an error on
13
14
  failure.
14
15
 
16
+
17
+ # Table of Contents
18
+ 1. [Obtaining a key](#obtaining-a-key)
19
+ 2. [Rails Installation](#rails-installation)
20
+ 3. [Sinatra / Rack / Ruby Installation](#sinatra--rack--ruby-installation)
21
+ 4. [reCAPTCHA V2 API & Usage](#recaptcha-v2-api-and-usage)
22
+ - [`recaptcha_tags`](#recaptcha_tags)
23
+ - [`verify_recaptcha`](#verify_recaptcha)
24
+ - [`invisible_recaptcha_tags`](#invisible_recaptcha_tags)
25
+ 5. [reCAPTCHA V3 API & Usage](#recaptcha-v3-api-and-usage)
26
+ - [`recaptcha_v3`](#recaptcha_v3)
27
+ - [`verify_recaptcha` (use with v3)](#verify_recaptcha-use-with-v3)
28
+ - [`recaptcha_reply`](#recaptcha_reply)
29
+ 6. [I18n Support](#i18n-support)
30
+ 7. [Testing](#testing)
31
+ 8. [Alternative API Key Setup](#alternative-api-key-setup)
32
+
15
33
  ## Obtaining a key
16
34
 
17
35
  Go to the [reCAPTCHA admin console](https://www.google.com/recaptcha/admin) to obtain a reCAPTCHA API key.
@@ -32,6 +50,8 @@ Note: Enter `localhost` or `127.0.0.1` as the domain if using in development wit
32
50
 
33
51
  ## Rails Installation
34
52
 
53
+ **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)!**
54
+
35
55
  ```ruby
36
56
  gem "recaptcha"
37
57
  ```
@@ -50,6 +70,14 @@ export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
50
70
  export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
51
71
  ```
52
72
 
73
+ If you have an Enterprise API key:
74
+
75
+ ```shell
76
+ export RECAPTCHA_ENTERPRISE = 'true'
77
+ export RECAPTCHA_ENTERPRISE_API_KEY = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
78
+ export RECAPTCHA_ENTERPRISE_PROJECT_ID = 'my-project'
79
+ ```
80
+
53
81
  Add `recaptcha_tags` to the forms you want to protect:
54
82
 
55
83
  ```erb
@@ -71,6 +99,7 @@ else
71
99
  render 'new'
72
100
  end
73
101
  ```
102
+ 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).
74
103
 
75
104
  ## Sinatra / Rack / Ruby installation
76
105
 
@@ -119,7 +148,7 @@ The following options are available:
119
148
  Any unrecognized options will be added as attributes on the generated tag.
120
149
 
121
150
  You can also override the html attributes for the sizes of the generated `textarea` and `iframe`
122
- elements, if CSS isn't your thing. Inspect the [source of `recaptcha_tags`](https://github.com/ambethia/recaptcha/blob/master/lib/recaptcha/client_helper.rb)
151
+ elements, if CSS isn't your thing. Inspect the [source of `recaptcha_tags`](https://github.com/ambethia/recaptcha/blob/master/lib/recaptcha/helpers.rb)
123
152
  to see these options.
124
153
 
125
154
  Note that you cannot submit/verify the same response token more than once or you will get a
@@ -140,16 +169,18 @@ you like.
140
169
 
141
170
  Some of the options available:
142
171
 
143
- | Option | Description |
144
- |----------------|-------------|
145
- | `:model` | Model to set errors.
146
- | `:attribute` | Model attribute to receive errors. (default: `:base`)
147
- | `:message` | Custom error message.
148
- | `:secret_key` | Override the secret API key from the configuration.
149
- | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
150
- | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
151
- | `: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`)
152
- | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
172
+ | Option | Description |
173
+ |---------------------------|-------------|
174
+ | `:model` | Model to set errors.
175
+ | `:attribute` | Model attribute to receive errors. (default: `:base`)
176
+ | `:message` | Custom error message.
177
+ | `:secret_key` | Override the secret API key from the configuration.
178
+ | `:enterprise_api_key` | Override the Enterprise API key from the configuration.
179
+ | `:enterprise_project_id ` | Override the Enterprise project ID from the configuration.
180
+ | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
181
+ | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
182
+ | `: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
+ | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
153
184
 
154
185
 
155
186
  ### `invisible_recaptcha_tags`
@@ -278,7 +309,7 @@ threshold:
278
309
  <% if @show_checkbox_recaptcha %>
279
310
  <%= recaptcha_tags %>
280
311
  <% else %>
281
- <%= recaptcha_v3(action: 'login') %>
312
+ <%= recaptcha_v3(action: 'login', site_key: ENV['RECAPTCHA_SITE_KEY_V3']) %>
282
313
  <% end %>
283
314
 
284
315
  ```
@@ -286,7 +317,7 @@ threshold:
286
317
  ```ruby
287
318
  # app/controllers/sessions_controller.rb
288
319
  def create
289
- success = verify_recaptcha(action: 'login', minimum_score: 0.5)
320
+ success = verify_recaptcha(action: 'login', minimum_score: 0.5, secret_key: ENV['RECAPTCHA_SECRET_KEY_V3'])
290
321
  checkbox_success = verify_recaptcha unless success
291
322
  if success || checkbox_success
292
323
  # Perform action
@@ -357,17 +388,19 @@ then you can either:
357
388
  2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
358
389
  don't have a use for the hidden input element.
359
390
 
360
- Note that you cannot submit/verify the same response token more than once or you will get a
361
- `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
362
- then you need to call `grecaptcha.execute(…)` again. This helper provides a JavaScript method (for
363
- each action) named `executeRecaptchaFor{action}` to make this easier. That is the same method that
364
- is invoked immediately. It simply calls `grecaptcha.execute` again and then calls the `callback`
365
- function with the response token.
391
+ Note that you cannot submit/verify the same response token more than once or you
392
+ will get a `timeout-or-duplicate` error code. If you need reset the captcha and
393
+ generate a new response token, then you need to call `grecaptcha.execute(…)` or
394
+ `grecaptcha.enterprise.execute(…)` again. This helper provides a JavaScript
395
+ method (for each action) named `executeRecaptchaFor{action}` to make this
396
+ easier. That is the same method that is invoked immediately. It simply calls
397
+ `grecaptcha.execute` or `grecaptcha.enterprise.execute` again and then calls the
398
+ `callback` function with the response token.
366
399
 
367
400
  You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
368
401
  response token and verifying it. This can easily happen with large forms that take the user a couple
369
402
  minutes to complete. Unlike v2, where you can use the `expired-callback` to be notified when the
370
- response expries, v3 appears to provide no such callback. See also
403
+ response expires, v3 appears to provide no such callback. See also
371
404
  [1](https://github.com/google/recaptcha/issues/281) and
372
405
  [2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
373
406
 
@@ -394,6 +427,7 @@ but only accepts the following options:
394
427
  | `: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`) |
395
428
  | `: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. |
396
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. |
430
+ | `: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) |
397
431
 
398
432
  [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
399
433
 
@@ -427,7 +461,7 @@ According to https://developers.google.com/recaptcha/docs/v3#placement,
427
461
 
428
462
  > Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
429
463
 
430
- You will need to verify each action individually with separate call to `verify_recaptcha`.
464
+ You will need to verify each action individually with a separate call to `verify_recaptcha`.
431
465
 
432
466
  ```ruby
433
467
  result_a = verify_recaptcha(action: 'a')
@@ -487,14 +521,20 @@ Recaptcha.configuration.skip_verify_env.delete("test")
487
521
  Recaptcha.configure do |config|
488
522
  config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
489
523
  config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
524
+
490
525
  # Uncomment the following line if you are using a proxy server:
491
526
  # config.proxy = 'http://myproxy.com.au:8080'
527
+
528
+ # Uncomment the following lines if you are using the Enterprise API:
529
+ # config.enterprise = true
530
+ # config.enterprise_api_key = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
531
+ # config.enterprise_project_id = 'my-project'
492
532
  end
493
533
  ```
494
534
 
495
535
  ### Recaptcha.with_configuration
496
536
 
497
- For temporary overwrites (not thread safe).
537
+ For temporary overwrites (not thread-safe).
498
538
 
499
539
  ```ruby
500
540
  Recaptcha.with_configuration(site_key: '12345') do
@@ -514,6 +554,38 @@ recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
514
554
  verify_recaptcha secret_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
515
555
  ```
516
556
 
557
+
558
+ ## hCaptcha support
559
+
560
+ [hCaptcha](https://hcaptcha.com) is an alternative service providing reCAPTCHA API.
561
+
562
+ To use hCaptcha:
563
+ 1. Set a site and a secret key as usual
564
+ 2. Set two options in `verify_url` and `api_service_url` pointing to hCaptcha API endpoints.
565
+ 3. Disable a response limit check by setting a `response_limit` to the large enough value (reCAPTCHA is limited by 4000 characters).
566
+ 4. It is not required to change a parameter name as [official docs suggest](https://docs.hcaptcha.com/switch) because API handles standard `g-recaptcha` for compatibility.
567
+
568
+ ```ruby
569
+ # config/initializers/recaptcha.rb
570
+ Recaptcha.configure do |config|
571
+ config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
572
+ config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
573
+ config.verify_url = 'https://hcaptcha.com/siteverify'
574
+ config.api_server_url = 'https://hcaptcha.com/1/api.js'
575
+ config.response_limit = 100000
576
+ end
577
+ ```
578
+
579
+ hCaptcha uses a scoring system (higher number more likely to be a bot) which is inverse of the reCaptcha scoring system (lower number more likely to be a bot). As such, a `maximum_score` attribute is provided for use with hCaptcha.
580
+
581
+ ```ruby
582
+ result = verify_recaptcha(maximum_score: 0.7)
583
+ ```
584
+
585
+ | Option | Description |
586
+ |------------------|-------------|
587
+ | `:maximum_score` | Provide a threshold to meet or fall below. Threshold should be a float between 0 and 1 which will be tested as `score <= maximum_score`. (Default: `nil`) |
588
+
517
589
  ## Misc
518
590
  - Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
519
591
  - [Add multiple widgets to the same page](https://github.com/ambethia/recaptcha/wiki/Add-multiple-widgets-to-the-same-page)
@@ -83,10 +83,12 @@ module Recaptcha
83
83
  # @return [String] A response token if one was passed in the params; otherwise, `''`
84
84
  def recaptcha_response_token(action = nil)
85
85
  response_param = params['g-recaptcha-response-data'] || params['g-recaptcha-response']
86
- if response_param&.respond_to?(:to_h) # Includes ActionController::Parameters
87
- response_param[action].to_s
86
+ response_param = response_param[action] if action && response_param.respond_to?(:key?)
87
+
88
+ if response_param.is_a?(String)
89
+ response_param
88
90
  else
89
- response_param.to_s
91
+ ''
90
92
  end
91
93
  end
92
94
  end
@@ -31,22 +31,32 @@ module Recaptcha
31
31
  #
32
32
  class Configuration
33
33
  DEFAULTS = {
34
- 'server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
- 'verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify'
34
+ 'free_server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
+ 'enterprise_server_url' => 'https://www.recaptcha.net/recaptcha/enterprise.js',
36
+ 'free_verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify',
37
+ 'enterprise_verify_url' => 'https://recaptchaenterprise.googleapis.com/v1/projects'
36
38
  }.freeze
37
39
 
38
- attr_accessor :default_env, :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
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
39
42
  attr_writer :api_server_url, :verify_url
40
43
 
41
- def initialize #:nodoc:
44
+ def initialize # :nodoc:
42
45
  @default_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
43
46
  @skip_verify_env = %w[test cucumber]
44
47
  @handle_timeouts_gracefully = true
45
48
 
46
49
  @secret_key = ENV['RECAPTCHA_SECRET_KEY']
47
50
  @site_key = ENV['RECAPTCHA_SITE_KEY']
51
+
52
+ @enterprise = ENV['RECAPTCHA_ENTERPRISE'] == 'true'
53
+ @enterprise_api_key = ENV['RECAPTCHA_ENTERPRISE_API_KEY']
54
+ @enterprise_project_id = ENV['RECAPTCHA_ENTERPRISE_PROJECT_ID']
55
+
48
56
  @verify_url = nil
49
57
  @api_server_url = nil
58
+
59
+ @response_limit = 4000
50
60
  end
51
61
 
52
62
  def secret_key!
@@ -57,12 +67,20 @@ module Recaptcha
57
67
  site_key || raise(RecaptchaError, "No site key specified.")
58
68
  end
59
69
 
70
+ def enterprise_api_key!
71
+ enterprise_api_key || raise(RecaptchaError, "No Enterprise API key specified.")
72
+ end
73
+
74
+ def enterprise_project_id!
75
+ enterprise_project_id || raise(RecaptchaError, "No Enterprise project ID specified.")
76
+ end
77
+
60
78
  def api_server_url
61
- @api_server_url || DEFAULTS.fetch('server_url')
79
+ @api_server_url || (enterprise ? DEFAULTS.fetch('enterprise_server_url') : DEFAULTS.fetch('free_server_url'))
62
80
  end
63
81
 
64
82
  def verify_url
65
- @verify_url || DEFAULTS.fetch('verify_url')
83
+ @verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url'))
66
84
  end
67
85
  end
68
86
  end
@@ -10,12 +10,13 @@ module Recaptcha
10
10
  def self.recaptcha_v3(options = {})
11
11
  site_key = options[:site_key] ||= Recaptcha.configuration.site_key!
12
12
  action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required')
13
- id = options.delete(:id) || "g-recaptcha-response-data-" + dasherize_action(action)
13
+ id = options.delete(:id) || "g-recaptcha-response-data-#{dasherize_action(action)}"
14
14
  name = options.delete(:name) || "g-recaptcha-response-data[#{action}]"
15
15
  turbolinks = 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
@@ -138,6 +139,7 @@ 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)
141
143
 
142
144
  data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
143
145
  data_attribute_keys << :tabindex unless ui == :button
@@ -174,7 +176,8 @@ module Recaptcha
174
176
 
175
177
  # v3
176
178
 
177
- # Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and
179
+ # Renders a script that calls `grecaptcha.execute` or
180
+ # `grecaptcha.enterprise.execute` for the given `site_key` and `action` and
178
181
  # calls the `callback` with the resulting response token.
179
182
  private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
180
183
  nonce = options[:nonce]
@@ -185,8 +188,8 @@ module Recaptcha
185
188
  // Define function so that we can call it again later if we need to reset it
186
189
  // This executes reCAPTCHA and then calls our callback.
187
190
  function #{recaptcha_v3_execute_function_name(action)}() {
188
- grecaptcha.ready(function() {
189
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
191
+ #{recaptcha_ready_method_name}(function() {
192
+ #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
190
193
  #{callback}('#{id}', token)
191
194
  });
192
195
  });
@@ -199,13 +202,13 @@ module Recaptcha
199
202
  // Returns a Promise that resolves with the response token.
200
203
  async function #{recaptcha_v3_async_execute_function_name(action)}() {
201
204
  return new Promise((resolve, reject) => {
202
- grecaptcha.ready(async function() {
203
- resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'}))
205
+ #{recaptcha_ready_method_name}(async function() {
206
+ resolve(await #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}))
204
207
  });
205
208
  })
206
209
  };
207
210
 
208
- #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
211
+ #{recaptcha_v3_define_default_callback(callback, options) if recaptcha_v3_define_default_callback?(callback, action, options)}
209
212
  </script>
210
213
  HTML
211
214
  end
@@ -217,13 +220,13 @@ module Recaptcha
217
220
  <<-HTML
218
221
  <script#{nonce_attr}>
219
222
  function #{recaptcha_v3_execute_function_name(action)}() {
220
- grecaptcha.ready(function() {
221
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
223
+ #{recaptcha_ready_method_name}(function() {
224
+ #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
222
225
  #{callback}('#{id}', token)
223
226
  });
224
227
  });
225
228
  };
226
- #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
229
+ #{recaptcha_v3_define_default_callback(callback, options) if recaptcha_v3_define_default_callback?(callback, action, options)}
227
230
  </script>
228
231
  HTML
229
232
  end
@@ -234,12 +237,12 @@ module Recaptcha
234
237
  options[:inline_script] != false
235
238
  end
236
239
 
237
- private_class_method def self.recaptcha_v3_define_default_callback(callback)
240
+ private_class_method def self.recaptcha_v3_define_default_callback(callback, options)
238
241
  <<-HTML
239
- var #{callback} = function(id, token) {
240
- var element = document.getElementById(id);
241
- element.value = token;
242
- }
242
+ var #{callback} = function(id, token) {
243
+ var element = document.getElementById(id);
244
+ #{element_check_condition(options)} element.value = token;
245
+ }
243
246
  HTML
244
247
  end
245
248
 
@@ -251,8 +254,9 @@ module Recaptcha
251
254
  recaptcha_v3_inline_script?(options)
252
255
  end
253
256
 
254
- # Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls
255
- # grecaptcha.execute). You can call it again later to reset it.
257
+ # Returns the name of the JavaScript function that actually executes the
258
+ # reCAPTCHA code (calls `grecaptcha.execute` or
259
+ # `grecaptcha.enterprise.execute`). You can call it again later to reset it.
256
260
  def self.recaptcha_v3_execute_function_name(action)
257
261
  "executeRecaptchaFor#{sanitize_action_for_js(action)}"
258
262
  end
@@ -271,6 +275,7 @@ module Recaptcha
271
275
  private_class_method def self.default_callback(options = {})
272
276
  nonce = options[:nonce]
273
277
  nonce_attr = " nonce='#{nonce}'" if nonce
278
+ selector_attr = options[:id] ? "##{options[:id]}" : ".g-recaptcha"
274
279
 
275
280
  <<-HTML
276
281
  <script#{nonce_attr}>
@@ -283,9 +288,9 @@ module Recaptcha
283
288
  return curEle.nodeName === 'FORM' ? curEle : null
284
289
  };
285
290
 
286
- var eles = document.getElementsByClassName('g-recaptcha');
287
- if (eles.length > 0) {
288
- var form = closestForm(eles[0]);
291
+ var el = document.querySelector("#{selector_attr}")
292
+ if (!!el) {
293
+ var form = closestForm(el);
289
294
  if (form) {
290
295
  form.submit();
291
296
  }
@@ -295,6 +300,14 @@ module Recaptcha
295
300
  HTML
296
301
  end
297
302
 
303
+ def self.recaptcha_execute_method_name
304
+ Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.execute" : "grecaptcha.execute"
305
+ end
306
+
307
+ def self.recaptcha_ready_method_name
308
+ Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.ready" : "grecaptcha.ready"
309
+ end
310
+
298
311
  private_class_method def self.default_callback_required?(options)
299
312
  options[:callback] == 'invisibleRecaptchaSubmit' &&
300
313
  !Recaptcha.skip_env?(options[:env]) &&
@@ -317,5 +330,9 @@ module Recaptcha
317
330
  private_class_method def self.hash_to_query(hash)
318
331
  hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
319
332
  end
333
+
334
+ private_class_method def self.element_check_condition(options)
335
+ options[:ignore_no_element] ? "if (element !== null)" : ""
336
+ end
320
337
  end
321
338
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Recaptcha
4
- VERSION = '5.5.0'
4
+ VERSION = '5.14.0'
5
5
  end
data/lib/recaptcha.rb CHANGED
@@ -14,7 +14,6 @@ end
14
14
 
15
15
  module Recaptcha
16
16
  DEFAULT_TIMEOUT = 3
17
- RESPONSE_LIMIT = 4000
18
17
 
19
18
  class RecaptchaError < StandardError
20
19
  end
@@ -56,24 +55,60 @@ module Recaptcha
56
55
  end
57
56
 
58
57
  def self.invalid_response?(resp)
59
- resp.empty? || resp.length > RESPONSE_LIMIT
58
+ resp.empty? || resp.length > configuration.response_limit
60
59
  end
61
60
 
62
61
  def self.verify_via_api_call(response, options)
62
+ if Recaptcha.configuration.enterprise
63
+ verify_via_api_call_enterprise(response, options)
64
+ else
65
+ verify_via_api_call_free(response, options)
66
+ end
67
+ end
68
+
69
+ def self.verify_via_api_call_enterprise(response, options)
70
+ site_key = options.fetch(:site_key) { configuration.site_key! }
71
+ api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! }
72
+ project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! }
73
+
74
+ query_params = { 'key' => api_key }
75
+ body = { 'event' => { 'token' => response, 'siteKey' => site_key } }
76
+ body['event']['expectedAction'] = options[:action] if options.key?(:action)
77
+ body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
78
+
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
94
+ end
95
+
96
+ def self.verify_via_api_call_free(response, options)
63
97
  secret_key = options.fetch(:secret_key) { configuration.secret_key! }
64
98
  verify_hash = { 'secret' => secret_key, 'response' => response }
65
99
  verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
66
100
 
67
- reply = api_verification(verify_hash, timeout: options[:timeout])
101
+ reply = api_verification_free(verify_hash, timeout: options[:timeout])
68
102
  success = reply['success'].to_s == 'true' &&
69
103
  hostname_valid?(reply['hostname'], options[:hostname]) &&
70
104
  action_valid?(reply['action'], options[:action]) &&
71
- score_above_threshold?(reply['score'], options[:minimum_score])
105
+ score_above_threshold?(reply['score'], options[:minimum_score]) &&
106
+ score_below_threshold?(reply['score'], options[:maximum_score])
72
107
 
73
108
  if options[:with_reply] == true
74
- return success, reply
109
+ [success, reply]
75
110
  else
76
- return success
111
+ success
77
112
  end
78
113
  end
79
114
 
@@ -90,22 +125,19 @@ module Recaptcha
90
125
  def self.action_valid?(action, expected_action)
91
126
  case expected_action
92
127
  when nil, FalseClass then true
93
- else action == expected_action
128
+ else action == expected_action.to_s
94
129
  end
95
130
  end
96
131
 
97
- # Returns true iff score is greater or equal to (>=) minimum_score, or if no minimum_score was specified
98
132
  def self.score_above_threshold?(score, minimum_score)
99
- return true if minimum_score.nil?
100
- return false if score.nil?
133
+ !minimum_score || (score && score >= minimum_score)
134
+ end
101
135
 
102
- case minimum_score
103
- when nil, FalseClass then true
104
- else score >= minimum_score
105
- end
136
+ def self.score_below_threshold?(score, maximum_score)
137
+ !maximum_score || (score && score <= maximum_score)
106
138
  end
107
139
 
108
- def self.api_verification(verify_hash, timeout: nil)
140
+ def self.http_client_for(uri:, timeout: nil)
109
141
  timeout ||= DEFAULT_TIMEOUT
110
142
  http = if configuration.proxy
111
143
  proxy_server = URI.parse(configuration.proxy)
@@ -113,12 +145,28 @@ module Recaptcha
113
145
  else
114
146
  Net::HTTP
115
147
  end
148
+ instance = http.new(uri.host, uri.port)
149
+ instance.read_timeout = instance.open_timeout = timeout
150
+ instance.use_ssl = true if uri.port == 443
151
+
152
+ instance
153
+ end
154
+
155
+ def self.api_verification_free(verify_hash, timeout: nil)
116
156
  query = URI.encode_www_form(verify_hash)
117
- uri = URI.parse(configuration.verify_url + '?' + query)
118
- http_instance = http.new(uri.host, uri.port)
119
- http_instance.read_timeout = http_instance.open_timeout = timeout
120
- http_instance.use_ssl = true if uri.port == 443
157
+ uri = URI.parse("#{configuration.verify_url}?#{query}")
158
+ http_instance = http_client_for(uri: uri, timeout: timeout)
121
159
  request = Net::HTTP::Get.new(uri.request_uri)
122
160
  JSON.parse(http_instance.request(request).body)
123
161
  end
162
+
163
+ def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
164
+ query = URI.encode_www_form(query_params)
165
+ uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
166
+ http_instance = http_client_for(uri: uri, timeout: timeout)
167
+ request = Net::HTTP::Post.new(uri.request_uri)
168
+ request['Content-Type'] = 'application/json; charset=utf-8'
169
+ request.body = JSON.generate(body)
170
+ JSON.parse(http_instance.request(request).body)
171
+ end
124
172
  end
@@ -0,0 +1,5 @@
1
+ fr:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: La vérification reCAPTCHA a échoué, veuillez essayer à nouveau.
5
+ recaptcha_unreachable: Oops, nous n'avons pas pu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
@@ -0,0 +1,5 @@
1
+ ja:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: reCAPTCHA認証に失敗しました。もう一度お試しください。
5
+ recaptcha_unreachable: reCAPTCHAのレスポンスを検証できませんでした。もう一度お試しください。
@@ -0,0 +1,5 @@
1
+ nl:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: reCAPTCHA-verificatie mislukt, probeer het opnieuw.
5
+ recaptcha_unreachable: Oeps, we hebben uw reCAPTCHA-antwoord niet kunnen valideren. Probeer het opnieuw.
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.5.0
4
+ version: 5.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason L Perry
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-31 00:00:00.000000000 Z
11
+ date: 2023-04-19 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
@@ -155,12 +141,15 @@ files:
155
141
  - lib/recaptcha/railtie.rb
156
142
  - lib/recaptcha/version.rb
157
143
  - rails/locales/en.yml
144
+ - rails/locales/fr.yml
145
+ - rails/locales/ja.yml
146
+ - rails/locales/nl.yml
158
147
  homepage: http://github.com/ambethia/recaptcha
159
148
  licenses:
160
149
  - MIT
161
150
  metadata:
162
151
  source_code_uri: https://github.com/ambethia/recaptcha
163
- post_install_message:
152
+ post_install_message:
164
153
  rdoc_options: []
165
154
  require_paths:
166
155
  - lib
@@ -168,15 +157,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
157
  requirements:
169
158
  - - ">="
170
159
  - !ruby/object:Gem::Version
171
- version: 2.3.0
160
+ version: 2.7.0
172
161
  required_rubygems_version: !ruby/object:Gem::Requirement
173
162
  requirements:
174
163
  - - ">="
175
164
  - !ruby/object:Gem::Version
176
165
  version: '0'
177
166
  requirements: []
178
- rubygems_version: 3.0.3
179
- signing_key:
167
+ rubygems_version: 3.3.3
168
+ signing_key:
180
169
  specification_version: 4
181
170
  summary: Helpers for the reCAPTCHA API
182
171
  test_files: []