recaptcha 5.7.0 → 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec76ff47d12ef3388c2a45d56850272fff3675c9f59619a09b55c918f70b5494
4
- data.tar.gz: 697f0a4d41ffab63c6eb5b7d7b81c1f5ac2b1305f27bb84b5fccc421bcd38e2c
3
+ metadata.gz: b4e91df77500a77804749d34ac9690c4ddc9a8cfa30af5f2eae6b51c3d0a0e02
4
+ data.tar.gz: 2fa166f38a4e39ee6b2244bdfc91991b3bfc6d65360c6dd2e846d584352e3aab
5
5
  SHA512:
6
- metadata.gz: 51b3627583241aef9a99e2a3ed9c78091c87a92e87210749d38e9ccf0d418dbec455fd524d83210b8331be7064efac2be2308366b8288c574c53b84f6013333e
7
- data.tar.gz: 8018c4668d7eef292e5908d609a7ebdd5cfbfb84b0c86a361dac36d3982cc075dc7e694c51639dd09eaf148cfd9536cecb12f6fa188ec72f87328e85976a4e22
6
+ metadata.gz: acfb0b7bf9d211b2571dfcd8ee5a8195dc149effee4a04fe609d116f869722e583f4a5737e7d21174f2ff3d0f08c1f73f845dd4859daa107500e272230562052
7
+ data.tar.gz: 8c64efbfd826ab4a817fdbd37ef8e199d6a7c661b0efc18f6e4c5b26aca45a4412c8c0b331772561fe478d1f88c88b1a0cc655471c3eb751883a767a852680cf
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
1
  ## Next
2
+ * Gracefully handle invalid params
3
+ * allow configuring response limit
4
+
5
+ ## 5.8.0
6
+ * Add support for the enterprise API
2
7
 
3
8
  ## 5.7.0
4
9
  * french locale
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 | Description |
163
- |----------------|-------------|
164
- | `:model` | Model to set errors.
165
- | `:attribute` | Model attribute to receive errors. (default: `:base`)
166
- | `:message` | Custom error message.
167
- | `:secret_key` | Override the secret API key from the configuration.
168
- | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
169
- | `:response` | Custom response parameter. (default: `params['g-recaptcha-response-data']`)
170
- | `: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`)
171
- | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
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 will get a
380
- `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
381
- then you need to call `grecaptcha.execute(…)` again. This helper provides a JavaScript method (for
382
- each action) named `executeRecaptchaFor{action}` to make this easier. That is the same method that
383
- is invoked immediately. It simply calls `grecaptcha.execute` again and then calls the `callback`
384
- function with the response token.
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 expries, v3 appears to provide no such callback. See also
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 safe).
534
+ For temporary overwrites (not thread-safe).
517
535
 
518
536
  ```ruby
519
537
  Recaptcha.with_configuration(site_key: '12345') do
@@ -533,6 +551,28 @@ recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
533
551
  verify_recaptcha secret_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
534
552
  ```
535
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 negative or 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 = -1
573
+ end
574
+ ```
575
+
536
576
  ## Misc
537
577
  - Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
