recaptcha 5.2.1 → 5.10.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 +32 -0
- data/README.md +113 -28
- data/lib/recaptcha/adapters/controller_methods.rb +17 -8
- data/lib/recaptcha/configuration.rb +24 -6
- data/lib/recaptcha/helpers.rb +48 -14
- data/lib/recaptcha/version.rb +1 -1
- data/lib/recaptcha.rb +70 -17
- data/rails/locales/fr.yml +5 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3870278113409bbbf6e9c772f8afc5a7130a1d9bf21eecaa7e8c6067b979a2c1
|
4
|
+
data.tar.gz: 7beaede8a6def64ae941a5c886188a53af77d243ddd80a84291fd9a033372c00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '089d2b491909e0e5c65dd021b97d99e196a69a6f43c452f835e08517a0e54b20d3a1166a16c7b16fa374e86c11629b847fe0f022b772db140c85412191231949'
|
7
|
+
data.tar.gz: ea8fe92b546e8174d0c143dd10c843683e7b4d793f40a3ccb6f403f10881ba0d246a20194e983f7332b869f11ca9aba7a1b998e984a21a49a550e960ba998e03
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,36 @@
|
|
1
1
|
## Next
|
2
|
+
* drop ruby 2.4 2.5 2.6
|
3
|
+
|
4
|
+
## 5.9.0
|
5
|
+
* Gracefully handle invalid params
|
6
|
+
|
7
|
+
## 5.8.1
|
8
|
+
* Allow configuring response limit
|
9
|
+
|
10
|
+
## 5.8.0
|
11
|
+
* Add support for the enterprise API
|
12
|
+
|
13
|
+
## 5.7.0
|
14
|
+
* french locale
|
15
|
+
* drop ruby 2.3
|
16
|
+
|
17
|
+
## 5.6.0
|
18
|
+
* Allow multiple invisible recaptchas on a single page by setting custom selector
|
19
|
+
|
20
|
+
## 5.5.0
|
21
|
+
* add `recaptcha_reply` controller method for better debugging/inspection
|
22
|
+
|
23
|
+
## 5.4.1
|
24
|
+
* fix v2 vs 'data' postfix
|
25
|
+
|
26
|
+
## 5.4.0
|
27
|
+
* added 'data' postfix to g-recaptcha-response attribute name to avoid collisions
|
28
|
+
|
29
|
+
## 5.3.0
|
30
|
+
* turbolinks support
|
31
|
+
|
32
|
+
## 5.2.0
|
33
|
+
* remove dependency on rails methods
|
2
34
|
|
3
35
|
## 5.1.0
|
4
36
|
* Added default translations for rails/i18n
|
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.
|
@@ -50,6 +68,14 @@ export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
|
50
68
|
export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
51
69
|
```
|
52
70
|
|
71
|
+
If you have an Enterprise API key:
|
72
|
+
|
73
|
+
```shell
|
74
|
+
export RECAPTCHA_ENTERPRISE = 'true'
|
75
|
+
export RECAPTCHA_ENTERPRISE_API_KEY = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
|
76
|
+
export RECAPTCHA_ENTERPRISE_PROJECT_ID = 'my-project'
|
77
|
+
```
|
78
|
+
|
53
79
|
Add `recaptcha_tags` to the forms you want to protect:
|
54
80
|
|
55
81
|
```erb
|
@@ -71,6 +97,7 @@ else
|
|
71
97
|
render 'new'
|
72
98
|
end
|
73
99
|
```
|
100
|
+
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
101
|
|
75
102
|
## Sinatra / Rack / Ruby installation
|
76
103
|
|
@@ -119,7 +146,7 @@ The following options are available:
|
|
119
146
|
Any unrecognized options will be added as attributes on the generated tag.
|
120
147
|
|
121
148
|
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/
|
149
|
+
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
150
|
to see these options.
|
124
151
|
|
125
152
|
Note that you cannot submit/verify the same response token more than once or you will get a
|
@@ -140,16 +167,18 @@ you like.
|
|
140
167
|
|
141
168
|
Some of the options available:
|
142
169
|
|
143
|
-
| Option
|
144
|
-
|
145
|
-
| `:model`
|
146
|
-
| `:attribute`
|
147
|
-
| `:message`
|
148
|
-
| `:secret_key`
|
149
|
-
| `:
|
150
|
-
| `:
|
151
|
-
| `:
|
152
|
-
| `:
|
170
|
+
| Option | Description |
|
171
|
+
|---------------------------|-------------|
|
172
|
+
| `:model` | Model to set errors.
|
173
|
+
| `:attribute` | Model attribute to receive errors. (default: `:base`)
|
174
|
+
| `:message` | Custom error message.
|
175
|
+
| `:secret_key` | Override the secret API key from the configuration.
|
176
|
+
| `:enterprise_api_key` | Override the Enterprise API key from the configuration.
|
177
|
+
| `:enterprise_project_id ` | Override the Enterprise project ID from the configuration.
|
178
|
+
| `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
|
179
|
+
| `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
|
180
|
+
| `: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`)
|
181
|
+
| `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
|
153
182
|
|
154
183
|
|
155
184
|
### `invisible_recaptcha_tags`
|
@@ -278,7 +307,7 @@ threshold:
|
|
278
307
|
<% if @show_checkbox_recaptcha %>
|
279
308
|
<%= recaptcha_tags %>
|
280
309
|
<% else %>
|
281
|
-
<%= recaptcha_v3(action: 'login') %>
|
310
|
+
<%= recaptcha_v3(action: 'login', site_key: ENV['RECAPTCHA_SITE_KEY_V3']) %>
|
282
311
|
<% end %>
|
283
312
|
…
|
284
313
|
```
|
@@ -286,7 +315,7 @@ threshold:
|
|
286
315
|
```ruby
|
287
316
|
# app/controllers/sessions_controller.rb
|
288
317
|
def create
|
289
|
-
success = verify_recaptcha(action: 'login', minimum_score: 0.5)
|
318
|
+
success = verify_recaptcha(action: 'login', minimum_score: 0.5, secret_key: ENV['RECAPTCHA_SECRET_KEY_V3'])
|
290
319
|
checkbox_success = verify_recaptcha unless success
|
291
320
|
if success || checkbox_success
|
292
321
|
# Perform action
|
@@ -344,7 +373,7 @@ function). This lets you include `recaptcha_v3` within a `<form>` tag and have i
|
|
344
373
|
submit the token as part of the form submission.
|
345
374
|
|
346
375
|
Note: reCAPTCHA actually already adds its own hidden tag, like `<textarea
|
347
|
-
id="g-recaptcha-response-100000" name="g-recaptcha-response" class="g-recaptcha-response">`,
|
376
|
+
id="g-recaptcha-response-data-100000" name="g-recaptcha-response-data" class="g-recaptcha-response">`,
|
348
377
|
immediately ater the reCAPTCHA badge in the bottom right of the page — but since it is not inside of
|
349
378
|
any `<form>` element, and since it already passes the token to the callback, this hidden `textarea`
|
350
379
|
isn't helpful to us.
|
@@ -353,21 +382,23 @@ If you need to submit the response token to the server in a different way than v
|
|
353
382
|
submit, such as via [Ajax](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) or [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API),
|
354
383
|
then you can either:
|
355
384
|
1. just extract the token out of the hidden `<input>` or `<textarea>` (both of which will have a
|
356
|
-
predictable name/id), like `document.getElementById('g-recaptcha-response-my-action').value`, or
|
385
|
+
predictable name/id), like `document.getElementById('g-recaptcha-response-data-my-action').value`, or
|
357
386
|
2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
|
358
387
|
don't have a use for the hidden input element.
|
359
388
|
|
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
|
-
|
389
|
+
Note that you cannot submit/verify the same response token more than once or you
|
390
|
+
will get a `timeout-or-duplicate` error code. If you need reset the captcha and
|
391
|
+
generate a new response token, then you need to call `grecaptcha.execute(…)` or
|
392
|
+
`grecaptcha.enterprise.execute(…)` again. This helper provides a JavaScript
|
393
|
+
method (for each action) named `executeRecaptchaFor{action}` to make this
|
394
|
+
easier. That is the same method that is invoked immediately. It simply calls
|
395
|
+
`grecaptcha.execute` or `grecaptcha.enterprise.execute` again and then calls the
|
396
|
+
`callback` function with the response token.
|
366
397
|
|
367
398
|
You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
|
368
399
|
response token and verifying it. This can easily happen with large forms that take the user a couple
|
369
400
|
minutes to complete. Unlike v2, where you can use the `expired-callback` to be notified when the
|
370
|
-
response
|
401
|
+
response expires, v3 appears to provide no such callback. See also
|
371
402
|
[1](https://github.com/google/recaptcha/issues/281) and
|
372
403
|
[2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
|
373
404
|
|
@@ -388,11 +419,12 @@ but only accepts the following options:
|
|
388
419
|
| `: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. |
|
389
420
|
| `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
|
390
421
|
| `: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
|
-
| `:id` | Specify a unique `id` attribute for the `<input>` element if using `element: :input`. (default: `"g-recaptcha-response-"` + `action`) |
|
392
|
-
| `:name` | Specify a unique `name` attribute for the `<input>` element if using `element: :input`. (default: `g-recaptcha-response[action]`) |
|
422
|
+
| `:id` | Specify a unique `id` attribute for the `<input>` element if using `element: :input`. (default: `"g-recaptcha-response-data-"` + `action`) |
|
423
|
+
| `:name` | Specify a unique `name` attribute for the `<input>` element if using `element: :input`. (default: `g-recaptcha-response-data[action]`) |
|
393
424
|
| `:script` | Same as setting both `:inline_script` and `:external_script`. (default: `true`). |
|
394
425
|
| `: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
426
|
| `: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. |
|
427
|
+
| `: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. |
|
396
428
|
|
397
429
|
[JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
|
398
430
|
|
@@ -426,7 +458,7 @@ According to https://developers.google.com/recaptcha/docs/v3#placement,
|
|
426
458
|
|
427
459
|
> Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
|
428
460
|
|
429
|
-
You will need to verify each action individually with separate call to `verify_recaptcha`.
|
461
|
+
You will need to verify each action individually with a separate call to `verify_recaptcha`.
|
430
462
|
|
431
463
|
```ruby
|
432
464
|
result_a = verify_recaptcha(action: 'a')
|
@@ -434,11 +466,27 @@ result_b = verify_recaptcha(action: 'b')
|
|
434
466
|
```
|
435
467
|
|
436
468
|
Because the response tokens for multiple actions may be submitted together in the same request, they
|
437
|
-
are passed as a hash under `params['g-recaptcha-response']` with the action as the key.
|
469
|
+
are passed as a hash under `params['g-recaptcha-response-data']` with the action as the key.
|
438
470
|
|
439
471
|
It is recommended to pass `external_script: false` on all but one of the calls to
|
440
472
|
`recaptcha` since you only need to include the script tag once for a given `site_key`.
|
441
473
|
|
474
|
+
## `recaptcha_reply`
|
475
|
+
|
476
|
+
After `verify_recaptcha` has been called, you can call `recaptcha_reply` to get the raw reply from recaptcha. This can allow you to get the exact score returned by recaptcha should you need it.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
if verify_recaptcha(action: 'login')
|
480
|
+
redirect_to @user
|
481
|
+
else
|
482
|
+
score = recaptcha_reply['score']
|
483
|
+
Rails.logger.warn("User #{@user.id} was denied login because of a recaptcha score of #{score}")
|
484
|
+
render 'new'
|
485
|
+
end
|
486
|
+
```
|
487
|
+
|
488
|
+
`recaptcha_reply` will return `nil` if the the reply was not yet fetched.
|
489
|
+
|
442
490
|
## I18n support
|
443
491
|
|
444
492
|
reCAPTCHA supports the I18n gem (it comes with English translations)
|
@@ -470,14 +518,20 @@ Recaptcha.configuration.skip_verify_env.delete("test")
|
|
470
518
|
Recaptcha.configure do |config|
|
471
519
|
config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
472
520
|
config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
521
|
+
|
473
522
|
# Uncomment the following line if you are using a proxy server:
|
474
523
|
# config.proxy = 'http://myproxy.com.au:8080'
|
524
|
+
|
525
|
+
# Uncomment the following lines if you are using the Enterprise API:
|
526
|
+
# config.enterprise = true
|
527
|
+
# config.enterprise_api_key = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA'
|
528
|
+
# config.enterprise_project_id = 'my-project'
|
475
529
|
end
|
476
530
|
```
|
477
531
|
|
478
532
|
### Recaptcha.with_configuration
|
479
533
|
|
480
|
-
For temporary overwrites (not thread
|
534
|
+
For temporary overwrites (not thread-safe).
|
481
535
|
|
482
536
|
```ruby
|
483
537
|
Recaptcha.with_configuration(site_key: '12345') do
|
@@ -497,8 +551,39 @@ recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
|
497
551
|
verify_recaptcha secret_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
498
552
|
```
|
499
553
|
|
554
|
+
|
555
|
+
## hCaptcha support
|
556
|
+
|
557
|
+
[hCaptcha](https://hcaptcha.com) is an alternative service providing reCAPTCHA API.
|
558
|
+
|
559
|
+
To use hCaptcha:
|
560
|
+
1. Set a site and a secret key as usual
|
561
|
+
2. Set two options in `verify_url` and `api_service_url` pointing to hCaptcha API endpoints.
|
562
|
+
3. Disable a response limit check by setting a `response_limit` to the large enough value (reCAPTCHA is limited by 4000 characters).
|
563
|
+
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.
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
# config/initializers/recaptcha.rb
|
567
|
+
Recaptcha.configure do |config|
|
568
|
+
config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
569
|
+
config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
570
|
+
config.verify_url = 'https://hcaptcha.com/siteverify'
|
571
|
+
config.api_server_url = 'https://hcaptcha.com/1/api.js'
|
572
|
+
config.response_limit = 100000
|
573
|
+
end
|
574
|
+
```
|
575
|
+
|
576
|
+
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.
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
result = verify_recaptcha(maximum_score: 0.7)
|
580
|
+
```
|
581
|
+
|
582
|
+
| Option | Description |
|
583
|
+
|------------------|-------------|
|
584
|
+
| `: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`) |
|
585
|
+
|
500
586
|
## Misc
|
501
587
|
- Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
|
502
588
|
- [Add multiple widgets to the same page](https://github.com/ambethia/recaptcha/wiki/Add-multiple-widgets-to-the-same-page)
|
503
589
|
- [Use Recaptcha with Devise](https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise)
|
504
|
-
|
@@ -24,7 +24,9 @@ module Recaptcha
|
|
24
24
|
options = options.merge(remote_ip: remoteip.to_s) if remoteip
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
success, @_recaptcha_reply =
|
28
|
+
Recaptcha.verify_via_api_call(recaptcha_response, options.merge(with_reply: true))
|
29
|
+
success
|
28
30
|
end
|
29
31
|
|
30
32
|
if verified
|
@@ -58,6 +60,10 @@ module Recaptcha
|
|
58
60
|
verify_recaptcha(options) || raise(VerifyError)
|
59
61
|
end
|
60
62
|
|
63
|
+
def recaptcha_reply
|
64
|
+
@_recaptcha_reply if defined?(@_recaptcha_reply)
|
65
|
+
end
|
66
|
+
|
61
67
|
def recaptcha_error(model, attribute, message)
|
62
68
|
if model
|
63
69
|
model.errors.add(attribute, message)
|
@@ -70,16 +76,19 @@ module Recaptcha
|
|
70
76
|
request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
|
71
77
|
end
|
72
78
|
|
73
|
-
# Extracts response token from params. params['g-recaptcha-response']
|
74
|
-
#
|
75
|
-
# the
|
79
|
+
# Extracts response token from params. params['g-recaptcha-response-data'] for recaptcha_v3 or
|
80
|
+
# params['g-recaptcha-response'] for recaptcha_tags and invisible_recaptcha_tags and should
|
81
|
+
# either be a string or a hash with the action name(s) as keys. If it is a hash, then `action`
|
82
|
+
# is used as the key.
|
76
83
|
# @return [String] A response token if one was passed in the params; otherwise, `''`
|
77
84
|
def recaptcha_response_token(action = nil)
|
78
|
-
response_param = params['g-recaptcha-response']
|
79
|
-
if response_param
|
80
|
-
|
85
|
+
response_param = params['g-recaptcha-response-data'] || params['g-recaptcha-response']
|
86
|
+
response_param = response_param[action] if action && response_param.respond_to?(:key?)
|
87
|
+
|
88
|
+
if response_param.is_a?(String)
|
89
|
+
response_param
|
81
90
|
else
|
82
|
-
|
91
|
+
''
|
83
92
|
end
|
84
93
|
end
|
85
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/v1beta1/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,8 +10,9 @@ 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
|
14
|
-
name = options.delete(:name) || "g-recaptcha-response[#{action}]"
|
13
|
+
id = options.delete(:id) || "g-recaptcha-response-data-#{dasherize_action(action)}"
|
14
|
+
name = options.delete(:name) || "g-recaptcha-response-data[#{action}]"
|
15
|
+
turbolinks = options.delete(:turbolinks)
|
15
16
|
options[:render] = site_key
|
16
17
|
options[:script_async] ||= false
|
17
18
|
options[:script_defer] ||= false
|
@@ -22,8 +23,13 @@ module Recaptcha
|
|
22
23
|
end
|
23
24
|
options[:class] = "g-recaptcha-response #{options[:class]}"
|
24
25
|
|
26
|
+
if turbolinks
|
27
|
+
options[:onload] = recaptcha_v3_execute_function_name(action)
|
28
|
+
end
|
25
29
|
html, tag_attributes = components(options)
|
26
|
-
if
|
30
|
+
if turbolinks
|
31
|
+
html << recaptcha_v3_onload_script(site_key, action, callback, id, options)
|
32
|
+
elsif recaptcha_v3_inline_script?(options)
|
27
33
|
html << recaptcha_v3_inline_script(site_key, action, callback, id, options)
|
28
34
|
end
|
29
35
|
case element
|
@@ -168,7 +174,8 @@ module Recaptcha
|
|
168
174
|
|
169
175
|
# v3
|
170
176
|
|
171
|
-
# Renders a script that calls `grecaptcha.execute`
|
177
|
+
# Renders a script that calls `grecaptcha.execute` or
|
178
|
+
# `grecaptcha.enterprise.execute` for the given `site_key` and `action` and
|
172
179
|
# calls the `callback` with the resulting response token.
|
173
180
|
private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
|
174
181
|
nonce = options[:nonce]
|
@@ -179,9 +186,8 @@ module Recaptcha
|
|
179
186
|
// Define function so that we can call it again later if we need to reset it
|
180
187
|
// This executes reCAPTCHA and then calls our callback.
|
181
188
|
function #{recaptcha_v3_execute_function_name(action)}() {
|
182
|
-
|
183
|
-
|
184
|
-
//console.log('#{id}', token)
|
189
|
+
#{recaptcha_ready_method_name}(function() {
|
190
|
+
#{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
|
185
191
|
#{callback}('#{id}', token)
|
186
192
|
});
|
187
193
|
});
|
@@ -194,8 +200,8 @@ module Recaptcha
|
|
194
200
|
// Returns a Promise that resolves with the response token.
|
195
201
|
async function #{recaptcha_v3_async_execute_function_name(action)}() {
|
196
202
|
return new Promise((resolve, reject) => {
|
197
|
-
|
198
|
-
resolve(await
|
203
|
+
#{recaptcha_ready_method_name}(async function() {
|
204
|
+
resolve(await #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}))
|
199
205
|
});
|
200
206
|
})
|
201
207
|
};
|
@@ -205,6 +211,24 @@ module Recaptcha
|
|
205
211
|
HTML
|
206
212
|
end
|
207
213
|
|
214
|
+
private_class_method def self.recaptcha_v3_onload_script(site_key, action, callback, id, options = {})
|
215
|
+
nonce = options[:nonce]
|
216
|
+
nonce_attr = " nonce='#{nonce}'" if nonce
|
217
|
+
|
218
|
+
<<-HTML
|
219
|
+
<script#{nonce_attr}>
|
220
|
+
function #{recaptcha_v3_execute_function_name(action)}() {
|
221
|
+
#{recaptcha_ready_method_name}(function() {
|
222
|
+
#{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
|
223
|
+
#{callback}('#{id}', token)
|
224
|
+
});
|
225
|
+
});
|
226
|
+
};
|
227
|
+
#{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
|
228
|
+
</script>
|
229
|
+
HTML
|
230
|
+
end
|
231
|
+
|
208
232
|
private_class_method def self.recaptcha_v3_inline_script?(options)
|
209
233
|
!Recaptcha.skip_env?(options[:env]) &&
|
210
234
|
options[:script] != false &&
|
@@ -228,8 +252,9 @@ module Recaptcha
|
|
228
252
|
recaptcha_v3_inline_script?(options)
|
229
253
|
end
|
230
254
|
|
231
|
-
# Returns the name of the JavaScript function that actually executes the
|
232
|
-
# grecaptcha.execute
|
255
|
+
# Returns the name of the JavaScript function that actually executes the
|
256
|
+
# reCAPTCHA code (calls `grecaptcha.execute` or
|
257
|
+
# `grecaptcha.enterprise.execute`). You can call it again later to reset it.
|
233
258
|
def self.recaptcha_v3_execute_function_name(action)
|
234
259
|
"executeRecaptchaFor#{sanitize_action_for_js(action)}"
|
235
260
|
end
|
@@ -248,6 +273,7 @@ module Recaptcha
|
|
248
273
|
private_class_method def self.default_callback(options = {})
|
249
274
|
nonce = options[:nonce]
|
250
275
|
nonce_attr = " nonce='#{nonce}'" if nonce
|
276
|
+
selector_attr = options[:id] ? "##{options[:id]}" : ".g-recaptcha"
|
251
277
|
|
252
278
|
<<-HTML
|
253
279
|
<script#{nonce_attr}>
|
@@ -260,9 +286,9 @@ module Recaptcha
|
|
260
286
|
return curEle.nodeName === 'FORM' ? curEle : null
|
261
287
|
};
|
262
288
|
|
263
|
-
var
|
264
|
-
if (
|
265
|
-
var form = closestForm(
|
289
|
+
var el = document.querySelector("#{selector_attr}")
|
290
|
+
if (!!el) {
|
291
|
+
var form = closestForm(el);
|
266
292
|
if (form) {
|
267
293
|
form.submit();
|
268
294
|
}
|
@@ -272,6 +298,14 @@ module Recaptcha
|
|
272
298
|
HTML
|
273
299
|
end
|
274
300
|
|
301
|
+
def self.recaptcha_execute_method_name
|
302
|
+
Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.execute" : "grecaptcha.execute"
|
303
|
+
end
|
304
|
+
|
305
|
+
def self.recaptcha_ready_method_name
|
306
|
+
Recaptcha.configuration.enterprise ? "grecaptcha.enterprise.ready" : "grecaptcha.ready"
|
307
|
+
end
|
308
|
+
|
275
309
|
private_class_method def self.default_callback_required?(options)
|
276
310
|
options[:callback] == 'invisibleRecaptchaSubmit' &&
|
277
311
|
!Recaptcha.skip_env?(options[:env]) &&
|
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,19 +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
|
+
token_properties = reply['tokenProperties']
|
81
|
+
success = !token_properties.nil? &&
|
82
|
+
token_properties['valid'].to_s == 'true' &&
|
83
|
+
hostname_valid?(token_properties['hostname'], options[:hostname]) &&
|
84
|
+
action_valid?(token_properties['action'], options[:action]) &&
|
85
|
+
score_above_threshold?(reply['score'], options[:minimum_score]) &&
|
86
|
+
score_below_threshold?(reply['score'], options[:maximum_score])
|
87
|
+
|
88
|
+
if options[:with_reply] == true
|
89
|
+
[success, reply]
|
90
|
+
else
|
91
|
+
success
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.verify_via_api_call_free(response, options)
|
63
96
|
secret_key = options.fetch(:secret_key) { configuration.secret_key! }
|
64
97
|
verify_hash = { 'secret' => secret_key, 'response' => response }
|
65
98
|
verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
|
66
99
|
|
67
|
-
reply =
|
68
|
-
reply['success'].to_s == 'true' &&
|
100
|
+
reply = api_verification_free(verify_hash, timeout: options[:timeout])
|
101
|
+
success = reply['success'].to_s == 'true' &&
|
69
102
|
hostname_valid?(reply['hostname'], options[:hostname]) &&
|
70
103
|
action_valid?(reply['action'], options[:action]) &&
|
71
|
-
score_above_threshold?(reply['score'], options[:minimum_score])
|
104
|
+
score_above_threshold?(reply['score'], options[:minimum_score]) &&
|
105
|
+
score_below_threshold?(reply['score'], options[:maximum_score])
|
106
|
+
|
107
|
+
if options[:with_reply] == true
|
108
|
+
[success, reply]
|
109
|
+
else
|
110
|
+
success
|
111
|
+
end
|
72
112
|
end
|
73
113
|
|
74
114
|
def self.hostname_valid?(hostname, validation)
|
@@ -88,18 +128,15 @@ module Recaptcha
|
|
88
128
|
end
|
89
129
|
end
|
90
130
|
|
91
|
-
# Returns true iff score is greater or equal to (>=) minimum_score, or if no minimum_score was specified
|
92
131
|
def self.score_above_threshold?(score, minimum_score)
|
93
|
-
|
94
|
-
|
132
|
+
!minimum_score || (score && score >= minimum_score)
|
133
|
+
end
|
95
134
|
|
96
|
-
|
97
|
-
|
98
|
-
else score >= minimum_score
|
99
|
-
end
|
135
|
+
def self.score_below_threshold?(score, maximum_score)
|
136
|
+
!maximum_score || (score && score <= maximum_score)
|
100
137
|
end
|
101
138
|
|
102
|
-
def self.
|
139
|
+
def self.http_client_for(uri:, timeout: nil)
|
103
140
|
timeout ||= DEFAULT_TIMEOUT
|
104
141
|
http = if configuration.proxy
|
105
142
|
proxy_server = URI.parse(configuration.proxy)
|
@@ -107,12 +144,28 @@ module Recaptcha
|
|
107
144
|
else
|
108
145
|
Net::HTTP
|
109
146
|
end
|
147
|
+
instance = http.new(uri.host, uri.port)
|
148
|
+
instance.read_timeout = instance.open_timeout = timeout
|
149
|
+
instance.use_ssl = true if uri.port == 443
|
150
|
+
|
151
|
+
instance
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.api_verification_free(verify_hash, timeout: nil)
|
110
155
|
query = URI.encode_www_form(verify_hash)
|
111
|
-
uri = URI.parse(configuration.verify_url
|
112
|
-
http_instance =
|
113
|
-
http_instance.read_timeout = http_instance.open_timeout = timeout
|
114
|
-
http_instance.use_ssl = true if uri.port == 443
|
156
|
+
uri = URI.parse("#{configuration.verify_url}?#{query}")
|
157
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
115
158
|
request = Net::HTTP::Get.new(uri.request_uri)
|
116
159
|
JSON.parse(http_instance.request(request).body)
|
117
160
|
end
|
161
|
+
|
162
|
+
def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
|
163
|
+
query = URI.encode_www_form(query_params)
|
164
|
+
uri = URI.parse("#{configuration.verify_url}/#{project_id}/assessments?#{query}")
|
165
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
166
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
167
|
+
request['Content-Type'] = 'application/json; charset=utf-8'
|
168
|
+
request.body = JSON.generate(body)
|
169
|
+
JSON.parse(http_instance.request(request).body)
|
170
|
+
end
|
118
171
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason L Perry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -155,6 +155,7 @@ files:
|
|
155
155
|
- lib/recaptcha/railtie.rb
|
156
156
|
- lib/recaptcha/version.rb
|
157
157
|
- rails/locales/en.yml
|
158
|
+
- rails/locales/fr.yml
|
158
159
|
homepage: http://github.com/ambethia/recaptcha
|
159
160
|
licenses:
|
160
161
|
- MIT
|
@@ -168,14 +169,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
169
|
requirements:
|
169
170
|
- - ">="
|
170
171
|
- !ruby/object:Gem::Version
|
171
|
-
version: 2.
|
172
|
+
version: 2.7.0
|
172
173
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
174
|
requirements:
|
174
175
|
- - ">="
|
175
176
|
- !ruby/object:Gem::Version
|
176
177
|
version: '0'
|
177
178
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
179
|
+
rubygems_version: 3.1.6
|
179
180
|
signing_key:
|
180
181
|
specification_version: 4
|
181
182
|
summary: Helpers for the reCAPTCHA API
|