recaptcha 5.5.0 → 5.17.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 +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +110 -24
- data/lib/recaptcha/adapters/controller_methods.rb +5 -3
- data/lib/recaptcha/configuration.rb +24 -6
- data/lib/recaptcha/helpers.rb +41 -24
- data/lib/recaptcha/version.rb +1 -1
- data/lib/recaptcha.rb +76 -21
- data/rails/locales/de.yml +5 -0
- data/rails/locales/es.yml +5 -0
- data/rails/locales/fr.yml +5 -0
- data/rails/locales/it.yml +5 -0
- data/rails/locales/ja.yml +5 -0
- data/rails/locales/nl.yml +5 -0
- data/rails/locales/pt-BR.yml +5 -0
- data/rails/locales/pt.yml +5 -0
- metadata +15 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52997b101110f7111f307af5bc5a66ce28d71cc74af339a267d55f8fe4bdeb1d
|
4
|
+
data.tar.gz: 5b62b2bf9740563b8f4e8edc9e3fdd90303daa4e8ea205cdb94e39a842dba770
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](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/
|
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
|
144
|
-
|
145
|
-
| `:model`
|
146
|
-
| `:attribute`
|
147
|
-
| `:message`
|
148
|
-
| `:secret_key`
|
149
|
-
| `:
|
150
|
-
| `:
|
151
|
-
| `:
|
152
|
-
| `:
|
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
|
361
|
-
`timeout-or-duplicate` error code. If you need reset the captcha and
|
362
|
-
then you need to call `grecaptcha.execute(…)`
|
363
|
-
|
364
|
-
|
365
|
-
|
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
|
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
|
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
|
-
| `:
|
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
|
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
|
87
|
-
|
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
|
-
|
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
|
-
'
|
35
|
-
'
|
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, :
|
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
|
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('
|
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('
|
83
|
+
@verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url'))
|
66
84
|
end
|
67
85
|
end
|
68
86
|
end
|
data/lib/recaptcha/helpers.rb
CHANGED
@@ -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
|
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
|
+
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
|
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
|
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
|
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`
|
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
|
-
|
189
|
-
|
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
|
-
|
203
|
-
resolve(await
|
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
|
-
|
221
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
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
|
255
|
-
# grecaptcha.execute
|
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
|
287
|
-
if (
|
288
|
-
var form = closestForm(
|
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
|
data/lib/recaptcha/version.rb
CHANGED
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 >
|
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 =
|
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
|
-
|
109
|
+
[success, reply]
|
75
110
|
else
|
76
|
-
|
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
|
-
|
100
|
-
|
133
|
+
!minimum_score || (score && score >= minimum_score)
|
134
|
+
end
|
101
135
|
|
102
|
-
|
103
|
-
|
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.
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
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.
|
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:
|
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:
|
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.
|
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: []
|