Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07. Major areas: - Outbound webhooks / Zapier: schema + signed delivery with retries, public v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain. - Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when Spaces is unconfigured instead of silently using ephemeral disk. - Integrations & features (concurrent work): accounting (QuickBooks/Xero), e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding, expanded legal pages. - DB migrations 0006–0009. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
969d5d4c8a
commit
c9968531e4
@@ -0,0 +1,37 @@
|
||||
const VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||||
|
||||
/**
|
||||
* Verifies a Cloudflare Turnstile token server-side against the siteverify API.
|
||||
*
|
||||
* Fails CLOSED when Turnstile is configured (secret present) but the token is
|
||||
* missing or invalid. Fails OPEN only when `TURNSTILE_SECRET_KEY` is unset — so
|
||||
* environments that haven't configured Turnstile keep working, matching how the
|
||||
* other optional integrations (Stripe / OpenAI / SMTP email) degrade in this app.
|
||||
*/
|
||||
export async function verifyTurnstile(
|
||||
token: string | undefined | null,
|
||||
remoteIp?: string | null
|
||||
): Promise<boolean> {
|
||||
const secret = process.env.TURNSTILE_SECRET_KEY
|
||||
if (!secret) return true // integration disabled — do not block auth
|
||||
if (!token) return false
|
||||
|
||||
try {
|
||||
const body = new URLSearchParams()
|
||||
body.append("secret", secret)
|
||||
body.append("response", token)
|
||||
if (remoteIp) body.append("remoteip", remoteIp)
|
||||
|
||||
const res = await fetch(VERIFY_URL, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body,
|
||||
cache: "no-store",
|
||||
})
|
||||
const data = (await res.json()) as { success?: boolean }
|
||||
return data.success === true
|
||||
} catch {
|
||||
// Network / provider error — fail closed so a challenge can't be bypassed.
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user