const VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify" /** * Verifies a Cloudflare Turnstile token server-side against the siteverify API. * * Fails CLOSED when Turnstile is configured (secret present) but the token is * missing or invalid. Fails OPEN only when `TURNSTILE_SECRET_KEY` is unset — so * environments that haven't configured Turnstile keep working, matching how the * other optional integrations (Stripe / OpenAI / SMTP email) degrade in this app. */ export async function verifyTurnstile( token: string | undefined | null, remoteIp?: string | null ): Promise { const secret = process.env.TURNSTILE_SECRET_KEY if (!secret) return true // integration disabled — do not block auth if (!token) return false try { const body = new URLSearchParams() body.append("secret", secret) body.append("response", token) if (remoteIp) body.append("remoteip", remoteIp) const res = await fetch(VERIFY_URL, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body, cache: "no-store", }) const data = (await res.json()) as { success?: boolean } return data.success === true } catch { // Network / provider error — fail closed so a challenge can't be bypassed. return false } }