From 368985c0cfef8231452891e333484ac11d482360 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Sun, 20 Sep 2026 04:04:36 +0200 Subject: [PATCH] fix: Newsletter und Firmenanfrage waeren mit aktivem CAPTCHA unabsendbar gewesen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beide Formulare lasen NEXT_PUBLIC_TURNSTILE_SITE_KEY direkt aus process.env, und zwar im Modulrumpf einer Client Component. NEXT_PUBLIC_-Variablen werden dort zur BUILD-Zeit ins Bundle eingebacken. Gesetzt wird der Wert hier aber erst zur Laufzeit (k8s/01-configmap.yaml) — im Docker-Build existiert er nicht und wird auch nicht als Build-Arg hereingereicht. Der Wert war damit dauerhaft undefined, das Widget erschien nie. Solange TURNSTILE_SECRET_KEY ebenfalls leer ist, faellt das nicht auf: verifyTurnstileToken gibt ohne Secret bewusst true zurueck. Sobald das Secret aber gesetzt wird — also genau beim Scharfschalten des Schutzes —, verlangt der Server einen Token, den der Browser nirgends erzeugen kann. Newsletter- Anmeldung und Firmenanfrage haetten dann jede Eingabe abgewiesen, ohne dass irgendwo ein Fehler sichtbar wird. Alle uebrigen Formulare (Login, Registrierung, Passwort-Reset, Zwei-Faktor, Checkout, Verfuegbarkeits-Benachrichtigung) lesen den Wert bereits im Server Component und reichen ihn als Prop weiter — dort wirkt die Laufzeit-Variable. Diese beiden folgen jetzt demselben Muster. Bewusst so und nicht ueber ein Docker-Build-Arg geloest: ein Build-Arg wuerde den Schluessel ins Image einbacken, womit ein Schluesselwechsel einen Neubau braeuchte und die ConfigMap fuer diese beiden Stellen wirkungslos bliebe. Ein einziger Weg fuer alle Formulare ist weniger fehleranfaellig als zwei. Hinweis fuer den Betrieb: der Schutz ist damit noch nicht aktiv. Dafuer muessen NEXT_PUBLIC_TURNSTILE_SITE_KEY (ConfigMap) UND TURNSTILE_SECRET_KEY (Secret) gesetzt sein — beide oder keiner. tsc: 0 Fehler. vitest: 359/359 gruen. eslint: 0 Warnungen. next build: gruen. Co-Authored-By: Claude Opus 5 (1M context) --- src/app/page.tsx | 2 +- src/app/unternehmen/page.tsx | 2 +- src/components/business-inquiry-form.tsx | 25 +++++++++++++++++++----- src/components/newsletter-signup.tsx | 25 +++++++++++++++++++----- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index fdce1e9..c2368cf 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -63,7 +63,7 @@ export default async function HomePage() { - + ); } diff --git a/src/app/unternehmen/page.tsx b/src/app/unternehmen/page.tsx index 0335970..f43fab7 100644 --- a/src/app/unternehmen/page.tsx +++ b/src/app/unternehmen/page.tsx @@ -51,7 +51,7 @@ export default async function UnternehmenPage() {

{t.unternehmenPage.inquiryHeading}

- +
diff --git a/src/components/business-inquiry-form.tsx b/src/components/business-inquiry-form.tsx index d3f994a..f4e827c 100644 --- a/src/components/business-inquiry-form.tsx +++ b/src/components/business-inquiry-form.tsx @@ -7,9 +7,24 @@ import { useI18n } from "@/i18n/i18n-provider"; type Status = "idle" | "loading" | "success" | "error"; -const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY; - -export function BusinessInquiryForm() { +/** + * Der Turnstile-Sitekey kommt als Prop vom Server und NICHT aus + * process.env in dieser Datei. + * + * Grund: NEXT_PUBLIC_-Variablen werden in Client Components zur BUILD-Zeit + * ins Bundle eingebacken. Der Wert wird hier aber erst zur Laufzeit gesetzt + * (k8s/01-configmap.yaml). Ein Zugriff auf process.env an dieser Stelle + * ergibt deshalb dauerhaft undefined — das Widget erschiene nie, waehrend die + * Serverseite bei gesetztem TURNSTILE_SECRET_KEY sehr wohl einen Token + * verlangt. Das Formular waere damit unabsendbar, ohne dass irgendwo ein + * Fehler auftaucht. + * + * Alle uebrigen Formulare (Login, Registrierung, Passwort-Reset, Zwei-Faktor, + * Checkout, Verfuegbarkeits-Benachrichtigung) lesen den Wert bereits im + * Server Component und reichen ihn als Prop weiter. Diese Datei folgt jetzt + * demselben Muster. + */ +export function BusinessInquiryForm({ turnstileSiteKey }: { turnstileSiteKey?: string }) { const { t } = useI18n(); const [status, setStatus] = useState("idle"); const [errorMessage, setErrorMessage] = useState(null); @@ -96,8 +111,8 @@ export function BusinessInquiryForm() { {errorMessage ?

{errorMessage}

: null} - {TURNSTILE_SITE_KEY ? ( - + {turnstileSiteKey ? ( + ) : null} - {TURNSTILE_SITE_KEY ? ( + {turnstileSiteKey ? (
- +
) : null}