Add Turnstile protection to user actions
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user