Files

54 lines
1.5 KiB
TypeScript

import { getClientIp } from "@/lib/auth/rateLimit";
type SiteverifyResult = {
success?: unknown;
action?: unknown;
hostname?: unknown;
};
function expectedHostnames(): Set<string> {
return new Set(
(process.env.TURNSTILE_HOSTNAMES ?? "")
.split(",")
.map((hostname) => hostname.trim())
.filter(Boolean),
);
}
export async function verifyTurnstileToken(
token: unknown,
expectedAction: string,
request: Request,
): Promise<boolean> {
const secret = process.env.TURNSTILE_SECRET_KEY;
const hostnames = expectedHostnames();
if (
typeof secret !== "string" ||
secret.length === 0 ||
typeof token !== "string" ||
token.length === 0 ||
token.length > 2048 ||
hostnames.size === 0
) {
return false;
}
const body = new URLSearchParams({ secret, response: token });
const remoteIp = getClientIp(request);
if (remoteIp !== "unknown") body.set("remoteip", remoteIp);
try {
const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
signal: AbortSignal.timeout(10_000),
body,
});
if (!response.ok) return false;
const result = (await response.json()) as SiteverifyResult;
return result.success === true && result.action === expectedAction && typeof result.hostname === "string" && hostnames.has(result.hostname);
} catch {
return false;
}
}