I have a simple contact us form on the website which is not working with Cloudflare Turnstile.
If I leave the page and come back to refresh it after a while, then turnstile widget loads and turns green with a tick. Post that I am able to submit the form otherwise I get the error:
The provided Turnstile token was not valid!
import type { ActionArgs, SerializeFrom } from '@remix-run/node';
import { json } from '@remix-run/node';
import { Form, useActionData, useLoaderData } from '@remix-run/react';
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
import type { ExternalScriptsFunction } from 'remix-utils';
type Props = {}
interface Response {
success?: string,
errors?: {
email?: string
name?: string
message?: string
server?: string
}
}
let scripts: ExternalScriptsFunction<SerializeFrom<typeof loader>> = () => {
return [
{
src: "https://challenges.cloudflare.com/turnstile/v0/api.js",
async: true,
defer: true
},
];
};
export let handle = { scripts };
export async function loader() {
return json({
TURNSTILE_SITE_KEY: process.env.TURNSTILE_SITE_KEY!
});
}
export async function action({ request }: ActionArgs) {
const formData = await request.formData();
const token = formData.get('cf-turnstile-response') as string;
formData.append('secret', process.env.TURNSTILE_SECRET_KEY!);
formData.append('response', token);
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const result = await fetch(url, {
body: formData,
method: 'POST'
})
const outcome = await result.json();
let response: Response = {
errors: {}
}
if (!outcome.success) {
console.log("Failed to validate request.")
console.log(outcome.success)
response.errors!.server = "The provided Turnstile token was not valid!"
return json(response, { status: 403 })
}
const name = formData.get("name")
const email = formData.get("email")
const message = formData.get("message")
// validate the fields
if (typeof email !== "string" || !email.includes("@")) {
response.errors!.email =
"That doesn't look like an email address";
}
if (typeof name !== "string" || !name?.length) {
response.errors!.name = "Name is empty";
}
if (typeof message !== "string" || !message?.length) {
response.errors!.message = "Message is empty";
}
// return data if we have errors
if (response.errors!.message != undefined || response.errors!.email != undefined || response.errors!.name != undefined) {
return json(response, { status: 422 });
}
// Send the email
}
const Contact = (props: Props) => {
const { TURNSTILE_SITE_KEY } = useLoaderData<typeof loader>()
const response = useActionData<typeof action>()
return (
<section className="relative flex flex-wrap lg:h-screen lg:items-center">
<div className="w-full px-4 sm:px-6 lg:w-1/2 lg:px-8 lg:py-24">
<div className="mx-auto max-w-lg text-center">
<h1 className="text-2xl sm:text-3xl text-gray-700">Let us know!</h1>
<p className="mt-4 text-gray-500">
Please contact us with any feedback and suggestions.
</p>
</div>
<div className="mx-auto max-w-lg text-center mt-20">
{response?.success ? (
<div className='text-green-800'>Thank you, we have received your feedback!</div>
) :
<div className='text-red-700'>{response?.errors?.server}</div>
}
<Form method='post' className="mx-auto mt-8 mb-0 max-w-md space-y-4 contact-form">
<div>
<label htmlFor="name" className="sr-only">Name</label>
<div className="relative">
<input
name="name"
type="name"
className="w-full rounded-lg border-gray-200 p-4 pr-12 text-sm shadow-sm"
placeholder="Enter name"
/>
{response?.errors?.name ? (
<span role="alert" className='text-red-800 dark:text-red-300'>{response?.errors?.name}</span>
) : null}
</div>
</div>
<div>
<label htmlFor="email" className="sr-only">Email</label>
<div className="relative">
<input
name="email"
type="email"
className="w-full rounded-lg border-gray-200 p-4 pr-12 text-sm shadow-sm"
placeholder="Enter email"
/>
{response?.errors?.email ? (
<span role="alert" className='text-red-800 dark:text-red-300'>{response?.errors?.email}</span>
) : null}
</div>
</div>
<div>
<label htmlFor="message" className="sr-only">Message</label>
<div className="relative">
<textarea
name='message'
className="w-full rounded-lg border-gray-200 p-4 pr-12 text-sm shadow-sm"
placeholder="Type in your message"
/>
{response?.errors?.message ? (
<span role="alert" className='text-red-800 dark:text-red-300'>{response?.errors?.message}</span>
) : null}
</div>
</div>
{TURNSTILE_SITE_KEY &&
<div className="cf-turnstile" data-sitekey={TURNSTILE_SITE_KEY} data-theme="light"></div>
}
<div className="flex items-center justify-between">
<button
type="submit"
className="ml-3 inline-block rounded-lg bg-blue-500 px-5 py-3 text-sm font-medium text-white"
>
Message
</button>
</div>
</Form>
</div>
</div >
</section >
)
}
export default Contact
Looking at the network tab, first time the call to a cloudflare api is pending
https://challenges.cloudflare.com/cdn-cgi/challenge-platform/h/b/flow/ov1/xxx:xxx:xxx/xxx/xxx
After a while when I refresh the page, upon reload, the same call succeeds and hence the capctha loads and turns to green.
Not sure, what is going wrong? Thanks for your help!