feat(web): add Cloudflare Turnstile to the public forms

Render the widget on the contact, event registration and both application
forms, and verify the token with Siteverify in each action before anything
is written to the CMS. Verification fails closed. The check is off unless
both TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY are set.

Allow challenges.cloudflare.com in script-src and frame-src.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:05:51 +02:00
co-authored by Claude Opus 5.5
parent 0b2c8d581f
commit 709a978da5
17 changed files with 475 additions and 15 deletions
+4 -1
View File
@@ -36,7 +36,7 @@ import { useLocale, useRouteHandleValue } from "~/i18n";
import { DEFAULT_LOCALE, dirFor, type Locale } from "~/i18n/config";
import { localizedPath } from "~/i18n/paths";
import { createTranslator, getMessages } from "~/i18n/t";
import { getSiteUrl } from "~/lib/env.server";
import { getServerEnv, getSiteUrl } from "~/lib/env.server";
import {
readCanonicalPath,
readFooterSurface,
@@ -94,6 +94,9 @@ export function loader({ request }: Route.LoaderArgs) {
`:root` is dark, so the absence of a cookie is a dark first visit. */
theme: readTheme(request.headers.get("Cookie")),
nonce: createCspNonce(),
/* Public by design - the widget needs it in the browser. Null when
Turnstile is not configured, and then no form renders the widget. */
turnstileSiteKey: getServerEnv().TURNSTILE_SITE_KEY ?? null,
};
}