cloudflare-turnstile-rails 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +41 -9
- data/lib/cloudflare/turnstile/rails/configuration.rb +5 -1
- data/lib/cloudflare/turnstile/rails/constants/cloudflare.rb +3 -0
- data/lib/cloudflare/turnstile/rails/controller_methods.rb +10 -2
- data/lib/cloudflare/turnstile/rails/verification.rb +35 -4
- data/lib/cloudflare/turnstile/rails/version.rb +1 -1
- data/lib/generators/cloudflare_turnstile/templates/cloudflare_turnstile.rb +6 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7bdf250ea2814264e89921898dcc1c38b968596fe59e9423914b2d245ee8607c
|
|
4
|
+
data.tar.gz: e72026fff680a00cccfdb6134fbe9dafe0658abc8c135b0c5a4bc73404a1a104
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dfe05337cc155cd855b9297c8512d1c173069862a33f3061b685e41db3ec4ac7c20e86466d2c90d82d10795ccb0560b64177a344a5098885506efc53f614872d
|
|
7
|
+
data.tar.gz: aa3d5dacc7e140dedf475881f61522480b30c87e39bf1b228999ed5244fbe33cf1f02af3ffa6575bc2e0a09acb78221375b2fe82863dfcf13274398bac631d3d
|
data/README.md
CHANGED
|
@@ -5,18 +5,21 @@
|
|
|
5
5
|
[](https://github.com/vkononov/cloudflare-turnstile-rails/blob/main/Appraisals)
|
|
6
6
|
[](https://github.com/vkononov/cloudflare-turnstile-rails/actions/workflows/test.yml)
|
|
7
7
|
[](https://github.com/vkononov/cloudflare-turnstile-rails/actions/workflows/lint.yml)
|
|
8
|
-
[](https://opensource.org/
|
|
8
|
+
[](https://opensource.org/license/MIT)
|
|
9
9
|
|
|
10
10
|
Cloudflare Turnstile gem for Ruby on Rails with built-in Turbo and Turbolinks support and CSP compliance.
|
|
11
11
|
|
|
12
12
|
Supports **Rails 5.0 → latest** and **Ruby 2.6 → latest**, with the full Rails/Ruby matrix tested daily in CI.
|
|
13
13
|
|
|
14
|
+
> [!TIP]
|
|
15
|
+
> **Using Devise?** [devise-cloudflare-turnstile](https://github.com/vkononov/devise-cloudflare-turnstile) protects every Devise form (sign in, sign up, password reset, and more) with no changes to your Devise views or controllers. It is built on this gem and shares its configuration, so you can use both to protect Devise and non-Devise forms in the same app.
|
|
16
|
+
|
|
14
17
|
[](https://www.buymeacoffee.com/vkononov)
|
|
15
18
|
|
|
16
19
|
## Features
|
|
17
20
|
|
|
18
21
|
* **One‑line integration**: `<%= cloudflare_turnstile_tag %>` in views, `valid_turnstile?(model:)` in controllers — no extra wiring.
|
|
19
|
-
* **Turbo & Turbo Streams aware**: Automatically re‑initializes widgets on `turbo:
|
|
22
|
+
* **Turbo & Turbo Streams aware**: Automatically re‑initializes widgets on `turbo:render` and `turbo:before-stream-render`.
|
|
20
23
|
* **Legacy Turbolinks support**: Includes a helper for Turbolinks to handle remote form submissions with validation errors.
|
|
21
24
|
* **CSP nonce support**: Honours Rails' `content_security_policy_nonce` for secure inline scripts.
|
|
22
25
|
* **Rails Engine & Asset pipeline**: Ships a precompiled JS helper via Railtie — no manual asset setup.
|
|
@@ -154,6 +157,22 @@ Supports **Rails 5.0 → latest** and **Ruby 2.6 → latest**, with the full Rai
|
|
|
154
157
|
end
|
|
155
158
|
```
|
|
156
159
|
|
|
160
|
+
The automatic message is a regular flash, so it survives exactly one redirect. If you **re-render the form** instead of redirecting, pass `flash: :now` to scope the message to that render, so it doesn't also appear on the next page the visitor opens:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
def create
|
|
164
|
+
if valid_turnstile?(flash: :now)
|
|
165
|
+
# Passed: no message is set either way
|
|
166
|
+
redirect_to dashboard_path, notice: 'Success!'
|
|
167
|
+
else
|
|
168
|
+
# Failed: flash.now[:alert] is automatically set
|
|
169
|
+
render :new, status: :unprocessable_entity
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Passing a `model` is the other option for a re-rendered form: the failure goes to `model.errors` and no flash is set at all.
|
|
175
|
+
|
|
157
176
|
* You may also pass additional **siteverify** parameters (e.g., `secret`, `response`, `remoteip`, `idempotency_key`) supported by Cloudflare's API:
|
|
158
177
|
[Cloudflare Server-Side Validation Parameters](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#required-parameters)
|
|
159
178
|
|
|
@@ -168,6 +187,16 @@ Supports **Rails 5.0 → latest** and **Ruby 2.6 → latest**, with the full Rai
|
|
|
168
187
|
end
|
|
169
188
|
```
|
|
170
189
|
|
|
190
|
+
* Requests to Cloudflare time out after 5 seconds to connect, read, or write, so a slow Cloudflare can't tie up your app's request threads for long. A timeout raises `Cloudflare::Turnstile::Rails::ConfigurationError`. To change the limits:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
Cloudflare::Turnstile::Rails.configure do |config|
|
|
194
|
+
config.open_timeout = 5 # connecting to siteverify
|
|
195
|
+
config.read_timeout = 5 # reading the response
|
|
196
|
+
config.write_timeout = 5 # sending the verification POST
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
171
200
|
#### Advanced Validation
|
|
172
201
|
|
|
173
202
|
* To inspect the entire verification payload, use `verify_turnstile`. It returns a `VerificationResponse` object with detailed information:
|
|
@@ -187,6 +216,8 @@ Supports **Rails 5.0 → latest** and **Ruby 2.6 → latest**, with the full Rai
|
|
|
187
216
|
end
|
|
188
217
|
```
|
|
189
218
|
|
|
219
|
+
* A missing, malformed, or oversized token fails immediately with `missing-input-response` or `invalid-input-response`, without a request to Cloudflare. The `VerificationResponse` for these failures only has `success` and `error-codes`; it doesn't carry `hostname`, `challenge_ts`, or the other fields a real siteverify call returns.
|
|
220
|
+
|
|
190
221
|
* The `VerificationResponse` object contains the raw response from Cloudflare:
|
|
191
222
|
|
|
192
223
|
```ruby
|
|
@@ -302,9 +333,9 @@ Cloudflare provides dummy sitekeys and secret keys for development and testing.
|
|
|
302
333
|
| Sitekey | Description | Visibility |
|
|
303
334
|
|----------------------------|---------------------------------|------------|
|
|
304
335
|
| `1x00000000000000000000AA` | Always passes | visible |
|
|
305
|
-
| `2x00000000000000000000AB` | Always
|
|
336
|
+
| `2x00000000000000000000AB` | Always fails | visible |
|
|
306
337
|
| `1x00000000000000000000BB` | Always passes | invisible |
|
|
307
|
-
| `2x00000000000000000000BB` | Always
|
|
338
|
+
| `2x00000000000000000000BB` | Always fails | invisible |
|
|
308
339
|
| `3x00000000000000000000FF` | Forces an interactive challenge | visible |
|
|
309
340
|
|
|
310
341
|
### Dummy Secret Keys
|
|
@@ -393,10 +424,11 @@ If you run into any issues after upgrading Rails, please [open an issue](https:/
|
|
|
393
424
|
|
|
394
425
|
### Setup
|
|
395
426
|
|
|
396
|
-
Install dependencies,
|
|
427
|
+
Install the Ruby dependencies, then the JavaScript dependencies used by the ESLint step:
|
|
397
428
|
|
|
398
429
|
```bash
|
|
399
430
|
bin/setup
|
|
431
|
+
npm install
|
|
400
432
|
```
|
|
401
433
|
|
|
402
434
|
### Running the Test Suite
|
|
@@ -414,13 +446,13 @@ bundle exec appraisal rake test
|
|
|
414
446
|
|
|
415
447
|
### Code Linting
|
|
416
448
|
|
|
417
|
-
|
|
449
|
+
Run RuboCop and ESLint together (autocorrecting by default, or pass `--no-fix` to only report):
|
|
418
450
|
|
|
419
451
|
```bash
|
|
420
|
-
|
|
452
|
+
bin/lint
|
|
421
453
|
```
|
|
422
454
|
|
|
423
|
-
> **CI Note:** We run
|
|
455
|
+
> **CI Note:** We run `bin/lint --no-fix` via [.github/workflows/lint.yml](https://github.com/vkononov/cloudflare-turnstile-rails/blob/main/.github/workflows/lint.yml) on the latest Ruby only.
|
|
424
456
|
|
|
425
457
|
### Generating Rails Apps Locally
|
|
426
458
|
|
|
@@ -461,4 +493,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/vkonon
|
|
|
461
493
|
|
|
462
494
|
## License
|
|
463
495
|
|
|
464
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/
|
|
496
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/MIT).
|
|
@@ -3,7 +3,8 @@ module Cloudflare
|
|
|
3
3
|
module Rails
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_writer :script_url
|
|
6
|
-
attr_accessor :site_key, :secret_key, :render, :onload, :default_data, :auto_populate_response_in_test_env
|
|
6
|
+
attr_accessor :site_key, :secret_key, :render, :onload, :default_data, :auto_populate_response_in_test_env,
|
|
7
|
+
:open_timeout, :read_timeout, :write_timeout
|
|
7
8
|
|
|
8
9
|
def initialize
|
|
9
10
|
@script_url = Cloudflare::SCRIPT_URL
|
|
@@ -13,6 +14,9 @@ module Cloudflare
|
|
|
13
14
|
@onload = nil
|
|
14
15
|
@default_data = {}
|
|
15
16
|
@auto_populate_response_in_test_env = true
|
|
17
|
+
@open_timeout = 5
|
|
18
|
+
@read_timeout = 5
|
|
19
|
+
@write_timeout = 5
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
# Dynamically build the URL every time, so that
|
|
@@ -11,6 +11,9 @@ module Cloudflare
|
|
|
11
11
|
# Default hidden input field name for Turnstile token submission
|
|
12
12
|
RESPONSE_FIELD_NAME = 'cf-turnstile-response'.freeze
|
|
13
13
|
|
|
14
|
+
# Longest token siteverify accepts; anything longer is rejected without a request
|
|
15
|
+
MAX_RESPONSE_LENGTH = 2048
|
|
16
|
+
|
|
14
17
|
# Default CSS class applied to Turnstile widget containers
|
|
15
18
|
WIDGET_CLASS = 'cf-turnstile'.freeze
|
|
16
19
|
end
|
|
@@ -17,10 +17,18 @@ module Cloudflare
|
|
|
17
17
|
result
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# flash: :keep carries the automatic failure message into the next request,
|
|
21
|
+
# for a redirect. flash: :now scopes it to the response being rendered. The
|
|
22
|
+
# keyword shadows the controller's own flash method, hence request.flash.
|
|
23
|
+
def valid_turnstile?(model: nil, flash: :keep, **opts)
|
|
21
24
|
response = verify_turnstile(model: model, **opts)
|
|
22
25
|
success = response.is_a?(VerificationResponse) && response.success?
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
if !success && model.nil?
|
|
28
|
+
store = flash == :now ? request.flash.now : request.flash
|
|
29
|
+
store[:alert] = ErrorMessage.default
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
success
|
|
25
33
|
end
|
|
26
34
|
|
|
@@ -2,6 +2,7 @@ require 'net/http'
|
|
|
2
2
|
require 'openssl'
|
|
3
3
|
|
|
4
4
|
require_relative 'constants/cloudflare'
|
|
5
|
+
require_relative 'constants/error_code'
|
|
5
6
|
|
|
6
7
|
module Cloudflare
|
|
7
8
|
module Turnstile
|
|
@@ -48,15 +49,25 @@ module Cloudflare
|
|
|
48
49
|
|
|
49
50
|
module Verification
|
|
50
51
|
def self.verify(response: nil, secret: nil, remoteip: nil, idempotency_key: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
51
|
-
if
|
|
52
|
-
|
|
52
|
+
if response.is_a?(String) && !response.valid_encoding?
|
|
53
|
+
return VerificationResponse.new('success' => false, 'error-codes' => [ErrorCode::INVALID_INPUT_RESPONSE])
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
response = 'dummy-response' if auto_populate_test_response?(response)
|
|
57
|
+
|
|
55
58
|
secret ||= Rails.configuration.secret_key
|
|
56
59
|
if secret.nil? || secret.strip.empty?
|
|
57
60
|
raise ConfigurationError, ErrorMessage.for(ErrorCode::MISSING_INPUT_SECRET)
|
|
58
61
|
end
|
|
59
62
|
|
|
63
|
+
local_error = if missing_response?(response)
|
|
64
|
+
ErrorCode::MISSING_INPUT_RESPONSE
|
|
65
|
+
elsif invalid_response?(response)
|
|
66
|
+
ErrorCode::INVALID_INPUT_RESPONSE
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return VerificationResponse.new('success' => false, 'error-codes' => [local_error]) if local_error
|
|
70
|
+
|
|
60
71
|
body = { 'secret' => secret, 'response' => response }
|
|
61
72
|
body['remoteip'] = remoteip if remoteip
|
|
62
73
|
body['idempotency_key'] = idempotency_key if idempotency_key
|
|
@@ -67,17 +78,20 @@ module Cloudflare
|
|
|
67
78
|
|
|
68
79
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
69
80
|
http.use_ssl = true
|
|
81
|
+
http.open_timeout = Rails.configuration.open_timeout
|
|
82
|
+
http.read_timeout = Rails.configuration.read_timeout
|
|
83
|
+
http.write_timeout = Rails.configuration.write_timeout
|
|
70
84
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
71
85
|
http.cert_store = OpenSSL::X509::Store.new
|
|
72
86
|
http.cert_store.set_default_paths
|
|
73
87
|
|
|
74
88
|
begin
|
|
75
89
|
res = http.request(request)
|
|
76
|
-
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
90
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Errno::ETIMEDOUT => e
|
|
77
91
|
raise ConfigurationError, "Turnstile verification timed out: #{e.message}"
|
|
78
92
|
rescue OpenSSL::SSL::SSLError => e
|
|
79
93
|
raise ConfigurationError, "SSL verification failed: #{e.message}"
|
|
80
|
-
rescue SocketError, Errno::ECONNREFUSED => e
|
|
94
|
+
rescue SocketError, EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH => e
|
|
81
95
|
raise ConfigurationError, "Network error during Turnstile verification: #{e.message}"
|
|
82
96
|
end
|
|
83
97
|
|
|
@@ -89,6 +103,23 @@ module Cloudflare
|
|
|
89
103
|
|
|
90
104
|
VerificationResponse.new(json)
|
|
91
105
|
end
|
|
106
|
+
|
|
107
|
+
def self.auto_populate_test_response?(response)
|
|
108
|
+
(response.nil? || (response.is_a?(String) && response.strip.empty?)) &&
|
|
109
|
+
::Rails.env.test? &&
|
|
110
|
+
Rails.configuration.auto_populate_response_in_test_env
|
|
111
|
+
end
|
|
112
|
+
private_class_method :auto_populate_test_response?
|
|
113
|
+
|
|
114
|
+
def self.missing_response?(response)
|
|
115
|
+
response.nil? || response == ''
|
|
116
|
+
end
|
|
117
|
+
private_class_method :missing_response?
|
|
118
|
+
|
|
119
|
+
def self.invalid_response?(response)
|
|
120
|
+
!response.is_a?(String) || response.strip.empty? || response.length > Cloudflare::MAX_RESPONSE_LENGTH
|
|
121
|
+
end
|
|
122
|
+
private_class_method :invalid_response?
|
|
92
123
|
end
|
|
93
124
|
end
|
|
94
125
|
end
|
|
@@ -25,6 +25,12 @@ Cloudflare::Turnstile::Rails.configure do |config|
|
|
|
25
25
|
# language: -> { I18n.locale }
|
|
26
26
|
# }
|
|
27
27
|
|
|
28
|
+
# Optional: Seconds to wait when connecting to, and reading from, Cloudflare's siteverify endpoint.
|
|
29
|
+
# Keep these short so a slow Cloudflare can't hold your app's request threads for long.
|
|
30
|
+
# config.open_timeout = 5 # connecting to siteverify
|
|
31
|
+
# config.read_timeout = 5 # reading the response
|
|
32
|
+
# config.write_timeout = 5 # sending the verification POST
|
|
33
|
+
|
|
28
34
|
# In the Rails Test environment, automatically fill in a dummy response if none was provided.
|
|
29
35
|
# This lets you keep existing controller tests without having to add
|
|
30
36
|
# params["cf-turnstile-response"] manually in every test.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cloudflare-turnstile-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vadim Kononov
|
|
@@ -23,8 +23,9 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '5.0'
|
|
26
|
-
description:
|
|
27
|
-
|
|
26
|
+
description: Cloudflare Turnstile CAPTCHA, a reCAPTCHA alternative, for Ruby on Rails.
|
|
27
|
+
View and controller helpers with Turbo, Turbo Streams, Turbolinks, CSP nonce and
|
|
28
|
+
i18n support.
|
|
28
29
|
email:
|
|
29
30
|
- vadim@konoson.com
|
|
30
31
|
executables: []
|
|
@@ -83,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
84
|
- !ruby/object:Gem::Version
|
|
84
85
|
version: '0'
|
|
85
86
|
requirements: []
|
|
86
|
-
rubygems_version: 4.0.
|
|
87
|
+
rubygems_version: 4.0.20
|
|
87
88
|
specification_version: 4
|
|
88
|
-
summary: Cloudflare Turnstile
|
|
89
|
-
and CSP compliance
|
|
89
|
+
summary: Cloudflare Turnstile CAPTCHA for Rails with Turbo, Turbolinks and CSP support
|
|
90
90
|
test_files: []
|