538
578
  - [Add multiple widgets to the same page](https://github.com/ambethia/recaptcha/wiki/Add-multiple-widgets-to-the-same-page)
@@ -83,10 +83,12 @@ module Recaptcha
83
83
  # @return [String] A response token if one was passed in the params; otherwise, `''`
84
84
  def recaptcha_response_token(action = nil)
85
85
  response_param = params['g-recaptcha-response-data'] || params['g-recaptcha-response']
86
- if response_param&.respond_to?(:to_h) # Includes ActionController::Parameters
87
- response_param[action].to_s
86
+ response_param = response_param[action] if action && response_param.respond_to?(:key?)
87
+
88
+ if response_param.is_a?(String)
89
+ response_param
88
90
  else
89
- response_param.to_s
91
+ ''
90
92
  end
91
93
  end
92
94
  end
@@ -31,11 +31,14 @@ module Recaptcha
31
31
  #
32
32
  class Configuration
33
33
  DEFAULTS = {
34
- 'server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
- 'verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify'
34
+ 'free_server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
35
+ 'enterprise_server_url' => 'https://www.recaptcha.net/recaptcha/enterprise.js',
36
+ 'free_verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify',
37
+ 'enterprise_verify_url' => 'https://recaptchaenterprise.googleapis.com/v1beta1/projects'
36
38
  }.freeze
37
39
 
38
- attr_accessor :default_env, :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
40
+ attr_accessor :default_env, :skip_verify_env, :proxy, :secret_key, :site_key, :handle_timeouts_gracefully, :hostname
41
+ attr_accessor :enterprise, :enterprise_api_key, :enterprise_project_id, :response_limit
39
42
  attr_writer :api_server_url, :verify_url
40
43
 
41
44
  def initialize #:nodoc:
@@ -45,8 +48,15 @@ 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
58
+
59
+ @response_limit = 4000
50
60
  end
51
61
 
52
62
  def secret_key!
@@ -57,12 +67,20 @@ module Recaptcha
57
67
  site_key || raise(RecaptchaError, "No site key specified.")
58
68
  end
59
69
 
70
+ def enterprise_api_key!
71
+ enterprise_api_key || raise(RecaptchaError, "No Enterprise API key specified.")
72
+ end
73
+
74
+ def enterprise_project_id!
75
+ enterprise_project_id || raise(RecaptchaError, "No Enterprise project ID specified.")
76
+ end
77
+
60
78
  def api_server_url
61
- @api_server_url || DEFAULTS.fetch('server_url')
79
+ @api_server_url || (enterprise ? DEFAULTS.fetch('enterprise_server_url') : DEFAULTS.fetch('free_server_url'))
62
80
  end
63
81
 
64
82
  def verify_url
65
- @verify_url || DEFAULTS.fetch('verify_url')
83
+ @verify_url || (enterprise ? DEFAULTS.fetch('enterprise_verify_url') : DEFAULTS.fetch('free_verify_url'))
66
84
  end
67
85
  end
68
86
  end
@@ -174,7 +174,8 @@ module Recaptcha
174
174
 
175
175
  # v3
176
176
 
177
- # Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and
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
- grecaptcha.ready(function() {
189
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
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
- grecaptcha.ready(async function() {
203
- resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'}))
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
- grecaptcha.ready(function() {
221
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
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 reCAPTCHA code (calls
255
- # grecaptcha.execute). You can call it again later to reset it.
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]) &&
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Recaptcha
4
- VERSION = '5.7.0'
4
+ VERSION = '5.9.0'
5
5
  end
data/lib/recaptcha.rb CHANGED
@@ -14,7 +14,6 @@ end
14
14
 
15
15
  module Recaptcha
16
16
  DEFAULT_TIMEOUT = 3
17
- RESPONSE_LIMIT = 4000
18
17
 
19
18
  class RecaptchaError < StandardError
20
19
  end
@@ -56,15 +55,48 @@ module Recaptcha
56
55
  end
57
56
 
58
57
  def self.invalid_response?(resp)
59
- resp.empty? || resp.length > RESPONSE_LIMIT
58
+ resp.empty? || resp.length > configuration.response_limit
60
59
  end
61
60
 
62
61
  def self.verify_via_api_call(response, options)
62
+ if Recaptcha.configuration.enterprise
63
+ verify_via_api_call_enterprise(response, options)
64
+ else
65
+ verify_via_api_call_free(response, options)
66
+ end
67
+ end
68
+
69
+ def self.verify_via_api_call_enterprise(response, options)
70
+ site_key = options.fetch(:site_key) { configuration.site_key! }
71
+ api_key = options.fetch(:enterprise_api_key) { configuration.enterprise_api_key! }
72
+ project_id = options.fetch(:enterprise_project_id) { configuration.enterprise_project_id! }
73
+
74
+ query_params = { 'key' => api_key }
75
+ body = { 'event' => { 'token' => response, 'siteKey' => site_key } }
76
+ body['event']['expectedAction'] = options[:action] if options.key?(:action)
77
+ body['event']['userIpAddress'] = options[:remote_ip] if options.key?(:remote_ip)
78
+
79
+ reply = api_verification_enterprise(query_params, body, project_id, timeout: options[:timeout])
80
+ 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
+
87
+ if options[:with_reply] == true
88
+ return success, reply
89
+ else
90
+ return success
91
+ end
92
+ end
93
+
94
+ def self.verify_via_api_call_free(response, options)
63
95
  secret_key = options.fetch(:secret_key) { configuration.secret_key! }
64
96
  verify_hash = { 'secret' => secret_key, 'response' => response }
65
97
  verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
66
98
 
67
- reply = api_verification(verify_hash, timeout: options[:timeout])
99
+ reply = api_verification_free(verify_hash, timeout: options[:timeout])
68
100
  success = reply['success'].to_s == 'true' &&
69
101
  hostname_valid?(reply['hostname'], options[:hostname]) &&
70
102
  action_valid?(reply['action'], options[:action]) &&
@@ -105,7 +137,7 @@ module Recaptcha
105
137
  end
106
138
  end
107
139
 
108
- def self.api_verification(verify_hash, timeout: nil)
140
+ def self.http_client_for(uri:, timeout: nil)
109
141
  timeout ||= DEFAULT_TIMEOUT
110
142
  http = if configuration.proxy
111
143
  proxy_server = URI.parse(configuration.proxy)
@@ -113,12 +145,28 @@ module Recaptcha
113
145
  else
114
146
  Net::HTTP
115
147
  end
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)
116
156
  query = URI.encode_www_form(verify_hash)
117
157
  uri = URI.parse(configuration.verify_url + '?' + query)
118
- http_instance = http.new(uri.host, uri.port)
119
- http_instance.read_timeout = http_instance.open_timeout = timeout
120
- http_instance.use_ssl = true if uri.port == 443
158
+ http_instance = http_client_for(uri: uri, timeout: timeout)
121
159
  request = Net::HTTP::Get.new(uri.request_uri)
122
160
  JSON.parse(http_instance.request(request).body)
123
161
  end
162
+
163
+ def self.api_verification_enterprise(query_params, body, project_id, timeout: nil)
164
+ query = URI.encode_www_form(query_params)
165
+ uri = URI.parse(configuration.verify_url + "/#{project_id}/assessments" + '?' + query)
166
+ http_instance = http_client_for(uri: uri, timeout: timeout)
167
+ request = Net::HTTP::Post.new(uri.request_uri)
168
+ request['Content-Type'] = 'application/json; charset=utf-8'
169
+ request.body = JSON.generate(body)
170
+ JSON.parse(http_instance.request(request).body)
171
+ end
124
172
  end
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 vu valider votre réponse reCAPTCHA. Veuillez essayer à nouveau.
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.7.0
4
+ version: 5.9.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-02-05 00:00:00.000000000 Z
11
+ date: 2022-03-07 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.1.3
179
+ rubygems_version: 3.1.6
180
180
  signing_key:
181
181
  specification_version: 4
182
182
  summary: Helpers for the reCAPTCHA API