Add Cloudflare Turnstile to the registration flow - #481
Draft
simonhamp wants to merge 1 commit into
Draft
Conversation
Registration was open to bot signups. This gates it behind the existing Turnstile widget already used by the lead submission form. Extends App\Rules\Turnstile with optional action pinning, a request timeout, a token length bound, opt-in hostname pinning, and fail-closed handling of connection errors and non-2xx responses. Default constructor args leave the lead form's usage unchanged. Register validation moves into a RegisterRequest, mirroring the sibling LoginRequest, so the controller body stays as it was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Registration is getting hit by bot signups. This gates it behind Cloudflare Turnstile.
The project already had Turnstile plumbing —
App\Rules\Turnstile,services.turnstile.*config, and a widget on the lead submission form — so this extends that rather than adding a parallel integration.What changed
app/Rules/Turnstile.php— hardened per Cloudflare's siteverify guidance:expectedActionconstructor arg, so a token minted on another form can't be replayed against registrationTURNSTILE_HOSTNAMESDefault constructor args leave the lead form's
new Turnstileusage unchanged.app/Http/Requests/Auth/RegisterRequest.php— new, mirroring the siblingLoginRequest. Moves the existing register rules out of the controller and addscf-turnstile-response. The controller body is otherwise untouched.resources/views/components/turnstile.blade.php— reusable component.@once-pushesapi.js, renders the widget withdata-theme="auto"(the site has dark mode) anddata-size="flexible", and slots in the Flux error.resources/views/components/layouts/auth.blade.php— added the@stack('head')the other layouts already have.12 new tests covering the happy path, missing/oversized tokens, siteverify failure, action mismatch, Cloudflare unreachable, 5xx responses, and hostname pinning both ways.
Verification
Full suite green: 1488 tests, 4016 assertions.
Also driven through a real browser at
/register— the widget mounts, Cloudflare injects the hiddencf-turnstile-responseinput into the form, and a bad challenge redirects back with the error rendered in place. The rule was additionally exercised against the live siteverify endpoint: valid secret passes, invalid secret fails, action mismatch fails.Before merging
TURNSTILE_SECRET_KEYin production. It's intentionally not in this PR. Until it's set the rule short-circuits and registration is unprotected. That fail-open is the pre-existing pattern from the lead form, kept so local dev and CI don't need Cloudflare — but it does mean a missing secret silently disables the gate. Happy to make it hard-fail in production instead.TURNSTILE_SITE_KEY.Notes
wrangler secret put. There's no Worker here, so that step maps onto Laravel's env store instead.TURNSTILE_HOSTNAMESis opt-in rather than fail-closed-when-empty; defaulting it on would have immediately broken the existing lead form and local dev./registerpage rendered before this deploy (withoutdata-action) will fail until a refresh.🤖 Generated with Claude Code