recaptcha 5.5.0 → 5.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba681f9321310ca16bb41dc622bf7051c30df10ec000582f9dd8d29310e21136
4
- data.tar.gz: 8d9a3a4a7adb2ea830f17ace469e6b475d4d5c633c6988cd04dd56a7fd70e414
3
+ metadata.gz: 52997b101110f7111f307af5bc5a66ce28d71cc74af339a267d55f8fe4bdeb1d
4
+ data.tar.gz: 5b62b2bf9740563b8f4e8edc9e3fdd90303daa4e8ea205cdb94e39a842dba770
5
5
  SHA512:
6
- metadata.gz: efba5551493debe23617c41db429c224f51a4d0253274cf6fe28c5e384d78f4bd374ba3ad3132c2009cb2df9813266ac3a90eabd9a019aa7d48ee3999f2451e3
7
- data.tar.gz: 1dfd4f2281c8f6902b1c70ca0d272d0d2f671d9062669f147cfb38416ee771e7348ff131c8fd957c1e8674b7d83ad3f3c79f6c306a7779638b4481000534f695
6
+ metadata.gz: 6fe87d18b768bcdd4bf50f6a8ba5248856bcf54d7dd5afb5c8f8f9da4003902f7078782c8f75c685874f4d1427deb1348c48d6fe001bd78aa733af0acf24060e
7
+ data.tar.gz: a0190cbff7d1e7f0d8b312ad2d6fc9e0a88336db8f8d2dea8ac9a7d678fd35faac9ec3ca7ba029f22b5b1d48614797caef9b56443e10837af1a2d5106fe7fccb
data/CHANGELOG.md CHANGED
@@ -1,4 +1,73 @@
1
1
  ## Next
2
+ * Add key setup to v3 example in README
3
+ * 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
4
+ * Update to latest version of rubocop
5
+ * Drop support for Ruby 2.7; add Ruby 3.3
6
+ * Add i18n: de, es, it, pt, pt-BR
7
+
8
+ ## 5.16.0
9
+ * Allow usage of `options[:turbo]` as well as `options[:turbolinks]` for `recaptcha_v3`
10
+
11
+ ## 5.15.0
12
+ * Add 3.2 to the list of Ruby CI versions
13
+ * Add ability to submit verify_recaptcha via POST with JSON Body with `options[:json] = true`
14
+
15
+ ## 5.14.0
16
+ * drop json dependency
17
+
18
+ ## 5.13.1
19
+ * Permit actions as symbol
20
+
21
+ ## 5.13.0
22
+ * Added option to ignore_no_element.
23
+
24
+ ## 5.12.3
25
+ * Remove score fallback for enterprise
26
+ * Update enterprise tests to v1 assessment schema
27
+
28
+ ## 5.12.2
29
+ * Fix minimum score for enterprise
30
+
31
+ ## 5.12.1
32
+ * Fix Japanese locale
33
+
34
+ ## 5.12.0
35
+ * Added Japanese locale
36
+
37
+ ## 5.11.0
38
+ * Added Dutch locale
39
+
40
+ ## 5.10.1
41
+ * Fix enterprise_verify_url #415
42
+
43
+ ## 5.10.0
44
+ * Drop ruby 2.4 2.5 2.6
45
+ * Add maxiumm score support for hcaptcha
46
+
47
+ ## 5.9.0
48
+ * Gracefully handle invalid params
49
+
50
+ ## 5.8.1
51
+ * Allow configuring response limit
52
+
53
+ ## 5.8.0
54
+ * Add support for the enterprise API
55
+
56
+ ## 5.7.0
57
+ * french locale
58
+ * drop ruby 2.3
59
+
60
+ ## 5.6.0
61
+ * Allow multiple invisible recaptchas on a single page by setting custom selector
62
+
63
+ ## 5.5.0
64
+ * add `recaptcha_reply` controller method for better debugging/inspection
65
+
66
+ ## 5.4.1
67
+ * fix v2 vs 'data' postfix
68
+
69
+ ## 5.4.0
70
+ * added 'data' postfix to g-recaptcha-response attribute name to avoid collisions
2
71
 
3
72
  ## 5.3.0
4
73
  * turbolinks support
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,18 @@ 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
+
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
+
53
85
  Add `recaptcha_tags` to the forms you want to protect:
54
86
 
55
87
  ```erb
@@ -71,6 +103,7 @@ else
71
103
  render 'new'
72
104
  end
73
105
  ```
106
+ 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
107
 
75
108
  ## Sinatra / Rack / Ruby installation
76
109
 
@@ -119,7 +152,7 @@ The following options are available:
119
152
  Any unrecognized options will be added as attributes on the generated tag.
120
153
 
121
154
  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)
155
+ 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
156
  to see these options.
124
157
 
