1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-06-05 19:15:30 -04:00

Remove Cloudflare Turnstile integration and replace it with Vercel's BotID for spam protection in the contact form. Update environment variables and dependencies accordingly.

This commit is contained in:
2025-08-28 18:08:05 -04:00
parent d4f2b812ed
commit ab6b188a99
12 changed files with 533 additions and 500 deletions
-16
View File
@@ -60,13 +60,6 @@ export const env = createEnv({
/** Required. The destination email for contact form submissions. */
RESEND_TO_EMAIL: z.string().email(),
/**
* Required. Secret for Cloudflare `siteverify` API to validate a form's turnstile result on the backend.
*
* @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
*/
TURNSTILE_SECRET_KEY: z.string().default("1x0000000000000000000000000000000AA"),
},
client: {
/**
@@ -146,14 +139,6 @@ export const env = createEnv({
* @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/
NEXT_PUBLIC_SITE_TZ: z.string().default("America/New_York"),
/**
* Required. Site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. Falls back to
* testing keys if not set or in dev environment.
*
* @see https://developers.cloudflare.com/turnstile/troubleshooting/testing/
*/
NEXT_PUBLIC_TURNSTILE_SITE_KEY: z.string().default("1x00000000000000000000AA"),
},
experimental__runtimeEnv: {
NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL,
@@ -163,7 +148,6 @@ export const env = createEnv({
NEXT_PUBLIC_ONION_DOMAIN: process.env.NEXT_PUBLIC_ONION_DOMAIN,
NEXT_PUBLIC_SITE_LOCALE: process.env.NEXT_PUBLIC_SITE_LOCALE,
NEXT_PUBLIC_SITE_TZ: process.env.NEXT_PUBLIC_SITE_TZ,
NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY,
},
emptyStringAsUndefined: true,
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
+7 -33
View File
@@ -1,17 +1,16 @@
"use server";
import { env } from "@/lib/env";
import { headers } from "next/headers";
import { Resend } from "resend";
import { z } from "zod";
import siteConfig from "@/lib/config/site";
import { checkBotId } from "botid/server";
const ContactSchema = z
.object({
name: z.string().trim().min(1, { message: "Your name is required." }),
email: z.string().email({ message: "Your email address is required." }),
message: z.string().trim().min(15, { message: "Your message must be at least 15 characters." }),
"cf-turnstile-response": z.string().min(1, { message: "Are you sure you're not a robot...? 🤖" }),
})
.readonly();
@@ -28,6 +27,12 @@ export const send = async (state: ContactState, payload: FormData): Promise<Cont
console.debug("[server/resend] received payload:", payload);
try {
// BotID server-side verification
const verification = await checkBotId();
if (verification.isBot) {
return { success: false, message: "Bot detection failed. 🤖" };
}
const data = ContactSchema.safeParse(Object.fromEntries(payload));
if (!data.success) {
@@ -38,37 +43,6 @@ export const send = async (state: ContactState, payload: FormData): Promise<Cont
};
}
// try to get the client IP (for turnstile accuracy, not logging!) but no biggie if we can't
let remoteip;
try {
remoteip = (await headers()).get("x-forwarded-for");
} catch {} // eslint-disable-line no-empty
// validate captcha
const turnstileResponse = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
secret: env.TURNSTILE_SECRET_KEY,
response: data.data["cf-turnstile-response"],
remoteip,
}),
cache: "no-store",
});
if (!turnstileResponse || !turnstileResponse.ok) {
throw new Error(`[server/resend] turnstile validation failed: ${turnstileResponse.status}`);
}
const turnstileData = (await turnstileResponse.json()) as { success: boolean };
if (!turnstileData.success) {
return {
success: false,
message: "Did you complete the CAPTCHA? (If you're human, that is...)",
};
}
if (env.RESEND_FROM_EMAIL === "onboarding@resend.dev") {
// https://resend.com/docs/api-reference/emails/send-email
console.warn("[server/resend] 'RESEND_FROM_EMAIL' is not set, falling back to onboarding@resend.dev.");