recaptcha 5.7.0 → 5.8.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 +3 -0
- data/README.md +37 -19
- data/lib/recaptcha.rb +54 -5
- data/lib/recaptcha/configuration.rb +21 -5
- data/lib/recaptcha/helpers.rb +19 -9
- data/lib/recaptcha/version.rb +1 -1
- data/rails/locales/fr.yml +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 555ad69df9bcc4154972887c8df613e687e2370caf4c4aa9e8e4843e02effd30
|
4
|
+
data.tar.gz: 40675afb3efbe1bdc78e48250715a8dd00bd1f4b27419f8bc72fbc1fd3911904
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8cc3cf0b1ccc9076a23a3f0588fcde888c131118089cb11fca59b79eccca511a61622286d087757b2a764744ac1370d3ab39e07a059dd5ab939e3c7d592660f
|
7
|
+
data.tar.gz: 8e716c170d96a0e39521d3c5a01a609c95dcdbe4daf5735936132e37bfc6ea8a7f73b9583bea653d145a950edaf3fdcbd1b6abdf63f886e04a6e12bbb052b7f1
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -68,6 +68,14 @@ export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
|
68
68
|
export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
69
69
|
```
|
70
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
|
+
|
71
79
|
Add `recaptcha_tags` to the forms you want to protect:
|
72
80
|
|
73
81
|
```erb
|
@@ -159,16 +167,18 @@ you like.
|
|
159
167
|
|
160
168
|
Some of the options available:
|
161
169
|
|
162
|
-
| Option
|
163
|
-
|
164
|
-
| `:model`
|
165
|
-
| `:attribute`
|
166
|
-
| `:message`
|
167
|
-
| `:secret_key`
|
168
|
-
| `:
|
169
|
-
| `:
|
170
|
-
| `:
|
171
|
-
| `:
|
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`
|
172
182
|
|
173
183
|
|
174
184
|
### `invisible_recaptcha_tags`
|
@@ -376,17 +386,19 @@ then you can either:
|
|
376
386
|
2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
|
377
387
|
don't have a use for the hidden input element.
|
378
388
|
|
379
|
-
Note that you cannot submit/verify the same response token more than once or you
|
380
|
-
`timeout-or-duplicate` error code. If you need reset the captcha and
|
381
|
-
then you need to call `grecaptcha.execute(…)`
|
382
|
-
|
383
|
-
|
384
|
-
|
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.
|
385
397
|
|
386
398
|
You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
|
387
399
|
response token and verifying it. This can easily happen with large forms that take the user a couple
|
388
400
|
minutes to complete. Unlike v2, where you can use the `expired-callback` to be notified when the
|
389
|
-
response
|
401
|
+
response expires, v3 appears to provide no such callback. See also
|
390
402
|
[1](https://github.com/google/recaptcha/issues/281) and
|
391
403
|
[2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
|
392
404
|
|
@@ -446,7 +458,7 @@ According to https://developers.google.com/recaptcha/docs/v3#placement,
|
|
446
458
|
|
447
459
|
> Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
|
448
460
|
|
449
|
-
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`.
|
450
462
|
|
451
463
|
```ruby
|
452
464
|
result_a = verify_recaptcha(action: 'a')
|
@@ -506,14 +518,20 @@ Recaptcha.configuration.skip_verify_env.delete("test")
|
|
506
518
|
Recaptcha.configure do |config|
|
507
519
|
config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
508
520
|
config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
521
|
+
|
509
522
|
# Uncomment the following line if you are using a proxy server:
|
510
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'
|
511
529
|
end
|
512
530
|
```
|
513
531
|
|
514
532
|
### Recaptcha.with_configuration
|
515
533
|
|
516
|
-
For temporary overwrites (not thread
|
534
|
+
For temporary overwrites (not thread-safe).
|
517
535
|
|
518
536
|
```ruby
|
519
537
|
Recaptcha.with_configuration(site_key: '12345') do
|
data/lib/recaptcha.rb
CHANGED
@@ -60,11 +60,44 @@ module Recaptcha
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def self.verify_via_api_call(response, options)
|
63
|
+
if Recaptcha.configuration.enterprise
|
64
|
+
verify_via_api_call_enterprise(response, options)
|
65
|
+
else
|
66
|
+
verify_via_api_call_free(response, options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.verify_via_api_call_enterprise(response, options)
|
71
|
+
site_key = options.fetch(:site_key) { configuration.site_key! }
|
72
|
+
api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! }
|
73
|
+
project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! }
|
74
|
+
|
75
|
+
query_params = { 'key' => api_key }
|
76
|
+
body = { 'event' => { 'token' => response, 'siteKey' => site_key } }
|
77
|
+
body['event']['expectedAction'] = options[:action] if options.key?(:action)
|
78
|
+
body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
|
79
|
+
|
80
|
+
reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
|
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?(reply['score'], options[:minimum_score])
|
87
|
+
|
88
|
+
if options[:with_reply] == true
|
89
|
+
return success, reply
|
90
|
+
else
|
91
|
+
return 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 =
|
100
|
+
reply = api_verification_free(verify_hash, timeout: options[:timeout])
|
68
101
|
success = reply['success'].to_s == 'true' &&
|
69
102
|
hostname_valid?(reply['hostname'], options[:hostname]) &&
|
70
103
|
action_valid?(reply['action'], options[:action]) &&
|
@@ -105,7 +138,7 @@ module Recaptcha
|
|
105
138
|
end
|
106
139
|
end
|
107
140
|
|
108
|
-
def self.
|
141
|
+
def self.http_client_for(uri:, timeout: nil)
|
109
142
|
timeout ||= DEFAULT_TIMEOUT
|
110
143
|
http = if configuration.proxy
|
111
144
|
proxy_server = URI.parse(configuration.proxy)
|
@@ -113,12 +146,28 @@ module Recaptcha
|
|
113
146
|
else
|
114
147
|
Net::HTTP
|
115
148
|
end
|
149
|
+
instance = http.new(uri.host, uri.port)
|
150
|
+
instance.read_timeout = instance.open_timeout = timeout
|
151
|
+
instance.use_ssl = true if uri.port == 443
|
152
|
+
|
153
|
+
instance
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.api_verification_free(verify_hash, timeout: nil)
|
116
157
|
query = URI.encode_www_form(verify_hash)
|
117
158
|
uri = URI.parse(configuration.verify_url + '?' + query)
|
118
|
-
http_instance =
|
119
|
-
http_instance.read_timeout = http_instance.open_timeout = timeout
|
120
|
-
http_instance.use_ssl = true if uri.port == 443
|
159
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
121
160
|
request = Net::HTTP::Get.new(uri.request_uri)
|
122
161
|
JSON.parse(http_instance.request(request).body)
|
123
162
|
end
|
163
|
+
|
164
|
+
def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
|
165
|
+
query = URI.encode_www_form(query_params)
|
166
|
+
uri = URI.parse(configuration.verify_url + "/#{project_id}/assessments" + '?' + query)
|
167
|
+
http_instance = http_client_for(uri: uri, timeout: timeout)
|
168
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
169
|
+
request['Content-Type'] = 'application/json; charset=utf-8'
|
170
|
+
request.body = JSON.generate(body)
|
171
|
+
JSON.parse(http_instance.request(request).body)
|
172
|
+
end
|
124
173
|
end
|
@@ -31,11 +31,14 @@ 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, :hostname
|
41
|
+
attr_accessor :enterprise, :enterprise_api_key, :enterprise_project_id
|
39
42
|
attr_writer :api_server_url, :verify_url
|
40
43
|
|
41
44
|
def initialize #:nodoc:
|
@@ -45,6 +48,11 @@ module Recaptcha
|
|
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
|
50
58
|
end
|
@@ -57,12 +65,20 @@ module Recaptcha
|
|
57
65
|
site_key || raise(RecaptchaError, "No site key specified.")
|
58
66
|
end
|
59
67
|
|
68
|
+
def enterprise_api_key!
|
69
|
+
enterprise_api_key || raise(RecaptchaError, "No Enterprise API key specified.")
|
70
|
+
end
|
71
|
+
|
72
|
+
def enterprise_project_id!
|
73
|
+
enterprise_project_id || raise(RecaptchaError, "No Enterprise project ID specified.")
|
74
|
+
end
|
75
|
+
|
60
76
|
def api_server_url
|
61
|
-
@api_server_url || DEFAULTS.fetch('
|
77
|
+
@api_server_url || (enterprise ? DEFAULTS.fetch('enterprise_server_url') : DEFAULTS.fetch('free_server_url'))
|
62
78
|
end
|
63
79
|
|
64
80
|
def verify_url
|
65
|
-
@verify_url || DEFAULTS.fetch('
|
81
|
+
@verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url'))
|
66
82
|
end
|
67
83
|
end
|
68
84
|
end
|
data/lib/recaptcha/helpers.rb
CHANGED
@@ -174,7 +174,8 @@ module Recaptcha
|
|
174
174
|
|
175
175
|
# v3
|
176
176
|
|
177
|
-
# 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
|
178
179
|
# calls the `callback` with the resulting response token.
|
179
180
|
private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
|
180
181
|
nonce = options[:nonce]
|
@@ -185,8 +186,8 @@ module Recaptcha
|
|
185
186
|
// Define function so that we can call it again later if we need to reset it
|
186
187
|
// This executes reCAPTCHA and then calls our callback.
|
187
188
|
function #{recaptcha_v3_execute_function_name(action)}() {
|
188
|
-
|
189
|
-
|
189
|
+
#{recaptcha_ready_method_name}(function() {
|
190
|
+
#{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
|
190
191
|
#{callback}('#{id}', token)
|
191
192
|
});
|
192
193
|
});
|
@@ -199,8 +200,8 @@ module Recaptcha
|
|
199
200
|
// Returns a Promise that resolves with the response token.
|
200
201
|
async function #{recaptcha_v3_async_execute_function_name(action)}() {
|
201
202
|
return new Promise((resolve, reject) => {
|
202
|
-
|
203
|
-
resolve(await
|
203
|
+
#{recaptcha_ready_method_name}(async function() {
|
204
|
+
resolve(await #{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}))
|
204
205
|
});
|
205
206
|
})
|
206
207
|
};
|
@@ -217,8 +218,8 @@ module Recaptcha
|
|
217
218
|
<<-HTML
|
218
219
|
<script#{nonce_attr}>
|
219
220
|
function #{recaptcha_v3_execute_function_name(action)}() {
|
220
|
-
|
221
|
-
|
221
|
+
#{recaptcha_ready_method_name}(function() {
|
222
|
+
#{recaptcha_execute_method_name}('#{site_key}', {action: '#{action}'}).then(function(token) {
|
222
223
|
#{callback}('#{id}', token)
|
223
224
|
});
|
224
225
|
});
|
@@ -251,8 +252,9 @@ module Recaptcha
|
|
251
252
|
recaptcha_v3_inline_script?(options)
|
252
253
|
end
|
253
254
|
|
254
|
-
# Returns the name of the JavaScript function that actually executes the
|
255
|
-
# 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.
|
256
258
|
def self.recaptcha_v3_execute_function_name(action)
|
257
259
|
"executeRecaptchaFor#{sanitize_action_for_js(action)}"
|
258
260
|
end
|
@@ -296,6 +298,14 @@ module Recaptcha
|
|
296
298
|
HTML
|
297
299
|
end
|
298
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
|
+
|
299
309
|
private_class_method def self.default_callback_required?(options)
|
300
310
|
options[:callback] == 'invisibleRecaptchaSubmit' &&
|
301
311
|
!Recaptcha.skip_env?(options[:env]) &&
|
data/lib/recaptcha/version.rb
CHANGED
data/rails/locales/fr.yml
CHANGED
@@ -2,4 +2,4 @@ fr:
|
|
2
2
|
recaptcha:
|
3
3
|
errors:
|
4
4
|
verification_failed: La vérification reCAPTCHA a échoué, veuillez essayer à nouveau.
|
5
|
-
recaptcha_unreachable: Oops, nous n'avons pas
|
5
|
+
recaptcha_unreachable: Oops, nous n'avons pas pu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
|
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.8.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: 2021-
|
11
|
+
date: 2021-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -176,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
176
|
- !ruby/object:Gem::Version
|
177
177
|
version: '0'
|
178
178
|
requirements: []
|
179
|
-
rubygems_version: 3.
|
179
|
+
rubygems_version: 3.2.16
|
180
180
|
signing_key:
|
181
181
|
specification_version: 4
|
182
182
|
summary: Helpers for the reCAPTCHA API
|