0

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!

Chirdeep Tomar
  • 4,281
  • 8
  • 37
  • 66

0 Answers0