Codebase list ruby-recaptcha / upstream/5.2.1
Import upstream version 5.2.1, md5 038ca8b5e3dc75fda2d80aaee0596f9a Debian Janitor 4 years ago
58 changed file(s) with 1715 addition(s) and 721 deletion(s). Raw diff Collapse all Expand all
55
66 .gems
77 .rbenv-gemsets
8
9 /demo/*/Gemfile.lock
00 AllCops:
11 TargetRubyVersion: 2.3
22 Include:
3 - 'lib/**/*'
34 - 'Rakefile'
45 - 'Gemfile'
5 - 'Rakefile'
66 Exclude:
77 - 'vendor/**/*'
88 - 'demo/**/*'
4949 Style/NumericLiterals:
5050 Enabled: false
5151
52 Layout/FirstParameterIndentation:
52 Layout/IndentFirstArgument:
5353 Enabled: false
5454
55 Layout/IndentHash:
55 Layout/IndentFirstHashElement:
5656 Enabled: false
5757
5858 Layout/AlignParameters:
6565 Enabled: false
6666
6767 Metrics/PerceivedComplexity:
68 Enabled: false
69
70 Metrics/CyclomaticComplexity:
7168 Enabled: false
7269
7370 Style/DoubleNegation:
44 - 2.3
55 - 2.4
66 - 2.5
7 - 2.6
78 env:
89 - TASK=test
910 matrix:
1314 script: bundle exec rake $TASK
1415 branches:
1516 only: master
16 matrix:
17 fast_finish: true
18
17 before_install: ruby -e "File.write('Gemfile.lock', File.read('Gemfile.lock').split('BUNDLED WITH').first)"
0 ## Next
1
2 ## 5.1.0
3 * Added default translations for rails/i18n
4 * use recaptcha.net for the script tag
5
6 ## 5.0.0
7 * Changed host to Recaptcha.net
8 * Add v3 API support
9 * Renamed `Recaptcha::ClientHelper` to `Recaptcha::Adapters::ViewMethods`
10 * Renamed `Recaptcha::Verify` to `Recaptcha::Adapters::ControllerMethods`
11
12 ## 4.12.0 - 2018-08-30
13 * add `input` option to `invisible_recaptcha_tags`'s `ui` setting
14
15 ## 4.11.1 - 2018-08-08
16 * leave `tabindex` attribute alone for `invisible_recaptcha_tags`
17
018 ## 4.11.0 - 2018-08-06
119 * prefer RAILS_ENV over RACK_ENV #286
220
5977 * support disabling stoken
6078 * support Rails.env
6179
80 ## 0.4.0 / 2015-03-22
81
82 * Add support for ReCaptcha v2 API
83 * V2 API requires `g-recaptcha-response` parameters; https://github.com/ambethia/recaptcha/pull/114
84
6285 ## 0.3.6 / 2012-01-07
6386
6487 * Many documentation changes
00 PATH
11 remote: .
22 specs:
3 recaptcha (4.11.1)
3 recaptcha (5.2.1)
44 json
55
66 GEM
77 remote: https://rubygems.org/
88 specs:
9 activesupport (5.2.0)
10 concurrent-ruby (~> 1.0, >= 1.0.2)
11 i18n (>= 0.7, < 2)
12 minitest (~> 5.1)
13 tzinfo (~> 1.1)
14 addressable (2.5.2)
9 addressable (2.6.0)
1510 public_suffix (>= 2.0.2, < 4.0)
1611 ast (2.4.0)
17 bump (0.6.1)
18 byebug (10.0.2)
12 bump (0.8.0)
13 byebug (11.0.1)
1914 coderay (1.1.2)
20 concurrent-ruby (1.0.5)
15 concurrent-ruby (1.1.5)
2116 crack (0.4.3)
2217 safe_yaml (~> 1.0.0)
23 hashdiff (0.3.7)
24 i18n (1.0.1)
18 hashdiff (0.3.9)
19 i18n (1.6.0)
2520 concurrent-ruby (~> 1.0)
26 jaro_winkler (1.5.1)
27 json (2.1.0)
21 jaro_winkler (1.5.2)
22 json (2.2.0)
2823 maxitest (3.1.0)
2924 minitest (>= 5.0.0, < 5.12.0)
3025 metaclass (0.0.4)
31 method_source (0.9.0)
26 method_source (0.9.2)
3227 minitest (5.11.3)
33 mocha (1.5.0)
28 mocha (1.8.0)
3429 metaclass (~> 0.0.1)
35 parallel (1.12.1)
36 parser (2.5.1.0)
30 parallel (1.17.0)
31 parser (2.6.3.0)
3732 ast (~> 2.4.0)
38 powerpack (0.1.2)
39 pry (0.11.3)
33 pry (0.12.2)
4034 coderay (~> 1.1.0)
4135 method_source (~> 0.9.0)
42 pry-byebug (3.6.0)
43 byebug (~> 10.0)
36 pry-byebug (3.7.0)
37 byebug (~> 11.0)
4438 pry (~> 0.10)
45 public_suffix (3.0.2)
39 public_suffix (3.0.3)
4640 rainbow (3.0.0)
47 rake (12.3.1)
48 rubocop (0.57.2)
41 rake (12.3.2)
42 rubocop (0.68.1)
4943 jaro_winkler (~> 1.5.1)
5044 parallel (~> 1.10)
51 parser (>= 2.5)
52 powerpack (~> 0.1)
45 parser (>= 2.5, != 2.5.1.1)
5346 rainbow (>= 2.2.2, < 4.0)
5447 ruby-progressbar (~> 1.7)
55 unicode-display_width (~> 1.0, >= 1.0.1)
56 ruby-progressbar (1.9.0)
57 safe_yaml (1.0.4)
58 thread_safe (0.3.6)
59 tzinfo (1.2.5)
60 thread_safe (~> 0.1)
61 unicode-display_width (1.4.0)
62 webmock (3.4.2)
48 unicode-display_width (>= 1.4.0, < 1.6)
49 ruby-progressbar (1.10.0)
50 safe_yaml (1.0.5)
51 unicode-display_width (1.5.0)
52 webmock (3.5.1)
6353 addressable (>= 2.3.6)
6454 crack (>= 0.3.2)
6555 hashdiff
6858 ruby
6959
7060 DEPENDENCIES
71 activesupport
7261 bump
7362 i18n
7463 maxitest
8069 webmock
8170
8271 BUNDLED WITH
83 1.16.1
72 2.0.1
00 # reCAPTCHA
1 [![Gem Version](https://badge.fury.io/rb/recaptcha.svg)](https://badge.fury.io/rb/recaptcha)
12
23 Author: Jason L Perry (http://ambethia.com)<br/>
34 Copyright: Copyright (c) 2007-2013 Jason L Perry<br/>
56 Info: https://github.com/ambethia/recaptcha<br/>
67 Bugs: https://github.com/ambethia/recaptcha/issues<br/>
78
8 This plugin adds helpers for the [reCAPTCHA API](https://www.google.com/recaptcha). In your
9 views you can use the `recaptcha_tags` method to embed the needed javascript,
10 and you can validate in your controllers with `verify_recaptcha` or `verify_recaptcha!`,
11 which throws an error on failiure.
9 This gem provides helper methods for the [reCAPTCHA API](https://www.google.com/recaptcha). In your
10 views you can use the `recaptcha_tags` method to embed the needed javascript, and you can validate
11 in your controllers with `verify_recaptcha` or `verify_recaptcha!`, which raises an error on
12 failure.
13
14 ## Obtaining a key
15
16 Go to the [reCAPTCHA admin console](https://www.google.com/recaptcha/admin) to obtain a reCAPTCHA API key.
17
18 The reCAPTCHA type(s) that you choose for your key will determine which methods to use below.
19
20 | reCAPTCHA type | Methods to use | Description |
21 |----------------------------------------------|----------------|-------------|
22 | v3 | [`recaptcha_v3`](#recaptcha_v3) | Verify requests with a [score](https://developers.google.com/recaptcha/docs/v3#score)
23 | v2 Checkbox<br/>("I'm not a robot" Checkbox) | [`recaptcha_tags`](#recaptcha_tags) | Validate requests with the "I'm not a robot" checkbox |
24 | v2 Invisible<br/>(Invisible reCAPTCHA badge) | [`invisible_recaptcha_tags`](#invisible_recaptcha_tags) | Validate requests in the background |
25
26 Note: You can _only_ use methods that match your key's type. You cannot use v2 methods with a v3
27 key or use `recaptcha_tags` with a v2 Invisible key, for example. Otherwise you will get an
28 error like "Invalid key type" or "This site key is not enabled for the invisible captcha."
29
30 Note: Enter `localhost` or `127.0.0.1` as the domain if using in development with `localhost:3000`.
1231
1332 ## Rails Installation
1433
15 [obtain a reCAPTCHA API key](https://www.google.com/recaptcha/admin). Note: Use localhost or 127.0.0.1 in domain if using localhost:3000.
16
17 ```Ruby
34 ```ruby
1835 gem "recaptcha"
1936 ```
2037
21 Keep keys out of the code base with environment variables.<br/>
22 Set in production and locally use [dotenv](https://github.com/bkeepers/dotenv), make sure to add it above recaptcha.
23
24 Otherwise see [Alternative API key setup](#alternative-api-key-setup).
25
26 ```
27 export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
38 You can keep keys out of the code base with environment variables or with Rails [secrets](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-secrets).<br/>
39
40 In development, you can use the [dotenv](https://github.com/bkeepers/dotenv) gem. (Make sure to add it above `gem 'recaptcha'`.)
41
42 See [Alternative API key setup](#alternative-api-key-setup) for more ways to configure or override
43 keys. See also the
44 [Configuration](https://www.rubydoc.info/github/ambethia/recaptcha/master/Recaptcha/Configuration)
45 documentation.
46
47 ```shell
48 export RECAPTCHA_SITE_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
2849 export RECAPTCHA_SECRET_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
2950 ```
3051
31 Add `recaptcha_tags` to the forms you want to protect.
32
33 ```Erb
52 Add `recaptcha_tags` to the forms you want to protect:
53
54 ```erb
3455 <%= form_for @foo do |f| %>
35 # ... other tags
56 # …
3657 <%= recaptcha_tags %>
37 # ... other tags
58 # …
3859 <% end %>
3960 ```
4061
41 And, add `verify_recaptcha` logic to each form action that you've protected.
42
43 ```Ruby
62 Then, add `verify_recaptcha` logic to each form action that you've protected:
63
64 ```ruby
4465 # app/controllers/users_controller.rb
4566 @user = User.new(params[:user].permit(:name))
4667 if verify_recaptcha(model: @user) && @user.save
5677
5778 - add `gem 'recaptcha'` to `Gemfile`
5879 - set env variables
59 - `include Recaptcha::ClientHelper` where you need `recaptcha_tags`
60 - `include Recaptcha::Verify` where you need `verify_recaptcha`
61
62 ## recaptcha_tags
80 - `include Recaptcha::Adapters::ViewMethods` where you need `recaptcha_tags`
81 - `include Recaptcha::Adapters::ControllerMethods` where you need `verify_recaptcha`
82
83
84 ## reCAPTCHA v2 API and Usage
85
86 ### `recaptcha_tags`
87
88 Use this when your key's reCAPTCHA type is "v2 Checkbox".
89
90 The following options are available:
91
92 | Option | Description |
93 |---------------------|-------------|
94 | `:theme` | Specify the theme to be used per the API. Available options: `dark` and `light`. (default: `light`) |
95 | `:ajax` | Render the dynamic AJAX captcha per the API. (default: `false`) |
96 | `:site_key` | Override site API key from configuration |
97 | `:error` | Override the error code returned from the reCAPTCHA API (default: `nil`) |
98 | `:size` | Specify a size (default: `nil`) |
99 | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
100 | `:id` | Specify an html id attribute (default: `nil`) |
101 | `:callback` | Optional. Name of success callback function, executed when the user submits a successful response |
102 | `:expired_callback` | Optional. Name of expiration callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. |
103 | `:error_callback` | Optional. Name of error callback function, executed when reCAPTCHA encounters an error (e.g. network connectivity) |
104 | `:noscript` | Include `<noscript>` content (default: `true`)|
105
106 [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
107
108 | Option | Description |
109 |---------------------|-------------|
110 | `:onload` | Optional. The name of your callback function to be executed once all the dependencies have loaded. (See [explicit rendering](https://developers.google.com/recaptcha/docs/display#explicit_render)) |
111 | `:render` | Optional. Whether to render the widget explicitly. Defaults to `onload`, which will render the widget in the first g-recaptcha tag it finds. (See [explicit rendering](https://developers.google.com/recaptcha/docs/display#explicit_render)) |
112 | `:hl` | Optional. Forces the widget to render in a specific language. Auto-detects the user's language if unspecified. (See [language codes](https://developers.google.com/recaptcha/docs/language)) |
113 | `:script` | Alias for `:external_script`. If you do not need to add a script tag by helper you can set the option to `false`. It's necessary when you add a script tag manualy (default: `true`). |
114 | `:external_script` | Set to `false` to avoid including a script tag for the external `api.js` resource. Useful when including multiple `recaptcha_tags` on the same page. |
115 | `:script_async` | Set to `false` to load the external `api.js` resource synchronously. (default: `true`) |
116 | `:script_defer` | Set to `true` to defer loading of external `api.js` until HTML documen has been parsed. (default: `true`) |
117
118 Any unrecognized options will be added as attributes on the generated tag.
119
120 You can also override the html attributes for the sizes of the generated `textarea` and `iframe`
121 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)
122 to see these options.
123
124 Note that you cannot submit/verify the same response token more than once or you will get a
125 `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
126 then you need to call `grecaptcha.reset()`.
127
128 ### `verify_recaptcha`
129
130 This method returns `true` or `false` after processing the response token from the reCAPTCHA widget.
131 This is usually called from your controller, as seen [above](#rails-installation).
132
133 Passing in the ActiveRecord object via `model: object` is optional. If you pass a `model`—and the
134 captcha fails to verify—an error will be added to the object for you to use (available as
135 `object.errors`).
136
137 Why isn't this a model validation? Because that violates MVC. You can use it like this, or how ever
138 you like.
63139
64140 Some of the options available:
65141
66 | Option | Description |
67 |-------------------|-------------|
68 | :noscript | Include <noscript> content (default `true`)|
69 | :theme | Specify the theme to be used per the API. Available options: `dark` and `light`. (default `light`)|
70 | :ajax | Render the dynamic AJAX captcha per the API. (default `false`)|
71 | :site_key | Override site API key |
72 | :error | Override the error code returned from the reCAPTCHA API (default `nil`)|
73 | :size | Specify a size (default `nil`)|
74 | :hl | Optional. Forces the widget to render in a specific language. Auto-detects the user's language if unspecified. (See [language codes](https://developers.google.com/recaptcha/docs/language)) |
75 | :nonce | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default `nil`)|
76 | :id | Specify an html id attribute (default `nil`)|
77 | :script | If you do not need to add a script tag by helper you can set the option to false. It's necessary when you add a script tag manualy (default `true`)|
78 | :callback | Optional. Name of success callback function, executed when the user submits a successful response |
79 | :expired_callback | Optional. Name of expiration callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. |
80 | :error_callback | Optional. Name of error callback function, executed when reCAPTCHA encounters an error (e.g. network connectivity) |
81
82 You can also override the html attributes for the sizes of the generated `textarea` and `iframe`
83 elements, if CSS isn't your thing. Inspect the source of `recaptcha_tags` to see these options.
84
85 ## verify_recaptcha
86
87 This method returns `true` or `false` after processing the parameters from the reCAPTCHA widget. Why
88 isn't this a model validation? Because that violates MVC. You can use it like this, or how ever you
89 like. Passing in the ActiveRecord object is optional, if you do--and the captcha fails to verify--an
90 error will be added to the object for you to use.
91
92 Some of the options available:
93
94 | Option | Description |
95 |--------------|-------------|
96 | :model | Model to set errors.
97 | :attribute | Model attribute to receive errors. (default :base)
98 | :message | Custom error message.
99 | :secret_key | Override secret API key.
100 | :timeout | The number of seconds to wait for reCAPTCHA servers before give up. (default `3`)
101 | :response | Custom response parameter. (default: params['g-recaptcha-response'])
102 | :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`)
103 | :env | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
104
105 ## invisible_recaptcha_tags
106
107 Make sure to read [Invisible reCAPTCHA](https://developers.google.com/recaptcha/docs/invisible).
142 | Option | Description |
143 |----------------|-------------|
144 | `:model` | Model to set errors.
145 | `:attribute` | Model attribute to receive errors. (default: `:base`)
146 | `:message` | Custom error message.
147 | `:secret_key` | Override the secret API key from the configuration.
148 | `:timeout` | The number of seconds to wait for reCAPTCHA servers before give up. (default: `3`)
149 | `:response` | Custom response parameter. (default: `params['g-recaptcha-response']`)
150 | `: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`)
151 | `:env` | Current environment. The request to verify will be skipped if the environment is specified in configuration under `skip_verify_env`
152
153
154 ### `invisible_recaptcha_tags`
155
156 Use this when your key's reCAPTCHA type is "v2 Invisible".
157
158 For more information, refer to: [Invisible reCAPTCHA](https://developers.google.com/recaptcha/docs/invisible).
159
160 This is similar to `recaptcha_tags`, with the following additional options that are only available
161 on `invisible_recaptcha_tags`:
162
163 | Option | Description |
164 |---------------------|-------------|
165 | `:ui` | The type of UI to render for this "invisible" widget. (default: `:button`)<br/>`:button`: Renders a `<button type="submit">` tag with `options[:text]` as the button text.<br/>`:invisible`: Renders a `<div>` tag.<br/>`:input`: Renders a `<input type="submit">` tag with `options[:text]` as the button text. |
166 | `:text` | The text to show for the button. (default: `"Submit"`)
167 | `:inline_script` | If you do not need this helper to add an inline script tag, you can set the option to `false` (default: `true`).
168
169 It also accepts most of the options that `recaptcha_tags` accepts, including the following:
170
171 | Option | Description |
172 |---------------------|-------------|
173 | `:site_key` | Override site API key from configuration |
174 | `:nonce` | Optional. Sets nonce attribute for script tag. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
175 | `:id` | Specify an html id attribute (default: `nil`) |
176 | `:script` | Same as setting both `:inline_script` and `:external_script`. If you only need one or the other, use `:inline_script` and `:external_script` instead. |
177 | `:callback` | Optional. Name of success callback function, executed when the user submits a successful response |
178 | `:expired_callback` | Optional. Name of expiration callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. |
179 | `:error_callback` | Optional. Name of error callback function, executed when reCAPTCHA encounters an error (e.g. network connectivity) |
180
181 [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
182
183 | Option | Description |
184 |---------------------|-------------|
185 | `:onload` | Optional. The name of your callback function to be executed once all the dependencies have loaded. (See [explicit rendering](https://developers.google.com/recaptcha/docs/display#explicit_render)) |
186 | `:render` | Optional. Whether to render the widget explicitly. Defaults to `onload`, which will render the widget in the first g-recaptcha tag it finds. (See [explicit rendering](https://developers.google.com/recaptcha/docs/display#explicit_render)) |
187 | `:hl` | Optional. Forces the widget to render in a specific language. Auto-detects the user's language if unspecified. (See [language codes](https://developers.google.com/recaptcha/docs/language)) |
188 | `:external_script` | Set to `false` to avoid including a script tag for the external `api.js` resource. Useful when including multiple `recaptcha_tags` on the same page. |
189 | `:script_async` | Set to `false` to load the external `api.js` resource synchronously. (default: `true`) |
190 | `:script_defer` | Set to `false` to defer loading of external `api.js` until HTML documen has been parsed. (default: `true`) |
108191
109192 ### With a single form on a page
110193
111194 1. The `invisible_recaptcha_tags` generates a submit button for you.
112195
113 ```Erb
196 ```erb
114197 <%= form_for @foo do |f| %>
115198 # ... other tags
116199 <%= invisible_recaptcha_tags text: 'Submit form' %>
124207 1. You will need a custom callback function, which is called after verification with Google's reCAPTCHA service. This callback function must submit the form. Optionally, `invisible_recaptcha_tags` currently implements a JS function called `invisibleRecaptchaSubmit` that is called when no `callback` is passed. Should you wish to override `invisibleRecaptchaSubmit`, you will need to use `invisible_recaptcha_tags script: false`, see lib/recaptcha/client_helper.rb for details.
125208 2. The `invisible_recaptcha_tags` generates a submit button for you.
126209
127 ```Erb
210 ```erb
128211 <%= form_for @foo, html: {id: 'invisible-recaptcha-form'} do |f| %>
129212 # ... other tags
130213 <%= invisible_recaptcha_tags callback: 'submitInvisibleRecaptchaForm', text: 'Submit form' %>
131214 <% end %>
132215 ```
133216
134 ```Javascript
217 ```javascript
135218 // app/assets/javascripts/application.js
136219 var submitInvisibleRecaptchaForm = function () {
137220 document.getElementById("invisible-recaptcha-form").submit();
144227
145228 1. Specify `ui` option
146229
147 ```Erb
230 ```erb
148231 <%= form_for @foo, html: {id: 'invisible-recaptcha-form'} do |f| %>
149232 # ... other tags
150233 <button type="button" id="submit-btn">
154237 <% end %>
155238 ```
156239
157 ```Javascript
240 ```javascript
158241 // app/assets/javascripts/application.js
159242 document.getElementById('submit-btn').addEventListener('click', function (e) {
160243 // do some validation
169252 };
170253 ```
171254
255
256 ## reCAPTCHA v3 API and Usage
257
258 The main differences from v2 are:
259 1. you must specify an [action](https://developers.google.com/recaptcha/docs/v3#actions) in both frontend and backend
260 1. you can choose the minimum score required for you to consider the verification a success
261 (consider the user a human and not a robot)
262 1. reCAPTCHA v3 is invisible (except for the reCAPTCHA badge) and will never interrupt your users;
263 you have to choose which scores are considered an acceptable risk, and choose what to do (require
264 two-factor authentication, show a v3 challenge, etc.) if the score falls below the threshold you
265 choose
266
267 For more information, refer to the [v3 documentation](https://developers.google.com/recaptcha/docs/v3).
268
269 ### Examples
270
271 With v3, you can let all users log in without any intervention at all if their score is above some
272 threshold, and only show a v2 checkbox recaptcha challenge (fall back to v2) if it is below the
273 threshold:
274
275 ```erb
276 …
277 <% if @show_checkbox_recaptcha %>
278 <%= recaptcha_tags %>
279 <% else %>
280 <%= recaptcha_v3(action: 'login') %>
281 <% end %>
282 …
283 ```
284
285 ```ruby
286 # app/controllers/sessions_controller.rb
287 def create
288 success = verify_recaptcha(action: 'login', minimum_score: 0.5)
289 checkbox_success = verify_recaptcha unless success
290 if success || checkbox_success
291 # Perform action
292 else
293 if !success
294 @show_checkbox_recaptcha = true
295 end
296 render 'new'
297 end
298 end
299 ```
300
301 (You can also find this [example](demo/rails/app/controllers/v3_captchas_controller.rb) in the demo app.)
302
303 Another example:
304
305 ```erb
306 <%= form_for @user do |f| %>
307 …
308 <%= recaptcha_v3(action: 'registration') %>
309 …
310 <% end %>
311 ```
312
313 ```ruby
314 # app/controllers/users_controller.rb
315 def create
316 @user = User.new(params[:user].permit(:name))
317 recaptcha_valid = verify_recaptcha(model: @user, action: 'registration')
318 if recaptcha_valid
319 if @user.save
320 redirect_to @user
321 else
322 render 'new'
323 end
324 else
325 # Score is below threshold, so user may be a bot. Show a challenge, require multi-factor
326 # authentication, or do something else.
327 render 'new'
328 end
329 end
330 ```
331
332
333 ### `recaptcha_v3`
334
335 Adds an inline script tag that calls `grecaptcha.execute` for the given `site_key` and `action` and
336 calls the `callback` with the resulting response token. You need to verify this token with
337 [`verify_recaptcha`](#verify_recaptcha-use-with-v3) in your controller in order to get the
338 [score](https://developers.google.com/recaptcha/docs/v3#score).
339
340 By default, this inserts a hidden `<input type="hidden" class="g-recaptcha-response">` tag. The
341 value of this input will automatically be set to the response token (by the default callback
342 function). This lets you include `recaptcha_v3` within a `<form>` tag and have it automatically
343 submit the token as part of the form submission.
344
345 Note: reCAPTCHA actually already adds its own hidden tag, like `<textarea
346 id="g-recaptcha-response-100000" name="g-recaptcha-response" class="g-recaptcha-response">`,
347 immediately ater the reCAPTCHA badge in the bottom right of the page — but since it is not inside of
348 any `<form>` element, and since it already passes the token to the callback, this hidden `textarea`
349 isn't helpful to us.
350
351 If you need to submit the response token to the server in a different way than via a regular form
352 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),
353 then you can either:
354 1. just extract the token out of the hidden `<input>` or `<textarea>` (both of which will have a
355 predictable name/id), like `document.getElementById('g-recaptcha-response-my-action').value`, or
356 2. write and specify a custom `callback` function. You may also want to pass `element: false` if you
357 don't have a use for the hidden input element.
358
359 Note that you cannot submit/verify the same response token more than once or you will get a
360 `timeout-or-duplicate` error code. If you need reset the captcha and generate a new response token,
361 then you need to call `grecaptcha.execute(…)` again. This helper provides a JavaScript method (for
362 each action) named `executeRecaptchaFor{action}` to make this easier. That is the same method that
363 is invoked immediately. It simply calls `grecaptcha.execute` again and then calls the `callback`
364 function with the response token.
365
366 You will also get a `timeout-or-duplicate` error if too much time has passed between getting the
367 response token and verifying it. This can easily happen with large forms that take the user a couple
368 minutes to complete. Unlike v2, where you can use the `expired-callback` to be notified when the
369 response expries, v3 appears to provide no such callback. See also
370 [1](https://github.com/google/recaptcha/issues/281) and
371 [2](https://stackoverflow.com/questions/54437745/recaptcha-v3-how-to-deal-with-expired-token-after-idle).
372
373 To deal with this, it is recommended to call the "execute" in your form's submit handler (or
374 immediately before sending to the server to verify if not using a form) rather than using the
375 response token that gets generated when the page first loads. The `executeRecaptchaFor{action}`
376 function mentioned above can be used if you want it to invoke a callback, or the
377 `executeRecaptchaFor{action}Async` variant if you want a `Promise` that you can `await`. See
378 [demo/rails/app/views/v3_captchas/index.html.erb](demo/rails/app/views/v3_captchas/index.html.erb)
379 for an example of this.
380
381 This helper is similar to the [`recaptcha_tags`](#recaptcha_tags)/[`invisible_recaptcha_tags`](#invisible_recaptcha_tags) helpers
382 but only accepts the following options:
383
384 | Option | Description |
385 |---------------------|-------------|
386 | `:site_key` | Override site API key |
387 | `: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. |
388 | `:nonce` | Optional. Sets nonce attribute for script. Can be generated via `SecureRandom.base64(32)`. (default: `nil`) |
389 | `: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. |
390 | `:id` | Specify a unique `id` attribute for the `<input>` element if using `element: :input`. (default: `"g-recaptcha-response-"` + `action`) |
391 | `:name` | Specify a unique `name` attribute for the `<input>` element if using `element: :input`. (default: `g-recaptcha-response[action]`) |
392 | `:script` | Same as setting both `:inline_script` and `:external_script`. (default: `true`). |
393 | `: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`) |
394 | `: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. |
395
396 [JavaScript resource (api.js) parameters](https://developers.google.com/recaptcha/docs/invisible#js_param):
397
398 | Option | Description |
399 |---------------------|-------------|
400 | `:onload` | Optional. The name of your callback function to be executed once all the dependencies have loaded. (See [explicit rendering](https://developers.google.com/recaptcha/docs/display#explicit_render))|
401 | `:external_script` | Set to `false` to avoid including a script tag for the external `api.js` resource. Useful when including multiple `recaptcha_tags` on the same page.
402 | `:script_async` | Set to `true` to load the external `api.js` resource asynchronously. (default: `false`) |
403 | `:script_defer` | Set to `true` to defer loading of external `api.js` until HTML documen has been parsed. (default: `false`) |
404
405 If using `element: :input`, any unrecognized options will be added as attributes on the generated
406 `<input>` element.
407
408 ### `verify_recaptcha` (use with v3)
409
410 This works the same as for v2, except that you may pass an `action` and `minimum_score` if you wish
411 to validate that the action matches or that the score is above the given threshold, respectively.
412
413 ```ruby
414 result = verify_recaptcha(action: 'action/name')
415 ```
416
417 | Option | Description |
418 |------------------|-------------|
419 | `:action` | The name of the [reCAPTCHA action](https://developers.google.com/recaptcha/docs/v3#actions) that we are verifying. Set to `false` or `nil` to skip verifying that the action matches.
420 | `:minimum_score` | Provide a threshold to meet or exceed. Threshold should be a float between 0 and 1 which will be tested as `score >= minimum_score`. (Default: `nil`) |
421
422 ### Multiple actions on the same page
423
424 According to https://developers.google.com/recaptcha/docs/v3#placement,
425
426 > Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
427
428 You will need to verify each action individually with separate call to `verify_recaptcha`.
429
430 ```ruby
431 result_a = verify_recaptcha(action: 'a')
432 result_b = verify_recaptcha(action: 'b')
433 ```
434
435 Because the response tokens for multiple actions may be submitted together in the same request, they
436 are passed as a hash under `params['g-recaptcha-response']` with the action as the key.
437
438 It is recommended to pass `external_script: false` on all but one of the calls to
439 `recaptcha` since you only need to include the script tag once for a given `site_key`.
440
172441 ## I18n support
173 reCAPTCHA passes two types of error explanation to a linked model. It will use the I18n gem
174 to translate the default error message if I18n is available. To customize the messages to your locale,
175 add these keys to your I18n backend:
176
177 `recaptcha.errors.verification_failed` error message displayed if the captcha words didn't match
178 `recaptcha.errors.recaptcha_unreachable` displayed if a timeout error occured while attempting to verify the captcha
179
180 Also you can translate API response errors to human friendly by adding translations to the locale (`config/locales/en.yml`):
181
182 ```Yaml
442
443 reCAPTCHA supports the I18n gem (it comes with English translations)
444 To override or add new languages, add to `config/locales/*.yml`
445
446 ```yaml
447 # config/locales/en.yml
183448 en:
184449 recaptcha:
185450 errors:
186 verification_failed: 'Fail'
451 verification_failed: 'reCAPTCHA was incorrect, please try again.'
452 recaptcha_unreachable: 'reCAPTCHA verification server error, please try again.'
187453 ```
188454
189455 ## Testing
190456
191457 By default, reCAPTCHA is skipped in "test" and "cucumber" env. To enable it during test:
192458
193 ```Ruby
459 ```ruby
194460 Recaptcha.configuration.skip_verify_env.delete("test")
195461 ```
196462
198464
199465 ### Recaptcha.configure
200466
201 ```Ruby
467 ```ruby
202468 # config/initializers/recaptcha.rb
203469 Recaptcha.configure do |config|
204470 config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
212478
213479 For temporary overwrites (not thread safe).
214480
215 ```Ruby
481 ```ruby
216482 Recaptcha.with_configuration(site_key: '12345') do
217483 # Do stuff with the overwritten site_key.
218484 end
222488
223489 Pass in keys as options at runtime, for code base with multiple reCAPTCHA setups:
224490
225 ```Ruby
491 ```ruby
226492 recaptcha_tags site_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
227493
228494 # and
234500 - Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
235501 - [Add multiple widgets to the same page](https://github.com/ambethia/recaptcha/wiki/Add-multiple-widgets-to-the-same-page)
236502 - [Use Recaptcha with Devise](https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise)
503
0 # reCAPTCHA keys for testing purposes
1 # Make your own at https://www.google.com/recaptcha
0 # v2 reCAPTCHA keys for testing purposes
1 # (https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-what-should-i-do)
2 # You can make your own at https://www.google.com/recaptcha
23 RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
34 RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
00 source 'https://rubygems.org'
11
22 gem 'dotenv-rails', groups: [:development, :test]
3 gem 'rails', '4.2.5'
3 gem 'rails', '~> 5.2'
44 gem 'sqlite3'
55 gem 'recaptcha', require: 'recaptcha/rails', path: '../..'
6
7 group :development do
8 gem 'listen'
9 gem 'byebug'
10 end
+0
-115
demo/rails/Gemfile.lock less more
0 PATH
1 remote: ../..
2 specs:
3 recaptcha (3.3.0)
4 json
5
6 GEM
7 remote: https://rubygems.org/
8 specs:
9 actionmailer (4.2.5)
10 actionpack (= 4.2.5)
11 actionview (= 4.2.5)
12 activejob (= 4.2.5)
13 mail (~> 2.5, >= 2.5.4)
14 rails-dom-testing (~> 1.0, >= 1.0.5)
15 actionpack (4.2.5)
16 actionview (= 4.2.5)
17 activesupport (= 4.2.5)
18 rack (~> 1.6)
19 rack-test (~> 0.6.2)
20 rails-dom-testing (~> 1.0, >= 1.0.5)
21 rails-html-sanitizer (~> 1.0, >= 1.0.2)
22 actionview (4.2.5)
23 activesupport (= 4.2.5)
24 builder (~> 3.1)
25 erubis (~> 2.7.0)
26 rails-dom-testing (~> 1.0, >= 1.0.5)
27 rails-html-sanitizer (~> 1.0, >= 1.0.2)
28 activejob (4.2.5)
29 activesupport (= 4.2.5)
30 globalid (>= 0.3.0)
31 activemodel (4.2.5)
32 activesupport (= 4.2.5)
33 builder (~> 3.1)
34 activerecord (4.2.5)
35 activemodel (= 4.2.5)
36 activesupport (= 4.2.5)
37 arel (~> 6.0)
38 activesupport (4.2.5)
39 i18n (~> 0.7)
40 json (~> 1.7, >= 1.7.7)
41 minitest (~> 5.1)
42 thread_safe (~> 0.3, >= 0.3.4)
43 tzinfo (~> 1.1)
44 arel (6.0.3)
45 builder (3.2.2)
46 dotenv (2.0.2)
47 dotenv-rails (2.0.2)
48 dotenv (= 2.0.2)
49 railties (~> 4.0)
50 erubis (2.7.0)
51 globalid (0.3.6)
52 activesupport (>= 4.1.0)
53 i18n (0.7.0)
54 json (1.8.3)
55 loofah (2.0.3)
56 nokogiri (>= 1.5.9)
57 mail (2.6.3)
58 mime-types (>= 1.16, < 3)
59 mime-types (2.99)
60 mini_portile (0.6.2)
61 minitest (5.8.3)
62 nokogiri (1.6.6.4)
63 mini_portile (~> 0.6.0)
64 rack (1.6.4)
65 rack-test (0.6.3)
66 rack (>= 1.0)
67 rails (4.2.5)
68 actionmailer (= 4.2.5)
69 actionpack (= 4.2.5)
70 actionview (= 4.2.5)
71 activejob (= 4.2.5)
72 activemodel (= 4.2.5)
73 activerecord (= 4.2.5)
74 activesupport (= 4.2.5)
75 bundler (>= 1.3.0, < 2.0)
76 railties (= 4.2.5)
77 sprockets-rails
78 rails-deprecated_sanitizer (1.0.3)
79 activesupport (>= 4.2.0.alpha)
80 rails-dom-testing (1.0.7)
81 activesupport (>= 4.2.0.beta, < 5.0)
82 nokogiri (~> 1.6.0)
83 rails-deprecated_sanitizer (>= 1.0.1)
84 rails-html-sanitizer (1.0.2)
85 loofah (~> 2.0)
86 railties (4.2.5)
87 actionpack (= 4.2.5)
88 activesupport (= 4.2.5)
89 rake (>= 0.8.7)
90 thor (>= 0.18.1, < 2.0)
91 rake (10.4.2)
92 sprockets (3.4.0)
93 rack (> 1, < 3)
94 sprockets-rails (2.3.3)
95 actionpack (>= 3.0)
96 activesupport (>= 3.0)
97 sprockets (>= 2.8, < 4.0)
98 sqlite3 (1.3.11)
99 thor (0.19.1)
100 thread_safe (0.3.5)
101 tzinfo (1.2.2)
102 thread_safe (~> 0.1)
103
104 PLATFORMS
105 ruby
106
107 DEPENDENCIES
108 dotenv-rails
109 rails (= 4.2.5)
110 recaptcha!
111 sqlite3
112
113 BUNDLED WITH
114 1.12.5
0 To run the v2 examples, start the server with `rails s`. Then go to <http://localhost:3000/captchas>.
1
2 To run the v3 examples, you will need a v3 key, which you can get from
3 https://www.google.com/recaptcha/admin. Unlike v2, where they provide a standard [key you can use for
4 testing](https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-what-should-i-do)
5 (and which is in the [.env](.env) file, no such standard testing key exists for v3, so you need to
6 obtain your own.
7
8 Then set these environment variables:
9
10 export RECAPTCHA_SITE_KEY=your_v3_key
11 export RECAPTCHA_SECRET_KEY=your_v3_key
12
13 and start the server with `rails s`. Then go to <http://localhost:3000/v3_captchas>.
14
15 To run the example of v3 with v2 fallback, you can set:
16
17 ```
18 unset RECAPTCHA_SITE_KEY
19 unset RECAPTCHA_SECRET_KEY
20 export RECAPTCHA_SITE_KEY_V3=your_v3_key
21 export RECAPTCHA_SECRET_KEY_V3=your_v3_key
22 ```
23
24 and start the server again with `rails s`. Then go to <http://localhost:3000/v3_captchas?with_v2_fallback=1>.
+0
-12
demo/rails/app/controllers/captcha_controller.rb less more
0 class CaptchaController < ApplicationController
1 def index
2 end
3
4 def create
5 if verify_recaptcha
6 render text: 'YES'
7 else
8 render text: 'NO'
9 end
10 end
11 end
0 class CaptchasController < ApplicationController
1 def index
2 end
3
4 def create
5 if verify_recaptcha
6 render plain: 'YES'
7 else
8 render plain: 'NO'
9 end
10 end
11 end
0 class V3CaptchasController < ApplicationController
1 def index
2 end
3
4 def create
5 if verify_recaptcha(action: 'demo', minimum_score: 0.5)
6 render plain: 'YES'
7 else
8 render plain: 'NO'
9 end
10 end
11
12 def create_multi
13 if verify_recaptcha(action: 'demo_a', minimum_score: 0.5) &&
14 verify_recaptcha(action: 'demo_b', minimum_score: 0.5)
15 render plain: 'YES'
16 else
17 render plain: 'NO'
18 end
19 end
20
21 def create_with_v2_fallback
22 success = verify_recaptcha(action: 'login', minimum_score: 0.2, secret_key: ENV['RECAPTCHA_SECRET_KEY_V3'], **response_option)
23 checkbox_success = verify_recaptcha unless success
24 if success || checkbox_success
25 render plain: 'Success'
26 else
27 if !success
28 @show_checkbox_recaptcha = true
29 end
30 render 'index'
31 end
32 end
33
34 private
35
36 # This is only used to be able to simulate a failure. You wouldn't need this in a production app.
37 def response_option
38 if params[:commit] =~ /fail/i
39 # Simulate a failure
40 # Note that this doesn't work for v2 with the default testing key because it always returns
41 # success for that key.
42 response_option = {response: 'bogus'}
43 else
44 # Use the response token that was submitted
45 response_option = {}
46 end
47 end
48
49 end
50
+0
-51
demo/rails/app/views/captcha/index.html.erb less more
0 <% if params[:multi] %>
1 <script type="text/javascript">
2 var verifyCallback = function(response) {
3 alert(response);
4 };
5 var widgetId1;
6 var widgetId2;
7 var onloadCallback = function() {
8 // Renders the HTML element with id 'example1' as a reCAPTCHA widget.
9 // The id of the reCAPTCHA widget is assigned to 'widgetId1'.
10 widgetId1 = grecaptcha.render('example1', {
11 'sitekey' : "<%= Recaptcha.configuration.site_key %>",
12 'theme' : 'light'
13 });
14 widgetId2 = grecaptcha.render(document.getElementById('example2'), {
15 'sitekey' : "<%= Recaptcha.configuration.site_key %>"
16 });
17 grecaptcha.render('example3', {
18 'sitekey' : "<%= Recaptcha.configuration.site_key %>",
19 'callback' : verifyCallback,
20 'theme' : 'dark'
21 });
22 };
23 </script>
24
25 <%= form_tag "/captchas" do |f| %>
26 <div id="example1"></div>
27 <%= submit_tag %>
28 <% end %>
29 <%= form_tag "/captchas" do %>
30 <div id="example2"></div>
31 <%= submit_tag %>
32 <% end %>
33 <%= form_tag "/captchas" do %>
34 <div id="example3"></div>
35 <%= submit_tag %>
36 <% end %>
37 <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
38 <% elsif params[:invisible] %>
39 <%= form_tag "/captchas", id: "invisible-recaptcha-form" do %>
40 <%= invisible_recaptcha_tags text: 'Save changes' %>
41 <% end %>
42 <% else %>
43 <%= form_tag "/captchas" do %>
44 <%= recaptcha_tags %>
45 <%= submit_tag %>
46 <% end %>
47 <% end %>
48 <%= link_to 'Single ?', '?' if params[:multi] or params[:invisible] %>
49 <%= link_to 'Multi ?', '?multi=1' unless params[:multi] %>
50 <%= link_to 'Invisible ?', '?invisible=1' unless params[:invisible] %>
0 <% if params[:multi] %>
1 <script type="text/javascript">
2 var verifyCallback = function(response) {
3 alert(response);
4 };
5 var widgetId1;
6 var widgetId2;
7 var onloadCallback = function() {
8 // Renders the HTML element with id 'example1' as a reCAPTCHA widget.
9 // The id of the reCAPTCHA widget is assigned to 'widgetId1'.
10 widgetId1 = grecaptcha.render('example1', {
11 'sitekey' : "<%= Recaptcha.configuration.site_key %>",
12 'theme' : 'light'
13 });
14 widgetId2 = grecaptcha.render(document.getElementById('example2'), {
15 'sitekey' : "<%= Recaptcha.configuration.site_key %>"
16 });
17 grecaptcha.render('example3', {
18 'sitekey' : "<%= Recaptcha.configuration.site_key %>",
19 'callback' : verifyCallback,
20 'theme' : 'dark'
21 });
22 };
23 </script>
24
25 <%= form_tag "/captchas" do |f| %>
26 <div id="example1"></div>
27 <%= submit_tag %>
28 <% end %>
29 <%= form_tag "/captchas" do %>
30 <div id="example2"></div>
31 <%= submit_tag %>
32 <% end %>
33 <%= form_tag "/captchas" do %>
34 <div id="example3"></div>
35 <%= submit_tag %>
36 <% end %>
37 <script src="<%= Recaptcha.configuration.api_server_url %>?onload=onloadCallback&render=explicit" async defer></script>
38 <% elsif params[:invisible] %>
39 <%= form_tag "/captchas", id: "invisible-recaptcha-form" do %>
40 <%= invisible_recaptcha_tags text: 'Save changes' %>
41 <% end %>
42 <% else %>
43 <%= form_tag "/captchas" do %>
44 <%= recaptcha_tags %>
45 <%= submit_tag %>
46 <% end %>
47 <% end %>
48 <%= link_to 'Single ?', '?' if params[:multi] or params[:invisible] %>
49 <%= link_to 'Multi ?', '?multi=1' unless params[:multi] %>
50 <%= link_to 'Invisible ?', '?invisible=1' unless params[:invisible] %>
0 <% if params[:multi] %>
1 <%= form_tag "/#{controller_name}/create_multi" do |f| %>
2 <%= recaptcha_v3 action: 'demo_a' %>
3 <%= recaptcha_v3 action: 'demo_b', external_script: false %>
4 <%= submit_tag %>
5 <% end %>
6
7 <% elsif action_name == 'create_with_v2_fallback' || params[:with_v2_fallback] %>
8 <% if @recaptcha_verify_returned %>
9 <%= @recaptcha_verify_result.pretty_inspect %>
10 <% end %>
11 <%= form_tag "/#{controller_name}/create_with_v2_fallback" do %>
12 <% if @show_checkbox_recaptcha %>
13 <p>Automatic v3 verification failed. Falling back to a v3 challange...</p>
14 <%= recaptcha_tags %>
15 <%= submit_tag 'Submit' %>
16 <% else %>
17 <%= recaptcha_v3 action: 'login', site_key: ENV['RECAPTCHA_SITE_KEY_V3'] %>
18 <%= submit_tag 'Succes' %>
19 <%= submit_tag 'Failure' %>
20 <% end %>
21 <% end %>
22
23 <% elsif params[:custom_callback] %>
24 <script type="text/javascript">
25 var myCallback = function(action, response) {
26 alert(`Response for ${action} action: ${response}`);
27 };
28 </script>
29 <%= recaptcha_v3 action: 'demo', callback: 'myCallback' %>
30
31 <% elsif params[:execute_on_submit] %>
32 <%= form_tag "/#{controller_name}" do %>
33 <%= recaptcha_v3 action: 'demo' %>
34 <%= submit_tag %>
35 <% end %>
36
37 <script type="text/javascript">
38 const form = document.forms.item(0)
39 const hiddenInput = document.getElementById('g-recaptcha-response-demo')
40 form.addEventListener('submit', async function(e) {
41 e.preventDefault();
42 console.log('Submitted form. Getting fresh reCAPTCHA response token…')
43 const response = await window.executeRecaptchaForDemoAsync()
44 console.log(`Response was: ${response}`)
45 hiddenInput.value = response
46 form.submit()
47 });
48 </script>
49
50 <% else # Single %>
51 <%= form_tag "/#{controller_name}" do %>
52 <%= recaptcha_v3 action: 'demo' %>
53 <%= submit_tag %>
54 <% end %>
55 <% end %>
56
57 <%= link_to 'Single', '?' if params[:multi] || params[:custom_callback] || params[:execute_on_submit] %>
58 <%= link_to 'with v2 fallback', '/v3_captchas/?with_v2_fallback=1' unless params[:with_v2_fallback] %>
59 <%= link_to 'Multi', '?multi=1' unless params[:multi] %>
60 <%= link_to 'Custom callback', '?custom_callback=1' unless params[:custom_callback] %>
61 <%= link_to 'Execute on submit', '?execute_on_submit=1' unless params[:execute_on_submit] %>
00 #!/usr/bin/env ruby
1 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
1 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
22 load Gem.bin_path('bundler', 'bundle')
00 #!/usr/bin/env ruby
1 begin
2 load File.expand_path('../spring', __FILE__)
3 rescue LoadError => e
4 raise unless e.message.include?('spring')
5 end
6 APP_PATH = File.expand_path('../../config/application', __FILE__)
1 APP_PATH = File.expand_path('../config/application', __dir__)
72 require_relative '../config/boot'
83 require 'rails/commands'
00 #!/usr/bin/env ruby
1 begin
2 load File.expand_path('../spring', __FILE__)
3 rescue LoadError => e
4 raise unless e.message.include?('spring')
5 end
61 require_relative '../config/boot'
72 require 'rake'
83 Rake.application.run
00 #!/usr/bin/env ruby
1 require 'pathname'
1 require 'fileutils'
2 include FileUtils
23
34 # path to your application root.
4 APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
5 APP_ROOT = File.expand_path('..', __dir__)
56
6 Dir.chdir APP_ROOT do
7 def system!(*args)
8 system(*args) || abort("\n== Command #{args} failed ==")
9 end
10
11 chdir APP_ROOT do
712 # This script is a starting point to setup your application.
8 # Add necessary setup steps to this file:
13 # Add necessary setup steps to this file.
914
10 puts "== Installing dependencies =="
11 system "gem install bundler --conservative"
12 system "bundle check || bundle install"
15 puts '== Installing dependencies =='
16 system! 'gem install bundler --conservative'
17 system('bundle check') || system!('bundle install')
18
19 # Install JavaScript dependencies if using Yarn
20 # system('bin/yarn')
1321
1422 # puts "\n== Copying sample files =="
15 # unless File.exist?("config/database.yml")
16 # system "cp config/database.yml.sample config/database.yml"
23 # unless File.exist?('config/database.yml')
24 # cp 'config/database.yml.sample', 'config/database.yml'
1725 # end
1826
1927 puts "\n== Preparing database =="
20 system "bin/rake db:setup"
28 system! 'bin/rails db:setup'
2129
2230 puts "\n== Removing old logs and tempfiles =="
23 system "rm -f log/*"
24 system "rm -rf tmp/cache"
31 system! 'bin/rails log:clear tmp:clear'
2532
2633 puts "\n== Restarting application server =="
27 system "touch tmp/restart.txt"
34 system! 'bin/rails restart'
2835 end
0 #!/usr/bin/env ruby
1 require 'fileutils'
2 include FileUtils
3
4 # path to your application root.
5 APP_ROOT = File.expand_path('..', __dir__)
6
7 def system!(*args)
8 system(*args) || abort("\n== Command #{args} failed ==")
9 end
10
11 chdir APP_ROOT do
12 # This script is a way to update your development environment automatically.
13 # Add necessary update steps to this file.
14
15 puts '== Installing dependencies =='
16 system! 'gem install bundler --conservative'
17 system('bundle check') || system!('bundle install')
18
19 # Install JavaScript dependencies if using Yarn
20 # system('bin/yarn')
21
22 puts "\n== Updating database =="
23 system! 'bin/rails db:migrate'
24
25 puts "\n== Removing old logs and tempfiles =="
26 system! 'bin/rails log:clear tmp:clear'
27
28 puts "\n== Restarting application server =="
29 system! 'bin/rails restart'
30 end
0 #!/usr/bin/env ruby
1 APP_ROOT = File.expand_path('..', __dir__)
2 Dir.chdir(APP_ROOT) do
3 begin
4 exec "yarnpkg", *ARGV
5 rescue Errno::ENOENT
6 $stderr.puts "Yarn executable was not detected in the system."
7 $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
8 exit 1
9 end
10 end
0 require File.expand_path('../boot', __FILE__)
0 require_relative 'boot'
11
22 require 'rails/all'
33
77
88 module Foo
99 class Application < Rails::Application
10 # Initialize configuration defaults for originally generated Rails version.
11 config.load_defaults 5.0
12
1013 # Settings in config/environments/* take precedence over those specified here.
11 # Application configuration should go into files in config/initializers
12 # -- all .rb files in that directory are automatically loaded.
13
14 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
15 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
16 # config.time_zone = 'Central Time (US & Canada)'
17
18 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20 # config.i18n.default_locale = :de
21
22 # Do not swallow errors in after_commit/after_rollback callbacks.
23 config.active_record.raise_in_transactional_callbacks = true
14 # Application configuration can go into files in config/initializers
15 # -- all .rb files in that directory are automatically loaded after loading
16 # the framework and any gems in your application.
2417 end
2518 end
0 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
0 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11
22 require 'bundler/setup' # Set up gems listed in the Gemfile.
0 development:
1 adapter: async
2
3 test:
4 adapter: async
5
6 production:
7 adapter: redis
8 url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
9 channel_prefix: foo_production
00 # Load the Rails application.
1 require File.expand_path('../application', __FILE__)
1 require_relative 'application'
22
33 # Initialize the Rails application.
44 Rails.application.initialize!
88 # Do not eager load code on boot.
99 config.eager_load = false
1010
11 # Show full error reports and disable caching.
12 config.consider_all_requests_local = true
13 config.action_controller.perform_caching = false
11 # Show full error reports.
12 config.consider_all_requests_local = true
13
14 # Enable/disable caching. By default caching is disabled.
15 # Run rails dev:cache to toggle caching.
16 if Rails.root.join('tmp', 'caching-dev.txt').exist?
17 config.action_controller.perform_caching = true
18
19 config.cache_store = :memory_store
20 config.public_file_server.headers = {
21 'Cache-Control' => "public, max-age=#{2.days.to_i}"
22 }
23 else
24 config.action_controller.perform_caching = false
25
26 config.cache_store = :null_store
27 end
28
29 # Store uploaded files on the local file system (see config/storage.yml for options)
30 config.active_storage.service = :local
1431
1532 # Don't care if the mailer can't send.
1633 config.action_mailer.raise_delivery_errors = false
34
35 config.action_mailer.perform_caching = false
1736
1837 # Print deprecation notices to the Rails logger.
1938 config.active_support.deprecation = :log
2140 # Raise an error on page load if there are pending migrations.
2241 config.active_record.migration_error = :page_load
2342
43 # Highlight code that triggered database queries in logs.
44 config.active_record.verbose_query_logs = true
45
2446 # Debug mode disables concatenation and preprocessing of assets.
2547 # This option may cause significant delays in view rendering with a large
2648 # number of complex assets.
2749 config.assets.debug = true
2850
29 # Asset digests allow you to set far-future HTTP expiration dates on all assets,
30 # yet still be able to expire them through the digest params.
31 config.assets.digest = true
32
33 # Adds additional error checking when serving assets at runtime.
34 # Checks for improperly declared sprockets dependencies.
35 # Raises helpful error messages.
36 config.assets.raise_runtime_errors = true
51 # Suppress logger output for asset requests.
52 config.assets.quiet = true
3753
3854 # Raises error for missing translations
3955 # config.action_view.raise_on_missing_translations = true
56
57 # Use an evented file watcher to asynchronously detect changes in source code,
58 # routes, locales, etc. This feature depends on the listen gem.
59 config.file_watcher = ActiveSupport::EventedFileUpdateChecker
4060 end
0 Rails.application.configure do
1 # Settings specified here will take precedence over those in config/application.rb.
2
3 # Code is not reloaded between requests.
4 config.cache_classes = true
5
6 # Eager load code on boot. This eager loads most of Rails and
7 # your application in memory, allowing both threaded web servers
8 # and those relying on copy on write to perform better.
9 # Rake tasks automatically ignore this option for performance.
10 config.eager_load = true
11
12 # Full error reports are disabled and caching is turned on.
13 config.consider_all_requests_local = false
14 config.action_controller.perform_caching = true
15
16 # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
17 # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
18 # config.require_master_key = true
19
20 # Disable serving static files from the `/public` folder by default since
21 # Apache or NGINX already handles this.
22 config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
23
24 # Compress JavaScripts and CSS.
25 config.assets.js_compressor = :uglifier
26 # config.assets.css_compressor = :sass
27
28 # Do not fallback to assets pipeline if a precompiled asset is missed.
29 config.assets.compile = false
30
31 # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
32
33 # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 # config.action_controller.asset_host = 'http://assets.example.com'
35
36 # Specifies the header that your server uses for sending files.
37 # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
38 # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
39
40 # Store uploaded files on the local file system (see config/storage.yml for options)
41 config.active_storage.service = :local
42
43 # Mount Action Cable outside main process or domain
44 # config.action_cable.mount_path = nil
45 # config.action_cable.url = 'wss://example.com/cable'
46 # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
47
48 # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 # config.force_ssl = true
50
51 # Use the lowest log level to ensure availability of diagnostic information
52 # when problems arise.
53 config.log_level = :debug
54
55 # Prepend all log lines with the following tags.
56 config.log_tags = [ :request_id ]
57
58 # Use a different cache store in production.
59 # config.cache_store = :mem_cache_store
60
61 # Use a real queuing backend for Active Job (and separate queues per environment)
62 # config.active_job.queue_adapter = :resque
63 # config.active_job.queue_name_prefix = "foo_#{Rails.env}"
64
65 config.action_mailer.perform_caching = false
66
67 # Ignore bad email addresses and do not raise email delivery errors.
68 # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 # config.action_mailer.raise_delivery_errors = false
70
71 # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 # the I18n.default_locale when a translation cannot be found).
73 config.i18n.fallbacks = true
74
75 # Send deprecation notices to registered listeners.
76 config.active_support.deprecation = :notify
77
78 # Use default logging formatter so that PID and timestamp are not suppressed.
79 config.log_formatter = ::Logger::Formatter.new
80
81 # Use a different logger for distributed setups.
82 # require 'syslog/logger'
83 # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
84
85 if ENV["RAILS_LOG_TO_STDOUT"].present?
86 logger = ActiveSupport::Logger.new(STDOUT)
87 logger.formatter = config.log_formatter
88 config.logger = ActiveSupport::TaggedLogging.new(logger)
89 end
90
91 # Do not dump schema after migrations.
92 config.active_record.dump_schema_after_migration = false
93 end
0 Rails.application.configure do
1 # Settings specified here will take precedence over those in config/application.rb.
2
3 # The test environment is used exclusively to run your application's
4 # test suite. You never need to work with it otherwise. Remember that
5 # your test database is "scratch space" for the test suite and is wiped
6 # and recreated between test runs. Don't rely on the data there!
7 config.cache_classes = true
8
9 # Do not eager load code on boot. This avoids loading your whole application
10 # just for the purpose of running a single test. If you are using a tool that
11 # preloads Rails for running tests, you may have to set it to true.
12 config.eager_load = false
13
14 # Configure public file server for tests with Cache-Control for performance.
15 config.public_file_server.enabled = true
16 config.public_file_server.headers = {
17 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
18 }
19
20 # Show full error reports and disable caching.
21 config.consider_all_requests_local = true
22 config.action_controller.perform_caching = false
23
24 # Raise exceptions instead of rendering exception templates.
25 config.action_dispatch.show_exceptions = false
26
27 # Disable request forgery protection in test environment.
28 config.action_controller.allow_forgery_protection = false
29
30 # Store uploaded files on the local file system in a temporary directory
31 config.active_storage.service = :test
32
33 config.action_mailer.perform_caching = false
34
35 # Tell Action Mailer not to deliver emails to the real world.
36 # The :test delivery method accumulates sent emails in the
37 # ActionMailer::Base.deliveries array.
38 config.action_mailer.delivery_method = :test
39
40 # Print deprecation notices to the stderr.
41 config.active_support.deprecation = :stderr
42
43 # Raises error for missing translations
44 # config.action_view.raise_on_missing_translations = true
45 end
0 # Be sure to restart your server when you modify this file.
1
2 # ActiveSupport::Reloader.to_prepare do
3 # ApplicationController.renderer.defaults.merge!(
4 # http_host: 'example.org',
5 # https: false
6 # )
7 # end
22 # Version of your assets, change this if you want to expire all your assets.
33 Rails.application.config.assets.version = '1.0'
44
5 # Add additional assets to the asset load path
5 # Add additional assets to the asset load path.
66 # Rails.application.config.assets.paths << Emoji.images_path
7 # Add Yarn node_modules folder to the asset load path.
8 Rails.application.config.assets.paths << Rails.root.join('node_modules')
79
810 # Precompile additional assets.
9 # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
10 # Rails.application.config.assets.precompile += %w( search.js )
11 # application.js, application.css, and all non-JS/CSS in the app/assets
12 # folder are already added.
13 # Rails.application.config.assets.precompile += %w( admin.js admin.css )
0 # Be sure to restart your server when you modify this file.
1
2 # Define an application-wide content security policy
3 # For further information see the following documentation
4 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
5
6 # Rails.application.config.content_security_policy do |policy|
7 # policy.default_src :self, :https
8 # policy.font_src :self, :https, :data
9 # policy.img_src :self, :https, :data
10 # policy.object_src :none
11 # policy.script_src :self, :https
12 # policy.style_src :self, :https
13
14 # # Specify URI for violation reports
15 # # policy.report_uri "/csp-violation-report-endpoint"
16 # end
17
18 # If you are using UJS then enable automatic nonce generation
19 # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
20
21 # Report CSP violations to a specified URI
22 # For further information see the following documentation:
23 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
24 # Rails.application.config.content_security_policy_report_only = true
00 # Be sure to restart your server when you modify this file.
11
2 # Specify a serializer for the signed and encrypted cookie jars.
3 # Valid options are :json, :marshal, and :hybrid.
24 Rails.application.config.action_dispatch.cookies_serializer = :json
0 # Be sure to restart your server when you modify this file.
1 #
2 # This file contains migration options to ease your Rails 5.2 upgrade.
3 #
4 # Once upgraded flip defaults one by one to migrate to the new default.
5 #
6 # Read the Guide for Upgrading Ruby on Rails for more info on each option.
7
8 # Make Active Record use stable #cache_key alongside new #cache_version method.
9 # This is needed for recyclable cache keys.
10 # Rails.application.config.active_record.cache_versioning = true
11
12 # Use AES-256-GCM authenticated encryption for encrypted cookies.
13 # Also, embed cookie expiry in signed or encrypted cookies for increased security.
14 #
15 # This option is not backwards compatible with earlier Rails versions.
16 # It's best enabled when your entire app is migrated and stable on 5.2.
17 #
18 # Existing cookies will be converted on read then written with the new scheme.
19 # Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
20
21 # Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
22 # instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
23 # Rails.application.config.active_support.use_authenticated_message_encryption = true
24
25 # Add default protection from forgery to ActionController::Base instead of in
26 # ApplicationController.
27 # Rails.application.config.action_controller.default_protect_from_forgery = true
28
29 # Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
30 # 'f' after migrating old data.
31 # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
32
33 # Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
34 # Rails.application.config.active_support.use_sha1_digests = true
35
36 # Make `form_with` generate id attributes for any generated HTML tags.
37 # Rails.application.config.action_view.form_with_generates_ids = true
44
55 # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
66 ActiveSupport.on_load(:action_controller) do
7 wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
7 wrap_parameters format: [:json]
88 end
99
1010 # To enable root element in JSON for ActiveRecord objects.
1111 # ActiveSupport.on_load(:active_record) do
12 # self.include_root_in_json = true
12 # self.include_root_in_json = true
1313 # end
1515 #
1616 # This would use the information in config/locales/es.yml.
1717 #
18 # The following keys must be escaped otherwise they will not be retrieved by
19 # the default I18n backend:
20 #
21 # true, false, on, off, yes, no
22 #
23 # Instead, surround them with single quotes.
24 #
25 # en:
26 # 'true': 'foo'
27 #
1828 # To learn more, please read the Rails Internationalization guide
1929 # available at http://guides.rubyonrails.org/i18n.html.
2030
00 Rails.application.routes.draw do
1 root to: "captcha#index"
2 post "/captchas" => "captcha#create"
1 root to: redirect('/captchas')
2 resources :captchas, only: [:index, :create]
3 resources :v3_captchas, only: [:index, :create] do
4 collection do
5 post :create_multi
6 post :create_with_v2_fallback
7 end
8 end
39 resources :users
410 end
0 test:
1 service: Disk
2 root: <%= Rails.root.join("tmp/storage") %>
3
4 local:
5 service: Disk
6 root: <%= Rails.root.join("storage") %>
7
8 # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
9 # amazon:
10 # service: S3
11 # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
12 # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
13 # region: us-east-1
14 # bucket: your_own_bucket
15
16 # Remember not to checkin your GCS keyfile to a repository
17 # google:
18 # service: GCS
19 # project: your_project
20 # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
21 # bucket: your_own_bucket
22
23 # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
24 # microsoft:
25 # service: AzureStorage
26 # storage_account_name: your_account_name
27 # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
28 # container: your_container_name
29
30 # mirror:
31 # service: Mirror
32 # primary: local
33 # mirrors: [ amazon, google, microsoft ]
0 class AddUser < ActiveRecord::Migration
0 class AddUser < ActiveRecord::Migration[4.2]
11 def change
22 create_table :users do |t|
33 t.string :name
0 # encoding: UTF-8
10 # This file is auto-generated from the current state of the database. Instead
21 # of editing this file, please use the migrations feature of Active Record to
32 # incrementally modify your database, and then regenerate this schema definition.
109 #
1110 # It's strongly recommended that you check this file into your version control system.
1211
13 ActiveRecord::Schema.define(version: 20151226015155) do
12 ActiveRecord::Schema.define(version: 2015_12_26_015155) do
1413
1514 create_table "users", force: :cascade do |t|
1615 t.string "name"
+0
-28
demo/sinatra/Gemfile.lock less more
0 PATH
1 remote: ../..
2 specs:
3 recaptcha (0.6.0)
4 json
5
6 GEM
7 remote: https://rubygems.org/
8 specs:
9 json (1.8.1)
10 rack (1.6.4)
11 rack-protection (1.5.3)
12 rack
13 sinatra (1.4.6)
14 rack (~> 1.4)
15 rack-protection (~> 1.4)
16 tilt (>= 1.3, < 3)
17 tilt (2.0.1)
18
19 PLATFORMS
20 ruby
21
22 DEPENDENCIES
23 recaptcha!
24 sinatra
25
26 BUNDLED WITH
27 1.10.6
77 config.secret_key = '6Le7oRETAAAAAL5a8yOmEdmDi3b2pH7mq5iH1bYK'
88 end
99
10 include Recaptcha::ClientHelper
11 include Recaptcha::Verify
10 include Recaptcha::Adapters::ControllerMethods
11 include Recaptcha::Adapters::ViewMethods
1212
1313 get '/' do
1414 <<-HTML
0 # frozen_string_literal: true
1
2 module Recaptcha
3 module Adapters
4 module ControllerMethods
5 private
6
7 # Your private API can be specified in the +options+ hash or preferably
8 # using the Configuration.
9 def verify_recaptcha(options = {})
10 options = {model: options} unless options.is_a? Hash
11 return true if Recaptcha.skip_env?(options[:env])
12
13 model = options[:model]
14 attribute = options.fetch(:attribute, :base)
15 recaptcha_response = options[:response] || recaptcha_response_token(options[:action])
16
17 begin
18 verified = if Recaptcha.invalid_response?(recaptcha_response)
19 false
20 else
21 unless options[:skip_remote_ip]
22 remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
23 options = options.merge(remote_ip: remoteip.to_s) if remoteip
24 end
25
26 Recaptcha.verify_via_api_call(recaptcha_response, options)
27 end
28
29 if verified
30 flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
31 true
32 else
33 recaptcha_error(
34 model,
35 attribute,
36 options.fetch(:message) { Recaptcha::Helpers.to_error_message(:verification_failed) }
37 )
38 false
39 end
40 rescue Timeout::Error
41 if Recaptcha.configuration.handle_timeouts_gracefully
42 recaptcha_error(
43 model,
44 attribute,
45 options.fetch(:message) { Recaptcha::Helpers.to_error_message(:recaptcha_unreachable) }
46 )
47 false
48 else
49 raise RecaptchaError, 'Recaptcha unreachable.'
50 end
51 rescue StandardError => e
52 raise RecaptchaError, e.message, e.backtrace
53 end
54 end
55
56 def verify_recaptcha!(options = {})
57 verify_recaptcha(options) || raise(VerifyError)
58 end
59
60 def recaptcha_error(model, attribute, message)
61 if model
62 model.errors.add(attribute, message)
63 elsif recaptcha_flash_supported?
64 flash[:recaptcha_error] = message
65 end
66 end
67
68 def recaptcha_flash_supported?
69 request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
70 end
71
72 # Extracts response token from params. params['g-recaptcha-response'] should either be a
73 # string or a hash with the action name(s) as keys. If it is a hash, then `action` is used as
74 # the key.
75 # @return [String] A response token if one was passed in the params; otherwise, `''`
76 def recaptcha_response_token(action = nil)
77 response_param = params['g-recaptcha-response']
78 if response_param&.respond_to?(:to_h) # Includes ActionController::Parameters
79 response_param[action].to_s
80 else
81 response_param.to_s
82 end
83 end
84 end
85 end
86 end
0 # frozen_string_literal: true
1
2 module Recaptcha
3 module Adapters
4 module ViewMethods
5 # Renders a [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) script and (by
6 # default) a hidden input to submit the response token. You can also call the functions
7 # directly if you prefer. You can use
8 # `Recaptcha::Helpers.recaptcha_v3_execute_function_name(action)` to get the name of the
9 # function to call.
10 def recaptcha_v3(options = {})
11 ::Recaptcha::Helpers.recaptcha_v3(options)
12 end
13
14 # Renders a reCAPTCHA [v2 Checkbox](https://developers.google.com/recaptcha/docs/display) widget
15 def recaptcha_tags(options = {})
16 ::Recaptcha::Helpers.recaptcha_tags(options)
17 end
18
19 # Renders a reCAPTCHA v2 [Invisible reCAPTCHA](https://developers.google.com/recaptcha/docs/invisible)
20 def invisible_recaptcha_tags(options = {})
21 ::Recaptcha::Helpers.invisible_recaptcha_tags(options)
22 end
23 end
24 end
25 end
+0
-144
lib/recaptcha/client_helper.rb less more
0 # frozen_string_literal: true
1
2 module Recaptcha
3 module ClientHelper
4 # Your public API can be specified in the +options+ hash or preferably
5 # using the Configuration.
6 def recaptcha_tags(options = {})
7 if options.key?(:stoken)
8 raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
9 end
10 if options.key?(:ssl)
11 raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
12 end
13
14 noscript = options.delete(:noscript)
15
16 html, tag_attributes, fallback_uri = Recaptcha::ClientHelper.recaptcha_components(options)
17 html << %(<div #{tag_attributes}></div>\n)
18
19 if noscript != false
20 html << <<-HTML
21 <noscript>
22 <div>
23 <div style="width: 302px; height: 422px; position: relative;">
24 <div style="width: 302px; height: 422px; position: absolute;">
25 <iframe
26 src="#{fallback_uri}"
27 scrolling="no" name="ReCAPTCHA"
28 style="width: 302px; height: 422px; border-style: none; border: 0;">
29 </iframe>
30 </div>
31 </div>
32 <div style="width: 300px; height: 60px; border-style: none;
33 bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
34 background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
35 <textarea id="g-recaptcha-response" name="g-recaptcha-response"
36 class="g-recaptcha-response"
37 style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
38 margin: 10px 25px; padding: 0px; resize: none;">
39 </textarea>
40 </div>
41 </div>
42 </noscript>
43 HTML
44 end
45
46 html.respond_to?(:html_safe) ? html.html_safe : html
47 end
48
49 # Invisible reCAPTCHA implementation
50 def invisible_recaptcha_tags(options = {})
51 options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge options
52 text = options.delete(:text)
53 html, tag_attributes = Recaptcha::ClientHelper.recaptcha_components(options)
54 html << recaptcha_default_callback(options) if recaptcha_default_callback_required?(options)
55 case options[:ui]
56 when :button
57 html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
58 when :invisible
59 html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
60 else
61 raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
62 end
63 html.respond_to?(:html_safe) ? html.html_safe : html
64 end
65
66 def self.recaptcha_components(options = {})
67 html = ''.dup
68 attributes = {}
69 fallback_uri = ''.dup
70
71 # Since leftover options get passed directly through as tag
72 # attributes, we must unconditionally delete all our options
73 options = options.dup
74 env = options.delete(:env)
75 class_attribute = options.delete(:class)
76 site_key = options.delete(:site_key)
77 hl = options.delete(:hl).to_s
78 nonce = options.delete(:nonce)
79 skip_script = (options.delete(:script) == false)
80 ui = options.delete(:ui)
81
82 data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
83 data_attribute_keys << :tabindex unless ui == :button
84 data_attributes = {}
85 data_attribute_keys.each do |data_attribute|
86 value = options.delete(data_attribute)
87 data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
88 end
89
90 unless Recaptcha::Verify.skip?(env)
91 site_key ||= Recaptcha.configuration.site_key!
92 script_url = Recaptcha.configuration.api_server_url
93 script_url += "?hl=#{hl}" unless hl == ""
94 nonce_attr = " nonce='#{nonce}'" if nonce
95 html << %(<script src="#{script_url}" async defer#{nonce_attr}></script>\n) unless skip_script
96 fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
97 attributes["data-sitekey"] = site_key
98 attributes.merge! data_attributes
99 end
100
101 # Append whatever that's left of options to be attributes on the tag.
102 attributes["class"] = "g-recaptcha #{class_attribute}"
103 tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
104
105 [html, tag_attributes, fallback_uri]
106 end
107
108 private
109
110 def recaptcha_default_callback(options = {})
111 nonce = options[:nonce]
112 nonce_attr = " nonce='#{nonce}'" if nonce
113
114 <<-HTML
115 <script#{nonce_attr}>
116 var invisibleRecaptchaSubmit = function () {
117 var closestForm = function (ele) {
118 var curEle = ele.parentNode;
119 while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
120 curEle = curEle.parentNode;
121 }
122 return curEle.nodeName === 'FORM' ? curEle : null
123 };
124
125 var eles = document.getElementsByClassName('g-recaptcha');
126 if (eles.length > 0) {
127 var form = closestForm(eles[0]);
128 if (form) {
129 form.submit();
130 }
131 }
132 };
133 </script>
134 HTML
135 end
136
137 def recaptcha_default_callback_required?(options)
138 options[:callback] == 'invisibleRecaptchaSubmit' &&
139 !Recaptcha::Verify.skip?(options[:env]) &&
140 options[:script] != false
141 end
142 end
143 end
2929 # end
3030 #
3131 class Configuration
32 attr_accessor :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
32 DEFAULTS = {
33 'server_url' => 'https://www.recaptcha.net/recaptcha/api.js',
34 'verify_url' => 'https://www.recaptcha.net/recaptcha/api/siteverify'
35 }.freeze
36
37 attr_accessor :default_env, :skip_verify_env, :secret_key, :site_key, :proxy, :handle_timeouts_gracefully, :hostname
3338 attr_writer :api_server_url, :verify_url
3439
3540 def initialize #:nodoc:
41 @default_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
3642 @skip_verify_env = %w[test cucumber]
37 @handle_timeouts_gracefully = HANDLE_TIMEOUTS_GRACEFULLY
43 @handle_timeouts_gracefully = true
3844
3945 @secret_key = ENV['RECAPTCHA_SECRET_KEY']
4046 @site_key = ENV['RECAPTCHA_SITE_KEY']
5157 end
5258
5359 def api_server_url
54 @api_server_url || CONFIG.fetch('server_url')
60 @api_server_url || DEFAULTS.fetch('server_url')
5561 end
5662
5763 def verify_url
58 @verify_url || CONFIG.fetch('verify_url')
64 @verify_url || DEFAULTS.fetch('verify_url')
5965 end
6066 end
6167 end
0 # frozen_string_literal: true
1
2 module Recaptcha
3 module Helpers
4 DEFAULT_ERRORS = {
5 recaptcha_unreachable: 'Oops, we failed to validate your reCAPTCHA response. Please try again.',
6 verification_failed: 'reCAPTCHA verification failed, please try again.'
7 }.freeze
8
9 def self.recaptcha_v3(options = {})
10 site_key = options[:site_key] ||= Recaptcha.configuration.site_key!
11 action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required')
12 id = options.delete(:id) || "g-recaptcha-response-" + dasherize_action(action)
13 name = options.delete(:name) || "g-recaptcha-response[#{action}]"
14 options[:render] = site_key
15 options[:script_async] ||= false
16 options[:script_defer] ||= false
17 element = options.delete(:element)
18 element = element == false ? false : :input
19 if element == :input
20 callback = options.delete(:callback) || recaptcha_v3_default_callback_name(action)
21 end
22 options[:class] = "g-recaptcha-response #{options[:class]}"
23
24 html, tag_attributes = components(options)
25 if recaptcha_v3_inline_script?(options)
26 html << recaptcha_v3_inline_script(site_key, action, callback, id, options)
27 end
28 case element
29 when :input
30 html << %(<input type="hidden" name="#{name}" id="#{id}" #{tag_attributes}/>\n)
31 when false
32 # No tag
33 nil
34 else
35 raise(RecaptchaError, "ReCAPTCHA element `#{options[:element]}` is not valid.")
36 end
37 html.respond_to?(:html_safe) ? html.html_safe : html
38 end
39
40 def self.recaptcha_tags(options)
41 if options.key?(:stoken)
42 raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
43 end
44 if options.key?(:ssl)
45 raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
46 end
47
48 noscript = options.delete(:noscript)
49
50 html, tag_attributes, fallback_uri = components(options.dup)
51 html << %(<div #{tag_attributes}></div>\n)
52
53 if noscript != false
54 html << <<-HTML
55 <noscript>
56 <div>
57 <div style="width: 302px; height: 422px; position: relative;">
58 <div style="width: 302px; height: 422px; position: absolute;">
59 <iframe
60 src="#{fallback_uri}"
61 name="ReCAPTCHA"
62 style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
63 </iframe>
64 </div>
65 </div>
66 <div style="width: 300px; height: 60px; border-style: none;
67 bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
68 background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
69 <textarea id="g-recaptcha-response" name="g-recaptcha-response"
70 class="g-recaptcha-response"
71 style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
72 margin: 10px 25px; padding: 0px; resize: none;">
73 </textarea>
74 </div>
75 </div>
76 </noscript>
77 HTML
78 end
79
80 html.respond_to?(:html_safe) ? html.html_safe : html
81 end
82
83 def self.invisible_recaptcha_tags(custom)
84 options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge(custom)
85 text = options.delete(:text)
86 html, tag_attributes = components(options.dup)
87 html << default_callback(options) if default_callback_required?(options)
88
89 case options[:ui]
90 when :button
91 html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
92 when :invisible
93 html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
94 when :input
95 html << %(<input type="submit" #{tag_attributes} value="#{text}"/>\n)
96 else
97 raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
98 end
99 html.respond_to?(:html_safe) ? html.html_safe : html
100 end
101
102 def self.to_error_message(key)
103 default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown reCAPTCHA error - #{key}" }
104 to_message("recaptcha.errors.#{key}", default)
105 end
106
107 if defined?(I18n)
108 def self.to_message(key, default)
109 I18n.translate(key, default: default)
110 end
111 else
112 def self.to_message(_key, default)
113 default
114 end
115 end
116
117 private_class_method def self.components(options)
118 html = +''
119 attributes = {}
120 fallback_uri = +''
121
122 options = options.dup
123 env = options.delete(:env)
124 class_attribute = options.delete(:class)
125 site_key = options.delete(:site_key)
126 hl = options.delete(:hl)
127 onload = options.delete(:onload)
128 render = options.delete(:render)
129 script_async = options.delete(:script_async)
130 script_defer = options.delete(:script_defer)
131 nonce = options.delete(:nonce)
132 skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false)
133 ui = options.delete(:ui)
134
135 data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
136 data_attribute_keys << :tabindex unless ui == :button
137 data_attributes = {}
138 data_attribute_keys.each do |data_attribute|
139 value = options.delete(data_attribute)
140 data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
141 end
142
143 unless Recaptcha.skip_env?(env)
144 site_key ||= Recaptcha.configuration.site_key!
145 script_url = Recaptcha.configuration.api_server_url
146 query_params = hash_to_query(
147 hl: hl,
148 onload: onload,
149 render: render
150 )
151 script_url += "?#{query_params}" unless query_params.empty?
152 async_attr = "async" if script_async != false
153 defer_attr = "defer" if script_defer != false
154 nonce_attr = " nonce='#{nonce}'" if nonce
155 html << %(<script src="#{script_url}" #{async_attr} #{defer_attr} #{nonce_attr}></script>\n) unless skip_script
156 fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
157 attributes["data-sitekey"] = site_key
158 attributes.merge! data_attributes
159 end
160
161 # The remaining options will be added as attributes on the tag.
162 attributes["class"] = "g-recaptcha #{class_attribute}"
163 tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")
164
165 [html, tag_attributes, fallback_uri]
166 end
167
168 # v3
169
170 # Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and
171 # calls the `callback` with the resulting response token.
172 private_class_method def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
173 nonce = options[:nonce]
174 nonce_attr = " nonce='#{nonce}'" if nonce
175
176 <<-HTML
177 <script#{nonce_attr}>
178 // Define function so that we can call it again later if we need to reset it
179 // This executes reCAPTCHA and then calls our callback.
180 function #{recaptcha_v3_execute_function_name(action)}() {
181 grecaptcha.ready(function() {
182 grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
183 //console.log('#{id}', token)
184 #{callback}('#{id}', token)
185 });
186 });
187 };
188 // Invoke immediately
189 #{recaptcha_v3_execute_function_name(action)}()
190
191 // Async variant so you can await this function from another async function (no need for
192 // an explicit callback function then!)
193 // Returns a Promise that resolves with the response token.
194 async function #{recaptcha_v3_async_execute_function_name(action)}() {
195 return new Promise((resolve, reject) => {
196 grecaptcha.ready(async function() {
197 resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'}))
198 });
199 })
200 };
201
202 #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
203 </script>
204 HTML
205 end
206
207 private_class_method def self.recaptcha_v3_inline_script?(options)
208 !Recaptcha.skip_env?(options[:env]) &&
209 options[:script] != false &&
210 options[:inline_script] != false
211 end
212
213 private_class_method def self.recaptcha_v3_define_default_callback(callback)
214 <<-HTML
215 var #{callback} = function(id, token) {
216 var element = document.getElementById(id);
217 element.value = token;
218 }
219 HTML
220 end
221
222 # Returns true if we should be adding the default callback.
223 # That is, if the given callback name is the default callback name (for the given action) and we
224 # are not skipping inline scripts for any reason.
225 private_class_method def self.recaptcha_v3_define_default_callback?(callback, action, options)
226 callback == recaptcha_v3_default_callback_name(action) &&
227 recaptcha_v3_inline_script?(options)
228 end
229
230 # Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls
231 # grecaptcha.execute). You can call it again later to reset it.
232 def self.recaptcha_v3_execute_function_name(action)
233 "executeRecaptchaFor#{sanitize_action_for_js(action)}"
234 end
235
236 # Returns the name of an async JavaScript function that executes the reCAPTCHA code.
237 def self.recaptcha_v3_async_execute_function_name(action)
238 "#{recaptcha_v3_execute_function_name(action)}Async"
239 end
240
241 def self.recaptcha_v3_default_callback_name(action)
242 "setInputWithRecaptchaResponseTokenFor#{sanitize_action_for_js(action)}"
243 end
244
245 # v2
246
247 private_class_method def self.default_callback(options = {})
248 nonce = options[:nonce]
249 nonce_attr = " nonce='#{nonce}'" if nonce
250
251 <<-HTML
252 <script#{nonce_attr}>
253 var invisibleRecaptchaSubmit = function () {
254 var closestForm = function (ele) {
255 var curEle = ele.parentNode;
256 while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
257 curEle = curEle.parentNode;
258 }
259 return curEle.nodeName === 'FORM' ? curEle : null
260 };
261
262 var eles = document.getElementsByClassName('g-recaptcha');
263 if (eles.length > 0) {
264 var form = closestForm(eles[0]);
265 if (form) {
266 form.submit();
267 }
268 }
269 };
270 </script>
271 HTML
272 end
273
274 private_class_method def self.default_callback_required?(options)
275 options[:callback] == 'invisibleRecaptchaSubmit' &&
276 !Recaptcha.skip_env?(options[:env]) &&
277 options[:script] != false &&
278 options[:inline_script] != false
279 end
280
281 # Returns a camelized string that is safe for use in a JavaScript variable/function name.
282 # sanitize_action_for_js('my/action') => 'MyAction'
283 private_class_method def self.sanitize_action_for_js(action)
284 action.to_s.gsub(/\W/, '_').split(/\/|_/).map(&:capitalize).join
285 end
286
287 # Returns a dasherized string that is safe for use as an HTML ID
288 # dasherize_action('my/action') => 'my-action'
289 private_class_method def self.dasherize_action(action)
290 action.to_s.gsub(/\W/, '-').tr('_', '-')
291 end
292
293 private_class_method def self.hash_to_query(hash)
294 hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
295 end
296 end
297 end
00 # frozen_string_literal: true
1
12 # deprecated, but let's not blow everyone up
23 require 'recaptcha'
22 module Recaptcha
33 class Railtie < Rails::Railtie
44 ActiveSupport.on_load(:action_view) do
5 require 'recaptcha/client_helper'
6 include Recaptcha::ClientHelper
5 include Recaptcha::Adapters::ViewMethods
76 end
87
98 ActiveSupport.on_load(:action_controller) do
10 require 'recaptcha/verify'
11 include Recaptcha::Verify
9 include Recaptcha::Adapters::ControllerMethods
10 end
11
12 initializer 'recaptcha' do |app|
13 Recaptcha::Railtie.instance_eval do
14 pattern = pattern_from app.config.i18n.available_locales
15
16 add("rails/locales/#{pattern}.yml")
17 end
18 end
19
20 class << self
21 protected
22
23 def add(pattern)
24 files = Dir[File.join(File.dirname(__FILE__), '../..', pattern)]
25 I18n.load_path.concat(files)
26 end
27
28 def pattern_from(args)
29 array = Array(args || [])
30 array.blank? ? '*' : "{#{array.join ','}}"
31 end
1232 end
1333 end
1434 end
+0
-107
lib/recaptcha/verify.rb less more
0 # frozen_string_literal: true
1
2 require 'json'
3
4 module Recaptcha
5 module Verify
6 # Your private API can be specified in the +options+ hash or preferably
7 # using the Configuration.
8 def verify_recaptcha(options = {})
9 options = {model: options} unless options.is_a? Hash
10 return true if Recaptcha::Verify.skip?(options[:env])
11
12 model = options[:model]
13 attribute = options[:attribute] || :base
14 recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
15
16 begin
17 verified = if recaptcha_response.empty?
18 false
19 else
20 recaptcha_verify_via_api_call(request, recaptcha_response, options)
21 end
22
23 if verified
24 flash.delete(:recaptcha_error) if recaptcha_flash_supported? && !model
25 true
26 else
27 recaptcha_error(
28 model,
29 attribute,
30 options[:message],
31 "recaptcha.errors.verification_failed",
32 "reCAPTCHA verification failed, please try again."
33 )
34 false
35 end
36 rescue Timeout::Error
37 if Recaptcha.configuration.handle_timeouts_gracefully
38 recaptcha_error(
39 model,
40 attribute,
41 options[:message],
42 "recaptcha.errors.recaptcha_unreachable",
43 "Oops, we failed to validate your reCAPTCHA response. Please try again."
44 )
45 false
46 else
47 raise RecaptchaError, "Recaptcha unreachable."
48 end
49 rescue StandardError => e
50 raise RecaptchaError, e.message, e.backtrace
51 end
52 end
53
54 def verify_recaptcha!(options = {})
55 verify_recaptcha(options) || raise(VerifyError)
56 end
57
58 def self.skip?(env)
59 env ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || (Rails.env if defined? Rails.env)
60 Recaptcha.configuration.skip_verify_env.include? env
61 end
62
63 private
64
65 def recaptcha_verify_via_api_call(request, recaptcha_response, options)
66 secret_key = options[:secret_key] || Recaptcha.configuration.secret_key!
67
68 verify_hash = {
69 "secret" => secret_key,
70 "response" => recaptcha_response
71 }
72
73 unless options[:skip_remote_ip]
74 remoteip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
75 verify_hash["remoteip"] = remoteip.to_s
76 end
77
78 reply = JSON.parse(Recaptcha.get(verify_hash, options))
79 reply['success'].to_s == "true" &&
80 recaptcha_hostname_valid?(reply['hostname'], options[:hostname])
81 end
82
83 def recaptcha_hostname_valid?(hostname, validation)
84 validation ||= Recaptcha.configuration.hostname
85
86 case validation
87 when nil, FalseClass then true
88 when String then validation == hostname
89 else validation.call(hostname)
90 end
91 end
92
93 def recaptcha_error(model, attribute, message, key, default)
94 message ||= Recaptcha.i18n(key, default)
95 if model
96 model.errors.add attribute, message
97 else
98 flash[:recaptcha_error] = message if recaptcha_flash_supported?
99 end
100 end
101
102 def recaptcha_flash_supported?
103 request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
104 end
105 end
106 end
00 # frozen_string_literal: true
11
22 module Recaptcha
3 VERSION = '4.11.1'
3 VERSION = '5.2.1'
44 end
00 # frozen_string_literal: true
11
2 require 'json'
3 require 'net/http'
4 require 'uri'
5
26 require 'recaptcha/configuration'
3 require 'uri'
4 require 'net/http'
5
7 require 'recaptcha/helpers'
8 require 'recaptcha/adapters/controller_methods'
9 require 'recaptcha/adapters/view_methods'
610 if defined?(Rails)
711 require 'recaptcha/railtie'
8 else
9 require 'recaptcha/client_helper'
10 require 'recaptcha/verify'
1112 end
1213
1314 module Recaptcha
14 CONFIG = {
15 'server_url' => 'https://www.google.com/recaptcha/api.js',
16 'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
17 }.freeze
15 DEFAULT_TIMEOUT = 3
16 RESPONSE_LIMIT = 4000
1817
19 USE_SSL_BY_DEFAULT = false
20 HANDLE_TIMEOUTS_GRACEFULLY = true
21 DEFAULT_TIMEOUT = 3
18 class RecaptchaError < StandardError
19 end
20
21 class VerifyError < RecaptchaError
22 end
2223
2324 # Gives access to the current Configuration.
2425 def self.configuration
4950 original_config.each { |key, value| configuration.send("#{key}=", value) }
5051 end
5152
52 def self.get(verify_hash, options)
53 http = if Recaptcha.configuration.proxy
54 proxy_server = URI.parse(Recaptcha.configuration.proxy)
53 def self.skip_env?(env)
54 configuration.skip_verify_env.include?(env || configuration.default_env)
55 end
56
57 def self.invalid_response?(resp)
58 resp.empty? || resp.length > RESPONSE_LIMIT
59 end
60
61 def self.verify_via_api_call(response, options)
62 secret_key = options.fetch(:secret_key) { configuration.secret_key! }
63 verify_hash = { 'secret' => secret_key, 'response' => response }
64 verify_hash['remoteip'] = options[:remote_ip] if options.key?(:remote_ip)
65
66 reply = api_verification(verify_hash, timeout: options[:timeout])
67 reply['success'].to_s == 'true' &&
68 hostname_valid?(reply['hostname'], options[:hostname]) &&
69 action_valid?(reply['action'], options[:action]) &&
70 score_above_threshold?(reply['score'], options[:minimum_score])
71 end
72
73 def self.hostname_valid?(hostname, validation)
74 validation ||= configuration.hostname
75
76 case validation
77 when nil, FalseClass then true
78 when String then validation == hostname
79 else validation.call(hostname)
80 end
81 end
82
83 def self.action_valid?(action, expected_action)
84 case expected_action
85 when nil, FalseClass then true
86 else action == expected_action
87 end
88 end
89
90 # Returns true iff score is greater or equal to (>=) minimum_score, or if no minimum_score was specified
91 def self.score_above_threshold?(score, minimum_score)
92 return true if minimum_score.nil?
93 return false if score.nil?
94
95 case minimum_score
96 when nil, FalseClass then true
97 else score >= minimum_score
98 end
99 end
100
101 def self.api_verification(verify_hash, timeout: nil)
102 timeout ||= DEFAULT_TIMEOUT
103 http = if configuration.proxy
104 proxy_server = URI.parse(configuration.proxy)
55105 Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
56106 else
57107 Net::HTTP
58108 end
59109 query = URI.encode_www_form(verify_hash)
60 uri = URI.parse(Recaptcha.configuration.verify_url + '?' + query)
110 uri = URI.parse(configuration.verify_url + '?' + query)
61111 http_instance = http.new(uri.host, uri.port)
62 http_instance.read_timeout = http_instance.open_timeout = options[:timeout] || DEFAULT_TIMEOUT
112 http_instance.read_timeout = http_instance.open_timeout = timeout
63113 http_instance.use_ssl = true if uri.port == 443
64114 request = Net::HTTP::Get.new(uri.request_uri)
65 http_instance.request(request).body
66 end
67
68 def self.i18n(key, default)
69 if defined?(I18n)
70 I18n.translate(key, default: default)
71 else
72 default
73 end
74 end
75
76 class RecaptchaError < StandardError
77 end
78
79 class VerifyError < RecaptchaError
115 JSON.parse(http_instance.request(request).body)
80116 end
81117 end
0 en:
1 recaptcha:
2 errors:
3 verification_failed: reCAPTCHA verification failed, please try again.
4 recaptcha_unreachable: Oops, we failed to validate your reCAPTCHA response. Please try again.
1111 s.license = "MIT"
1212 s.required_ruby_version = '>= 2.3.0'
1313
14 s.files = `git ls-files lib README.md CHANGELOG.md LICENSE`.split("\n")
14 s.files = `git ls-files lib rails README.md CHANGELOG.md LICENSE`.split("\n")
1515
1616 s.add_runtime_dependency "json"
1717 s.add_development_dependency "mocha"
1818 s.add_development_dependency "rake"
19 s.add_development_dependency "activesupport"
2019 s.add_development_dependency "i18n"
2120 s.add_development_dependency "maxitest"
2221 s.add_development_dependency "pry-byebug"
2322 s.add_development_dependency "bump"
2423 s.add_development_dependency "webmock"
2524 s.add_development_dependency "rubocop"
25
26 s.metadata = { "source_code_uri" => "https://github.com/ambethia/recaptcha" }
2627 end
00 require_relative 'helper'
1 require 'active_support/core_ext/object/blank'
2 require 'active_support/core_ext/hash'
3
4 describe Recaptcha::ClientHelper do
5 include Recaptcha::ClientHelper
1
2 describe 'View helpers' do
3 include Recaptcha::Adapters::ViewMethods
64
75 it "uses ssl" do
86 recaptcha_tags.must_include "\"#{Recaptcha.configuration.api_server_url}\""
5755
5856 it "adds :hl option to the url" do
5957 html = recaptcha_tags(hl: 'en')
60 html.must_include("?hl=en")
58 html.must_include("hl=en")
6159
6260 html = recaptcha_tags(hl: 'ru')
63 html.wont_include("?hl=en")
64 html.must_include("?hl=ru")
61 html.wont_include("hl=en")
62 html.must_include("hl=ru")
6563
6664 html = recaptcha_tags
67 html.wont_include("?hl=")
65 html.wont_include("hl=")
66 end
67
68 it "adds :onload option to the url" do
69 html = recaptcha_tags(onload: 'foobar')
70 html.must_include("onload=foobar")
71
72 html = recaptcha_tags(onload: 'anotherFoobar')
73 html.wont_include("onload=foobar")
74 html.must_include("onload=anotherFoobar")
75
76 html = recaptcha_tags
77 html.wont_include("onload=")
78 end
79
80 it "adds :render option to the url" do
81 html = recaptcha_tags(render: 'onload')
82 html.must_include("render=onload")
83
84 html = recaptcha_tags(render: 'explicit')
85 html.wont_include("render=onload")
86 html.must_include("render=explicit")
87
88 html = recaptcha_tags
89 html.wont_include("render=")
90 end
91
92 it "adds query params to the url" do
93 html = recaptcha_tags(hl: 'en', onload: 'foobar')
94 html.must_include("?")
95 html.must_include("hl=en")
96 html.must_include("&")
97 html.must_include("onload=foobar")
6898 end
6999
70100 it "includes the site key in the button attributes" do
71101 html = invisible_recaptcha_tags
72102 html.must_include(" data-sitekey=\"#{Recaptcha.configuration.site_key}\"")
103 end
104
105 it "lets you override the site_key from configuration via options hash" do
106 html = invisible_recaptcha_tags(site_key: 'different_key')
107 html.must_include(" data-sitekey=\"different_key\"")
73108 end
74109
75110 it "dasherizes the expired_callback attribute name" do
153188 html.wont_include("<button")
154189 end
155190
191 it "renders an input element with supplied text if UI is input" do
192 html = invisible_recaptcha_tags(ui: :input, text: 'Send')
193 html.must_include("<input type=\"submit\"")
194 html.must_include("value=\"Send\"/>")
195 html.wont_include("<button")
196 end
197
156198 it "raises an error on an invalid ui option" do
157199 assert_raises Recaptcha::RecaptchaError do
158200 invisible_recaptcha_tags(ui: :foo)
159201 end
160202 end
161203 end
204
205 describe "v3 recaptcha" do
206 it "renders input" do
207 html = recaptcha_v3 action: :foo
208 html.must_include('<input type="hidden" name="g-recaptcha-response[foo]" id="g-recaptcha-response-foo" data-sitekey="0000000000000000000000000000000000000000" class="g-recaptcha g-recaptcha-response "/>')
209 end
210
211 it "does not have obsole closing script tag" do
212 html = recaptcha_v3 action: :foo
213 assert html.scan(/script/).length.even?
214 end
215 end
162216 end
22 describe Recaptcha::Configuration do
33 describe "#api_server_url" do
44 it "serves the default" do
5 Recaptcha.configuration.api_server_url.must_equal "https://www.google.com/recaptcha/api.js"
5 Recaptcha.configuration.api_server_url.must_equal "https://www.recaptcha.net/recaptcha/api.js"
66 end
77
88 describe "when api_server_url is overwritten" do
1717
1818 describe "#verify_url" do
1919 it "serves the default" do
20 Recaptcha.configuration.verify_url.must_equal "https://www.google.com/recaptcha/api/siteverify"
20 Recaptcha.configuration.verify_url.must_equal "https://www.recaptcha.net/recaptcha/api/siteverify"
2121 end
2222
2323 describe "when api_server_url is overwritten" do
0 # set default_env to nil
1 ENV.delete('RAILS_ENV')
2 ENV.delete('RACK_ENV')
3
04 require 'bundler/setup'
15 require 'maxitest/autorun'
26 require 'mocha/setup'
37 require 'webmock/minitest'
8 require 'byebug'
49 require 'cgi'
10 require 'i18n'
511 require 'recaptcha'
6 require 'i18n'
7
8 ENV.delete('RAILS_ENV')
9 ENV.delete('RACK_ENV')
1012
1113 I18n.enforce_available_locales = false
1214
00 require_relative 'helper'
11
2 describe Recaptcha::Verify do
2 describe 'controller helpers' do
33 before do
44 @controller = TestController.new
55 @controller.request = stub(remote_ip: "1.1.1.1", format: :html)
7878 secret_key = Recaptcha.configuration.secret_key
7979 stub_request(
8080 :get,
81 "https://www.google.com/recaptcha/api/siteverify?response=string&secret=#{secret_key}"
81 "https://www.recaptcha.net/recaptcha/api/siteverify?response=string&secret=#{secret_key}"
8282 ).to_return(body: '{"success":true}')
8383
8484 assert @controller.verify_recaptcha(skip_remote_ip: true)
171171 assert_equal "reCAPTCHA verification failed, please try again.", @controller.flash[:recaptcha_error]
172172 end
173173
174 it "does not verify via http call when response length exceeds G_RESPONSE_LIMIT" do
175 # this returns a 400 or 413 instead of a 200 response with error code
176 # typical response length is less than 400 characters
177 str = "a" * 4001
178 @controller.params = { 'g-recaptcha-response' => "#{str}"}
179 assert_not_requested :get, %r{\.google\.com}
180 assert_equal false, @controller.verify_recaptcha
181 assert_equal "reCAPTCHA verification failed, please try again.", @controller.flash[:recaptcha_error]
182 end
183
174184 describe ':hostname' do
175185 let(:hostname) { 'fake.hostname.com' }
176186
239249 end
240250 end
241251 end
252
253 describe 'action_valid?' do
254 let(:default_response_hash) { {
255 success: true,
256 action: 'homepage',
257 } }
258
259 before do
260 expect_http_post.to_return(body: success_body)
261 end
262
263 it "fails when action from response does not match expected action" do
264 expect_http_post.to_return(body: success_body(action: "not_homepage"))
265
266 refute verify_recaptcha(action: 'homepage')
267 assert_flash_error
268 end
269
270 it "passes with string that matches" do
271 assert verify_recaptcha(action: 'homepage')
272 assert_nil @controller.flash[:recaptcha_error]
273 end
274
275 it "passes with nil" do
276 assert verify_recaptcha(action: nil)
277 assert_nil @controller.flash[:recaptcha_error]
278 end
279
280 it "passes with false" do
281 assert verify_recaptcha(action: false)
282 assert_nil @controller.flash[:recaptcha_error]
283 end
284 end
285
286 describe 'score_above_threshold?' do
287 let(:default_response_hash) { {
288 success: true,
289 action: 'homepage',
290 } }
291
292 before do
293 expect_http_post.to_return(body: success_body(score: 0.4))
294 end
295
296 it "fails when score is below minimum_score" do
297 refute verify_recaptcha(minimum_score: 0.5)
298 assert_flash_error
299 end
300
301 it "fails when response doesn't include a score" do
302 expect_http_post.to_return(body: success_body())
303 refute verify_recaptcha(minimum_score: 0.4)
304 assert_flash_error
305 end
306
307 it "passes with score exactly at minimum_score" do
308 assert verify_recaptcha(minimum_score: 0.4)
309 assert_nil @controller.flash[:recaptcha_error]
310 end
311
312 it "passes when minimum_score not specified or nil" do
313 assert verify_recaptcha()
314 assert_nil @controller.flash[:recaptcha_error]
315 end
316
317 it "passes with false" do
318 assert verify_recaptcha(minimum_score: false)
319 assert_nil @controller.flash[:recaptcha_error]
320 end
321 end
242322 end
243323
244324 private
245325
246326 class TestController
247 include Recaptcha::Verify
327 include Recaptcha::Adapters::ControllerMethods
328
248329 attr_accessor :request, :params, :flash
249330
250331 def initialize
251332 @flash = {}
252333 end
334
335 public :verify_recaptcha
336 public :verify_recaptcha!
253337 end
254338
255339 def expect_http_post(secret_key: Recaptcha.configuration.secret_key)
256340 stub_request(
257341 :get,
258 "https://www.google.com/recaptcha/api/siteverify?remoteip=1.1.1.1&response=string&secret=#{secret_key}"
342 "https://www.recaptcha.net/recaptcha/api/siteverify?remoteip=1.1.1.1&response=string&secret=#{secret_key}"
259343 )
260344 end
345
346 def success_body(other = {})
347 default_response_hash.
348 merge(other).
349 to_json
350 end
351
352 def error_body(error_code = "bad-news")
353 { "error-codes" => [error_code] }.
354 to_json
355 end
356
357 def verify_recaptcha(options = {})
358 options[:action] = 'homepage' unless options.key?(:action)
359 @controller.verify_recaptcha(options)
360 end
361
362 def assert_flash_error
363 assert_equal "reCAPTCHA verification failed, please try again.", @controller.flash[:recaptcha_error]
364 end
261365 end