const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; export function isPageDataCaptchaEnabled() { return Boolean(String(process.env.PAGE_DATA_TURNSTILE_SECRET ?? '').trim()); } export async function verifyPageDataCaptcha(token, remoteIp, { fetchImpl = globalThis.fetch } = {}) { const secret = String(process.env.PAGE_DATA_TURNSTILE_SECRET ?? '').trim(); if (!secret) { return { ok: true, skipped: true }; } const responseToken = String(token ?? '').trim(); if (!responseToken) { throw Object.assign(new Error('提交表单需要验证码'), { code: 'captcha_required', status: 400 }); } const body = new URLSearchParams({ secret, response: responseToken, }); if (remoteIp) body.set('remoteip', String(remoteIp)); const response = await fetchImpl(TURNSTILE_VERIFY_URL, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: body.toString(), }); const payload = await response.json().catch(() => ({})); if (!payload?.success) { throw Object.assign(new Error('验证码校验失败'), { code: 'captcha_invalid', status: 403, }); } return { ok: true, skipped: false }; } export function extractPageDataCaptchaToken(req, payload) { return ( String(req.headers?.['x-page-data-captcha'] ?? '').trim() || String(payload?.turnstile_token ?? payload?.turnstileToken ?? payload?.captcha_token ?? '').trim() || null ); } export const PAGE_DATA_META_FIELDS = new Set([ 'turnstile_token', 'turnstileToken', 'captcha_token', 'updated_by_label', ]); export function stripPageDataMetaFields(payload) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload; const cleaned = { ...payload }; for (const key of PAGE_DATA_META_FIELDS) { delete cleaned[key]; } return cleaned; }