recaptcha 5.2.1 → 5.10.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: 1308b1cf9b9c14d9df6c9fe04eebfd241406e7afa31fbd522df8296ac7b0392a
4
- data.tar.gz: 9771326e251bbf449107fe8a3a3a9b52c8b0a3b2e99858c3867d9867b690408a
3
+ metadata.gz: 3870278113409bbbf6e9c772f8afc5a7130a1d9bf21eecaa7e8c6067b979a2c1
4
+ data.tar.gz: 7beaede8a6def64ae941a5c886188a53af77d243ddd80a84291fd9a033372c00
5
5
  SHA512:
6
- metadata.gz: 5eaae94b79027b794a1baaa5e0c46bf976356fa90e3008d7452fc5b109b08ba79a4f48aaf2c6f24bbfd039f1ee5949d45db7c9b7a8ba98fe10776d241bc894d2
7
- data.tar.gz: a18343d8c7a9162c2963992af87471d5bdcebf3123a95bba777d4223a8cfb9af776c0fb2dca7c89b2d9e1214aa0a72ad707f9fe1c8e5a23c91c80a787232b942
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
  [![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.
@@ -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/client_helper.rb)
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 | Description |
144
- |----------------|-------------|
145
- | `:model` | Model to set errors.
146
- | `:attribute` | Model attribute to receive errors. (default: `:base`)
147
- | `:message` | Custom error message.
148
- | `:secret_key` | Override the secret API key from the configuration.
149
- | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
150
- | `:response` | Custom response parameter. (default: `params['g-recaptcha-response']`)
151
- | `: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`)
152
- | `: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`
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 will get a
361
- `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
362
- then you need to call `grecaptcha.execute(…)` again. This helper provides a JavaScript method (for
363
- each action) named `executeRecaptchaFor{action}` to make this easier. That is the same method that
364
- is invoked immediately. It simply calls `grecaptcha.execute` again and then calls the `callback`
365
- 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.
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 expries, v3 appears to provide no such callback. See also
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 safe).
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
- Recaptcha.verify_via_api_call(recaptcha_response, options)
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'] should either be a
74
- # string or a hash with the action name(s) as keys. If it is a hash, then `action` is used as
75
- # the key.
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&.respond_to?(:to_h) # Includes ActionController::Parameters
80
- response_param[action].to_s
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
- response_param.to_s
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
- '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,
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 #:nodoc:
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('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
@@ -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 = options.delete(:id) || "g-recaptcha-response-" + dasherize_action(action)
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 recaptcha_v3_inline_script?(options)
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` 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
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
- grecaptcha.ready(function() {
183
- grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
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
- grecaptcha.ready(async function() {
198
- 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}'}))
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 reCAPTCHA code (calls
232
- # 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.
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 eles = document.getElementsByClassName('g-recaptcha');
264
- if (eles.length > 0) {
265
- var form = closestForm(eles[0]);
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]) &&
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Recaptcha
4
- VERSION = '5.2.1'
4
+ VERSION = '5.10.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,19 +55,60 @@ 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
+ 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 = api_verification(verify_hash, timeout: options[:timeout])
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
- return true if minimum_score.nil?
94
- return false if score.nil?
132
+ !minimum_score || (score && score >= minimum_score)
133
+ end
95
134
 
96
- case minimum_score
97
- when nil, FalseClass then true
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.api_verification(verify_hash, timeout: nil)
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 + '?' + query)
112
- http_instance = http.new(uri.host, uri.port)
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
@@ -0,0 +1,5 @@
1
+ fr:
2
+ recaptcha:
3
+ errors:
4
+ verification_failed: La vérification reCAPTCHA a échoué, 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.2.1
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: 2019-10-09 00:00:00.000000000 Z
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.3.0
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.0.3
179
+ rubygems_version: 3.1.6
179
180
  signing_key:
180
181
  specification_version: 4
181
182
  summary: Helpers for the reCAPTCHA API