devise-cloudflare-turnstile 0.10.0 → 0.12.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 11f41c4964063781c8e59be1c7030ce996230ece6674388be3a12cb0ebfbc1f8
|
|
4
|
+
data.tar.gz: 8da777725211daaf3d95c5ef642f52ce1dfa6cbcc09890d344614fa3e7b441de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e28097cca5cdfe4a358980ea4d443d4a6edd761cab258efc82b22188cf2d6a08233def94e755bc0b0470db189c3f6e5898dd687671935e19ab30f26542342d7e
|
|
7
|
+
data.tar.gz: 27ea16b46b2605f5fac172e583054677bb0b2ef791ba873a672a312cee371a25b47ccc498213af58066b68b71969a210e121c2724f83f8546e43d2cdf2a190ba
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/vkononov/devise-cloudflare-turnstile/blob/main/Appraisals)
|
|
6
6
|
[](https://github.com/vkononov/devise-cloudflare-turnstile/actions/workflows/test.yml)
|
|
7
7
|
[](https://github.com/vkononov/devise-cloudflare-turnstile/actions/workflows/lint.yml)
|
|
8
|
-
[](https://opensource.org/
|
|
8
|
+
[](https://opensource.org/license/MIT)
|
|
9
9
|
|
|
10
10
|
Automatically protect all Devise authentication forms with Cloudflare Turnstile. Add two helper tags to your layout, set your API keys, and every Devise form is protected — no changes to your Devise views or controllers.
|
|
11
11
|
|
|
@@ -84,8 +84,10 @@ That's it — all Devise forms are now protected.
|
|
|
84
84
|
|
|
85
85
|
### How It Works
|
|
86
86
|
|
|
87
|
-
* **Controller** — a `before_action` on every Devise controller verifies the Turnstile response on `create` and re-renders the form
|
|
88
|
-
* **View** — on Devise form pages, the two layout helpers emit the site key and load the injector JavaScript, which adds a `cf-turnstile` widget before each submit button.
|
|
87
|
+
* **Controller** — a `before_action` on every Devise controller verifies the Turnstile response on `create` and re-renders the form if it fails, reporting the failure as `flash.now[:alert]`. Your layout must render `alert` for the message to be seen.
|
|
88
|
+
* **View** — on Devise form pages, the two layout helpers emit the site key and load the injector JavaScript, which adds a `cf-turnstile` widget before each submit button. Only forms with a field a person fills in are considered, so action-only POST forms such as OAuth sign-in and log-out buttons are skipped.
|
|
89
|
+
|
|
90
|
+
The widget appears only on the `create` forms (and their `new` pages) — sign in, sign up, password-reset request, and resend confirmation/unlock. Authenticated and edit pages such as account settings, password-reset completion, and accepting an invitation are left untouched.
|
|
89
91
|
|
|
90
92
|
Widget rendering, script loading, CSP nonces, and Turbo/Turbolinks handling are delegated to [cloudflare-turnstile-rails](https://github.com/vkononov/cloudflare-turnstile-rails).
|
|
91
93
|
|
|
@@ -176,10 +178,12 @@ end
|
|
|
176
178
|
|
|
177
179
|
| Situation | Behavior |
|
|
178
180
|
|-----------|----------|
|
|
179
|
-
| No custom controllers | All Devise controllers are protected automatically |
|
|
181
|
+
| No custom controllers | All Devise controllers are protected automatically on their `create` actions |
|
|
182
|
+
| Edit / authenticated pages (account settings, password-reset completion, accept invitation) | Not protected — the widget only appears on the `create` forms |
|
|
180
183
|
| Custom controller with `skip_turnstile` (or config `skip`) | That controller/action opts out of both widget and verification |
|
|
181
184
|
| No custom views | Devise's default views render; the widget still injects |
|
|
182
|
-
| Custom view without a widget | The widget is
|
|
185
|
+
| Custom view without a widget | The widget is injected before the submit button if the form has a fillable field: a text, email, password, `textarea` or `select` field counts, a hidden input, checkbox, radio or file input does not |
|
|
186
|
+
| Action-only POST form (OAuth sign-in, log out, delete) | No fillable field, so no widget |
|
|
183
187
|
| Custom view with `cloudflare_turnstile_tag` | Your widget is used as-is; nothing is auto-injected |
|
|
184
188
|
| Invisible vs visible widget | Determined by the site key's widget type in the Cloudflare dashboard |
|
|
185
189
|
|
|
@@ -191,7 +195,7 @@ Cloudflare provides dummy sitekeys and secret keys for development and testing.
|
|
|
191
195
|
|----------|-------|----------|
|
|
192
196
|
| Site Key | `1x00000000000000000000AA` | Always passes (visible) |
|
|
193
197
|
| Secret Key | `1x0000000000000000000000000000000AA` | Always passes |
|
|
194
|
-
| Site Key | `2x00000000000000000000AB` | Always
|
|
198
|
+
| Site Key | `2x00000000000000000000AB` | Always fails (visible) |
|
|
195
199
|
| Secret Key | `2x0000000000000000000000000000000AA` | Always fails |
|
|
196
200
|
|
|
197
201
|
## Development
|
|
@@ -224,7 +228,7 @@ Run RuboCop and ESLint together:
|
|
|
224
228
|
bin/lint
|
|
225
229
|
```
|
|
226
230
|
|
|
227
|
-
> **CI Note:** We run
|
|
231
|
+
> **CI Note:** We run `bin/lint --no-fix` via [.github/workflows/lint.yml](.github/workflows/lint.yml) on the latest Ruby only.
|
|
228
232
|
|
|
229
233
|
## Contributing
|
|
230
234
|
|
|
@@ -43,6 +43,15 @@
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Hidden inputs, checkboxes, radios and file inputs don't count as fillable.
|
|
47
|
+
function hasFillableField(form) {
|
|
48
|
+
var selector = 'input[type="text"], input[type="email"], input[type="password"], ' +
|
|
49
|
+
'input[type="search"], input[type="tel"], input[type="url"], ' +
|
|
50
|
+
'input[type="number"], input:not([type]), textarea, select';
|
|
51
|
+
|
|
52
|
+
return form.querySelector(selector) !== null;
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
function renderWidget(widget) {
|
|
47
56
|
if (widget.dataset.initialized || widget.childElementCount > 0) {
|
|
48
57
|
return;
|
|
@@ -81,6 +90,10 @@
|
|
|
81
90
|
widget = form.querySelector('.' + WIDGET_CLASS);
|
|
82
91
|
|
|
83
92
|
if (!widget) {
|
|
93
|
+
if (!hasFillableField(form)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
submit = form.querySelector('input[type="submit"], button[type="submit"], button:not([type])');
|
|
85
98
|
|
|
86
99
|
if (!submit) {
|
|
@@ -10,7 +10,7 @@ module Devise
|
|
|
10
10
|
class_attribute :_turnstile_skip_rules, instance_accessor: false
|
|
11
11
|
self._turnstile_skip_rules = []
|
|
12
12
|
|
|
13
|
-
before_action :verify_cloudflare_turnstile!,
|
|
13
|
+
before_action :verify_cloudflare_turnstile!, if: :turnstile_verify_action?
|
|
14
14
|
before_action :set_turnstile_page_marker, if: :turnstile_form_action?
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -30,23 +30,30 @@ module Devise
|
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
def verify_cloudflare_turnstile!
|
|
33
|
+
def verify_cloudflare_turnstile!
|
|
34
34
|
return if turnstile_skipped?
|
|
35
35
|
|
|
36
36
|
self.resource ||= resource_class.new
|
|
37
|
-
|
|
37
|
+
# No model is passed, so the failure stays out of resource.errors and is
|
|
38
|
+
# reported through the flash, scoped to the render below.
|
|
39
|
+
return if valid_turnstile?(flash: :now, **turnstile_verify_options)
|
|
38
40
|
|
|
41
|
+
revoke_params_authentication!
|
|
39
42
|
restore_turnstile_submitted_values
|
|
40
|
-
|
|
41
|
-
# Sessions (and some other Devise views) do not render resource errors.
|
|
42
|
-
# Always surface the failure via flash so the user sees feedback.
|
|
43
|
-
flash.now[:alert] = ::Cloudflare::Turnstile::Rails::ErrorMessage.default
|
|
44
43
|
clean_up_passwords(resource) if respond_to?(:clean_up_passwords, true)
|
|
45
44
|
set_minimum_password_length if respond_to?(:set_minimum_password_length, true)
|
|
46
45
|
set_turnstile_page_marker
|
|
47
46
|
render turnstile_failure_action, status: :unprocessable_entity
|
|
48
47
|
end
|
|
49
48
|
|
|
49
|
+
# Devise's sessions#create marks the request eligible for authentication
|
|
50
|
+
# straight from the posted credentials. That flag outlives our halt, so
|
|
51
|
+
# any current_user call during the failure render would sign the visitor
|
|
52
|
+
# in. Devise publishes no constant for the key.
|
|
53
|
+
def revoke_params_authentication!
|
|
54
|
+
request.env.delete('devise.allow_params_authentication')
|
|
55
|
+
end
|
|
56
|
+
|
|
50
57
|
# Re-populate the resource with the values the user just submitted (minus
|
|
51
58
|
# passwords) so the re-rendered form keeps their input. We fail in a
|
|
52
59
|
# before_action, before Devise builds the resource from params, so
|
|
@@ -85,13 +92,21 @@ module Devise
|
|
|
85
92
|
@_devise_turnstile_protected = true
|
|
86
93
|
end
|
|
87
94
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
95
|
+
# Verify only on create. Kept as a predicate rather than `only: :create`
|
|
96
|
+
# so the callback is skipped cleanly on controllers without a create
|
|
97
|
+
# action instead of raising.
|
|
98
|
+
def turnstile_verify_action?
|
|
99
|
+
action_name == 'create'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The widget shows on the create forms (`new`) and on `create` itself, so
|
|
103
|
+
# a failed submission that re-renders still emits the meta tag and scripts
|
|
104
|
+
# that Turbo needs. Edit/update pages are excluded because only `create`
|
|
105
|
+
# is verified.
|
|
91
106
|
def turnstile_form_action?
|
|
92
107
|
return false if turnstile_skipped?
|
|
93
108
|
|
|
94
|
-
action_name.in?(%w[new
|
|
109
|
+
action_name.in?(%w[new create])
|
|
95
110
|
end
|
|
96
111
|
|
|
97
112
|
def turnstile_skipped?
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise-cloudflare-turnstile
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vadim Kononov
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.
|
|
25
|
+
version: '1.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: devise
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '0'
|
|
81
81
|
requirements: []
|
|
82
|
-
rubygems_version:
|
|
82
|
+
rubygems_version: 3.7.2
|
|
83
83
|
specification_version: 4
|
|
84
84
|
summary: Cloudflare Turnstile integration for Devise
|
|
85
85
|
test_files: []
|