54 lines
1.5 KiB
TypeScript
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;
|
|
}
|
|
}
|