# Cloudflare Turnstile

Turnstile is implemented on sign-in, registration, password-reset requests and password-reset completion. Every role uses the same protected sign-in endpoint. Tokens are checked server-side before authentication, account creation, reset mail or password changes. Existing CSRF, account activation, password validation, role authorization and rate limits still apply.

## Current local setup

The running local application has Turnstile enabled with Cloudflare's official always-pass test keys. These exercise the widget and live Siteverify request; they do not provide bot protection. Existing test account passwords are unchanged. Complete the test widget on the normal login page before submitting.

```dotenv
TURNSTILE_ENABLED=true
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
TURNSTILE_HOSTNAMES=127.0.0.1,localhost
```

Official dummy tokens/responses work only with test keys. They are accepted in local/testing environments and an explicitly enabled Testing/Demo staging installation. Production rejects all recognized test keys even if testing flags are enabled. The server still calls Cloudflare for dummy-token verification and never accepts a provider failure.

## Production configuration

Create a Turnstile widget in your Cloudflare account for the actual application hostname(s), using Managed mode. Place its site key and secret key in the server's environment:

```dotenv
TURNSTILE_ENABLED=true
TURNSTILE_SITE_KEY=your-widget-site-key
TURNSTILE_SECRET_KEY=your-widget-secret-key
TURNSTILE_HOSTNAMES=example.com,www.example.com
```

Replace the example hostnames. `TURNSTILE_HOSTNAMES` is an exact comma-separated allowlist without schemes, ports, paths or wildcards. If left blank, the application uses the hostname from `APP_URL`. It does not use the submitted request Host header as its allowlist.

Apply environment changes with:

```powershell
php artisan config:clear
php artisan view:cache
php artisan platform:check
```

Production deployments using cached configuration should then run `php artisan config:cache` as part of their normal deployment. The readiness check reports whether Turnstile is enabled with non-test keys; this is a configuration check. Verify the real widget and allowed hostnames on the deployed site before launch.

The site key is intentionally public. Keep the secret key in the server environment. It is never rendered into HTML, exposed through a public settings endpoint or forwarded to the browser. Authentication data goes only to this application; Siteverify receives the token and secret, without account credentials or thesis data.

## Backend behavior

- The server calls the fixed HTTPS Siteverify endpoint and requires a successful HTTP response with a boolean `success: true`.
- With real keys, the returned hostname must be on the configured allowlist and the returned action must match the form action.
- Missing, non-string and oversized tokens are rejected before the external request. Cloudflare rejects expired/reused tokens. Tokens are not cached as accepted or reused by the application.
- Missing keys, provider rejection, invalid response data, HTTP errors and connection failures block the submission. There is no fallback that silently disables verification. Reloading the form provides a new widget/token.
- Requests have a three-second connection timeout and ten-second total timeout. The application does not retry a consumed token or follow provider redirects. TLS certificate validation remains enabled.
- Verification tokens and passwords are excluded from flashed form input. Errors do not disclose the secret or provider response.
- The security policy allows `https://challenges.cloudflare.com` in the additional script/frame sources when configured. Inline scripts remain disallowed. The widget loads directly from Cloudflare without an asset build.

The local PHP development server was restarted with outbound HTTPS access so Siteverify can be reached. A production web/PHP service must likewise be able to contact `challenges.cloudflare.com:443`.

## Verification

The full regression suite passed **302 tests / 3,829 assertions**. Turnstile adds 22 focused cases for all four forms, successful authentication/reset behavior, token validation, rejected/malformed/provider-error responses, timeouts, hostname/action checks, secret/token handling and rejection of test keys in production.

Live checks verified the rendered widget/CSP, rejection of a missing challenge, and successful test-key logins using the existing Student, Admin, Manager, Designer, QC and Super Admin credentials. A direct request to Cloudflare Siteverify returned HTTP 200 and accepted the official dummy token. Real production keys and visual widget interaction remain to be checked on the target hostname; browser automation was unavailable in this session.

```powershell
php artisan test --compact --filter=TurnstileTest
php artisan test --compact
```

The general test suite forces `TURNSTILE_ENABLED=false` to keep isolated workflow tests independent of networking. The dedicated tests explicitly enable it and fake Siteverify responses, including failures.

Sources: [Cloudflare server-side validation](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/), [official test keys](https://developers.cloudflare.com/turnstile/troubleshooting/testing/), [content security policy](https://developers.cloudflare.com/turnstile/reference/content-security-policy/).
