This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+55
-48
@@ -13,64 +13,71 @@ import { siteSettings } from "@/lib/services/site-settings";
|
||||
* recaptcha_secret / recaptcha_site_key
|
||||
*/
|
||||
export interface CaptchaConfig {
|
||||
provider: "turnstile" | "recaptcha" | "none";
|
||||
siteKey: string;
|
||||
/** Form field the widget writes the token into. */
|
||||
field: string;
|
||||
provider: "turnstile" | "recaptcha" | "none";
|
||||
siteKey: string;
|
||||
/** Form field the widget writes the token into. */
|
||||
field: string;
|
||||
}
|
||||
|
||||
const TURNSTILE_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
||||
const TURNSTILE_URL =
|
||||
"https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
||||
const RECAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify";
|
||||
|
||||
/** Public config the register page needs to render the widget (no secrets). */
|
||||
export async function captchaConfig(): Promise<CaptchaConfig> {
|
||||
const provider = ((await siteSettings.get("captcha_provider", "none")) ?? "none").toLowerCase();
|
||||
if (provider === "turnstile") {
|
||||
return {
|
||||
provider: "turnstile",
|
||||
siteKey: (await siteSettings.get("turnstile_site_key", "")) ?? "",
|
||||
field: "cf-turnstile-response",
|
||||
};
|
||||
}
|
||||
if (provider === "recaptcha") {
|
||||
return {
|
||||
provider: "recaptcha",
|
||||
siteKey: (await siteSettings.get("recaptcha_site_key", "")) ?? "",
|
||||
field: "g-recaptcha-response",
|
||||
};
|
||||
}
|
||||
return { provider: "none", siteKey: "", field: "" };
|
||||
const provider = (
|
||||
(await siteSettings.get("captcha_provider", "none")) ?? "none"
|
||||
).toLowerCase();
|
||||
if (provider === "turnstile") {
|
||||
return {
|
||||
provider: "turnstile",
|
||||
siteKey: (await siteSettings.get("turnstile_site_key", "")) ?? "",
|
||||
field: "cf-turnstile-response",
|
||||
};
|
||||
}
|
||||
if (provider === "recaptcha") {
|
||||
return {
|
||||
provider: "recaptcha",
|
||||
siteKey: (await siteSettings.get("recaptcha_site_key", "")) ?? "",
|
||||
field: "g-recaptcha-response",
|
||||
};
|
||||
}
|
||||
return { provider: "none", siteKey: "", field: "" };
|
||||
}
|
||||
|
||||
/** Verify a submitted token. Returns true when allowed (incl. fail-open). */
|
||||
export async function verifyCaptcha(token: string | null, remoteIp?: string): Promise<boolean> {
|
||||
const cfg = await captchaConfig();
|
||||
if (cfg.provider === "none" || !cfg.siteKey) return true;
|
||||
export async function verifyCaptcha(
|
||||
token: string | null,
|
||||
remoteIp?: string,
|
||||
): Promise<boolean> {
|
||||
const cfg = await captchaConfig();
|
||||
if (cfg.provider === "none" || !cfg.siteKey) return true;
|
||||
|
||||
const secretKey = cfg.provider === "turnstile" ? "turnstile_secret" : "recaptcha_secret";
|
||||
const secret = (await siteSettings.get(secretKey, "")) ?? "";
|
||||
if (!secret) return true; // configured but no secret — don't hard-block
|
||||
if (!token) return false;
|
||||
const secretKey =
|
||||
cfg.provider === "turnstile" ? "turnstile_secret" : "recaptcha_secret";
|
||||
const secret = (await siteSettings.get(secretKey, "")) ?? "";
|
||||
if (!secret) return true; // configured but no secret — don't hard-block
|
||||
if (!token) return false;
|
||||
|
||||
const url = cfg.provider === "turnstile" ? TURNSTILE_URL : RECAPTCHA_URL;
|
||||
const body = new URLSearchParams({ secret, response: token });
|
||||
if (remoteIp) body.set("remoteip", remoteIp);
|
||||
const url = cfg.provider === "turnstile" ? TURNSTILE_URL : RECAPTCHA_URL;
|
||||
const body = new URLSearchParams({ secret, response: token });
|
||||
if (remoteIp) body.set("remoteip", remoteIp);
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 5000);
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body,
|
||||
signal: controller.signal,
|
||||
cache: "no-store",
|
||||
});
|
||||
clearTimeout(timer);
|
||||
const data = (await res.json()) as { success?: boolean };
|
||||
return data?.success === true;
|
||||
} catch {
|
||||
// Network/timeout — fail-open so a provider outage can't lock out signups.
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 5000);
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body,
|
||||
signal: controller.signal,
|
||||
cache: "no-store",
|
||||
});
|
||||
clearTimeout(timer);
|
||||
const data = (await res.json()) as { success?: boolean };
|
||||
return data?.success === true;
|
||||
} catch {
|
||||
// Network/timeout — fail-open so a provider outage can't lock out signups.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user