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 +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
|
[![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/
|
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: []
|