import { env } from "cloudflare:workers";
import { forbidden } from "./errors";
import { rawRequestIp } from "./limits";
const SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
const MAX_TOKEN_LENGTH = 2048;
const VERIFY_TIMEOUT_MS = 10_000;
export type TurnstileAction = "signup" | "login";
type SiteverifyResult = { success?: boolean; action?: string; hostname?: string };
function loopbackForge(): boolean {
try {
const host = new URL(env.PUBLIC_URL).hostname;
return host === "localhost" || host === "127.0.0.1" || host === "[::1]";
} catch {
return false;
}
}
/**
* The public sitekey; Turnstile is on for a deployment exactly when this is set. A loopback forge with no
* TURNSTILE_SECRET in .dev.vars runs without it; a deployed forge missing the secret fails closed instead.
*/
export function turnstileSiteKey(): string | null {
const siteKey = env.TURNSTILE_SITEKEY?.trim();
if (!siteKey) return null;
if (!env.TURNSTILE_SECRET && loopbackForge()) return null;
return siteKey;
}
/** TURNSTILE_HOSTNAMES, or this deployment's PUBLIC_URL host, so a production forge never accepts localhost tokens. */
function expectedHostnames(): Set<string> {
const configured = (env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim().toLowerCase())
.filter(Boolean);
if (configured.length > 0) return new Set(configured);
try {