125
158
  Note that you cannot submit/verify the same response token more than once or you will get a
@@ -140,16 +173,19 @@ you like.
140
173
 
141
174
  Some of the options available:
142
175
 
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`
176
+ | Option | Description |
177
+ |---------------------------|-------------|
178
+ | `:model` | Model to set errors.
179
+ | `:attribute` | Model attribute to receive errors. (default: `:base`)
180
+ | `:message` | Custom error message.
181
+ | `:secret_key` | Override the secret API key from the configuration.
182
+ | `:enterprise_api_key` | Override the Enterprise API key from the configuration.
183
+ | `:enterprise_project_id ` | Override the Enterprise project ID from the configuration.
184
+ | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
185
+ | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
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`)
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
153
189
 
154
190
 
155
191
  ### `invisible_recaptcha_tags`
@@ -273,12 +309,20 @@ With v3, you can let all users log in without any intervention at all if their s
273
309
  threshold, and only show a v2 checkbox recaptcha challenge (fall back to v2) if it is below the
274
310
  threshold:
275
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
+
276
320
  ```erb
277
321
 
278
322
  <% if @show_checkbox_recaptcha %>
279
323
  <%= recaptcha_tags %>
280
324
  <% else %>
281
- <%= recaptcha_v3(action: 'login') %>
325
+ <%= recaptcha_v3(action: 'login', site_key: ENV['RECAPTCHA_SITE_KEY_V3']) %>
282
326
  <% end %>
283
327
 
284
328
  ```
@@ -286,7 +330,7 @@ threshold:
286
330
  ```ruby
287
331
  # app/controllers/sessions_controller.rb
288
332
  def create
289
- success = verify_recaptcha(action: 'login', minimum_score: 0.5)
333
+ success = verify_recaptcha(action: 'login', minimum_score: 0.5, secret_key: ENV['RECAPTCHA_SECRET_KEY_V3'])
290
334
  checkbox_success = verify_recaptcha unless success
291
335
  if success || checkbox_success
292
336
  # Perform action
@@ -357,17 +401,19 @@ then you can either:
357
401
  2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
358
402
  don't have a use for the hidden input element.
359
403
 
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.
404
+ Note that you cannot submit/verify the same response token more than once or you
405
+ will get a `timeout-or-duplicate` error code. If you need reset the captcha and
406
+ generate a new response token, then you need to call `grecaptcha.execute(…)` or
407
+ `grecaptcha.enterprise.execute(…)` again. This helper provides a JavaScript
408
+ method (for each action) named `executeRecaptchaFor{action}` to make this
409
+ easier. That is the same method that is invoked immediately. It simply calls
410
+ `grecaptcha.execute` or `grecaptcha.enterprise.execute` again and then calls the
411
+ `callback` function with the response token.
366
412
 
367
413
  You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
368
414
  response token and verifying it. This can easily happen with large forms that take the user a couple
369
415
  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
416
+ response expires, v3 appears to provide no such callback. See also
371
417
  [1](https://github.com/google/recaptcha/issues/281) and
372
418
  [2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
373
419
 
@@ -385,7 +431,7 @@ but only accepts the following options:
385
431
  | Option | Description |
386
432
  |---------------------|-------------|
387
433
  | `:site_key` | Override site API key |
388
- | `: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. |
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. |
389
435
  | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
390
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. |
391
437
  | `:id` | Specify a unique `id` attribute for the `<input>` element if using `element: :input`. (default: `"g-recaptcha-response-data-"` + `action`) |
@@ -393,7 +439,9 @@ but only accepts the following options:
393
439
  | `:script` | Same as setting both `:inline_script` and `:external_script`. (default: `true`). |
394
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`) |
395
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. |
396
- | `: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) |
397
445
 
