| 1 | import { env } from "cloudflare:workers"; |
| 2 | |
| 3 | import { TURNSTILE_FIELD, TURNSTILE_VERIFY, type TurnstileSettings, turnstilePassed, turnstileSettings } from "./turnstile"; |
| 4 | |
| 5 | /** Turnstile's settings from the Worker's: null when it is off (lib/turnstile.ts). */ |
| 6 | export function turnstile(): TurnstileSettings { |
| 7 | const vars = env as unknown as { TURNSTILE_SITE_KEY?: string; TURNSTILE_SECRET?: string }; |
| 8 | return turnstileSettings(vars.TURNSTILE_SITE_KEY, vars.TURNSTILE_SECRET); |
| 9 | } |
| 10 | |
| 11 | /** |
| 12 | * Whether the form's Turnstile answer is good. True when Turnstile is off. |
| 13 | * A missing or refused answer is false. When Cloudflare cannot be reached |
| 14 | * the sign-up goes ahead, with the rate limit and email confirmation still |
| 15 | * in its way: a check that cannot be made is no reason to stop everyone. |
| 16 | */ |
| 17 | export async function turnstileOk(form: FormData, client: string | null): Promise<boolean> { |
| 18 | const settings = turnstile(); |
| 19 | if (!settings) return true; |
| 20 | const answer = String(form.get(TURNSTILE_FIELD) ?? "").trim(); |
| 21 | if (!answer) return false; |
| 22 | const body = new FormData(); |
| 23 | body.set("secret", settings.secret); |
| 24 | body.set("response", answer); |
| 25 | if (client) body.set("remoteip", client); |
| 26 | try { |
| 27 | const response = await fetch(TURNSTILE_VERIFY, { method: "POST", body }); |
| 28 | if (!response.ok) { |
| 29 | console.error(`turnstile: siteverify answered ${response.status}`); |
| 30 | return true; |
| 31 | } |
| 32 | return turnstilePassed(await response.json()); |
| 33 | } catch (error) { |
| 34 | console.error("turnstile: siteverify could not be reached", error); |
| 35 | return true; |
| 36 | } |
| 37 | } |