fix: Newsletter und Firmenanfrage waeren mit aktivem CAPTCHA unabsendbar gewesen

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) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5 committed 2026-09-20 04:04:36 +02:00
1 parent 5870c60c50
commit 368985c0cf
4 files changed
+42 -12

No files matched your search

+20 -5
View File
@@ -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<Status>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@@ -96,8 +111,8 @@ export function BusinessInquiryForm() {
{errorMessage ? <p className="text-sm text-red-700">{errorMessage}</p> : null}
{TURNSTILE_SITE_KEY ? (
<TurnstileWidget siteKey={TURNSTILE_SITE_KEY} onVerify={setTurnstileToken} />
{turnstileSiteKey ? (
<TurnstileWidget siteKey={turnstileSiteKey} onVerify={setTurnstileToken} />
) : null}
<Button type="submit" disabled={status === "loading"}>
+20 -5
View File
@@ -8,9 +8,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 NewsletterSignup() {
/**
* 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 NewsletterSignup({ turnstileSiteKey }: { turnstileSiteKey?: string }) {
const { t } = useI18n();
const [email, setEmail] = useState("");
const [status, setStatus] = useState<Status>("idle");
@@ -63,9 +78,9 @@ export function NewsletterSignup() {
<Button type="submit" disabled={status === "loading"} className="flex-shrink-0 whitespace-nowrap">
{status === "loading" ? t.home.newsletter.subscribing : t.home.newsletter.subscribe}
</Button>
{TURNSTILE_SITE_KEY ? (
{turnstileSiteKey ? (
<div className="w-full flex justify-center">
<TurnstileWidget siteKey={TURNSTILE_SITE_KEY} onVerify={setTurnstileToken} />
<TurnstileWidget siteKey={turnstileSiteKey} onVerify={setTurnstileToken} />
</div>
) : null}
</form>