398
446
  [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
399
447
 
@@ -427,7 +475,7 @@ According to https://developers.google.com/recaptcha/docs/v3#placement,
427
475
 
428
476
  > Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
429
477
 
430
- You will need to verify each action individually with separate call to `verify_recaptcha`.
478
+ You will need to verify each action individually with a separate call to `verify_recaptcha`.
431
479
 
432
480
  ```ruby
433
481
  result_a = verify_recaptcha(action: 'a')
@@ -487,14 +535,20 @@ Recaptcha.configuration.skip_verify_env.delete("test")
487
535
  Recaptcha.configure do |config|
488
536
  config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
489
537
  config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
538
+
490
539
  # Uncomment the following line if you are using a proxy server:
491
540
  # config.proxy = 'http://myproxy.com.au:8080'
541
+
542
+ # Uncomment the following lines if you are using the Enterprise API:
543
+ # config.enterprise = true
544
+ # config.enterprise_api_key = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
545
+ # config.enterprise_project_id = 'my-project'
492
546
  end
493
547
  ```
494
548
 
495
549
  ### Recaptcha.with_configuration
496
550
 
497
- For temporary overwrites (not thread safe).
551
+ For temporary overwrites (not thread-safe).
498
552
 
499
553
  ```ruby
500
554
  Recaptcha.with_configuration(site_key: '12345') do
@@ -514,6 +568,38 @@ recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
514
568
  verify_recaptcha secret_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
515
569
  ```
516
570
 
571
+
572
+ ## hCaptcha support
573
+
574
+ [hCaptcha](https://hcaptcha.com) is an alternative service providing reCAPTCHA API.
575
+
576
+ To use hCaptcha:
577
+ 1. Set a site and a secret key as usual
578
+ 2. Set two options in `verify_url` and `api_service_url` pointing to hCaptcha API endpoints.
579
+ 3. Disable a response limit check by setting a `response_limit` to the large enough value (reCAPTCHA is limited by 4000 characters).
580
+ 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.
581
+
582
+ ```ruby
583
+ # config/initializers/recaptcha.rb
584
+ Recaptcha.configure do |config|
585
+ config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
586
+ config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
587
+ config.verify_url = 'https://hcaptcha.com/siteverify'
588
+ config.api_server_url = 'https://hcaptcha.com/1/api.js'
589
+ config.response_limit = 100000
590
+ end
591
+ ```
592
+
593
+ 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.
594
+
595
+ ```ruby
596
+ result = verify_recaptcha(maximum_score: 0.7)
597
+ ```
598
+
599
+ | Option | Description |
600
+ |------------------|-------------|
601
+ | `: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`) |
602
+
517
603
  ## Misc
518
604
  - Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
519
605
  - [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
- 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,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.17.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], json: options[:json])
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,35 @@ module Recaptcha
113
145
  else
114
146
  Net::HTTP
115
147
  end
116
- 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
121
- request = Net::HTTP::Get.new(uri.request_uri)
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, json: false)
156
+ if json
157
+ uri = URI.parse(configuration.verify_url)
158
+ request = Net::HTTP::Post.new(uri.request_uri)
159
+ request['Content-Type'] = 'application/json; charset=utf-8'
160
+ request.body = JSON.generate(verify_hash)
161
+ else
162
+ query = URI.encode_www_form(verify_hash)
163
+ uri = URI.parse("#{configuration.verify_url}?#{query}")
164
+ request = Net::HTTP::Get.new(uri.request_uri)
165
+ end
166
+ http_instance = http_client_for(uri: uri, timeout: timeout)
167
+ JSON.parse(http_instance.request(request).body)
168
+ end
169
+
170
+ def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
171
+ query = URI.encode_www_form(query_params)
172
+ uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
173
+ http_instance = http_client_for(uri: uri, timeout: timeout)
174
+ request = Net::HTTP::Post.new(uri.request_uri)
175
+ request['Content-Type'] = 'application/json; charset=utf-8'
176
+ request.body = JSON.generate(body)
122
177
  JSON.parse(http_instance.request(request).body)
123
178
  end
124
179
  end
@@ -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
+ 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
+ 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
+ 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.
@@ -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.5.0
4
+ version: 5.17.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: 2024-06-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
@@ -154,13 +140,21 @@ files:
154
140
  - lib/recaptcha/rails.rb
155
141
  - lib/recaptcha/railtie.rb
156
142
  - lib/recaptcha/version.rb
143
+ - rails/locales/de.yml
157
144
  - rails/locales/en.yml
145
+ - rails/locales/es.yml
146
+ - rails/locales/fr.yml
147
+ - rails/locales/it.yml
148
+ - rails/locales/ja.yml
149
+ - rails/locales/nl.yml
150
+ - rails/locales/pt-BR.yml
151
+ - rails/locales/pt.yml
158
152
  homepage: http://github.com/ambethia/recaptcha
159
153
  licenses:
160
154
  - MIT
161
155
  metadata:
162
156
  source_code_uri: https://github.com/ambethia/recaptcha
163
- post_install_message:
157
+ post_install_message:
164
158
  rdoc_options: []
165
159
  require_paths:
166
160
  - lib
@@ -168,15 +162,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
162
  requirements:
169
163
  - - ">="
170
164
  - !ruby/object:Gem::Version
171
- version: 2.3.0
165
+ version: 3.0.0
172
166
  required_rubygems_version: !ruby/object:Gem::Requirement
173
167
  requirements:
174
168
  - - ">="
175
169
  - !ruby/object:Gem::Version
176
170
  version: '0'
177
171
  requirements: []
178
- rubygems_version: 3.0.3
179
- signing_key:
172
+ rubygems_version: 3.4.10
173
+ signing_key:
180
174
  specification_version: 4
181
175
  summary: Helpers for the reCAPTCHA API
182
176
  test_files: []