Replacing captcha with turnstile server side Cloudflare worker code

Hello there,

I had previously implemented a client-side CAPTCHA solution, which I have now successfully replaced. However, I’m facing challenges with the server-side code, which was developed by a developer who has since closed down his business.

I’m seeking assistance to replace my current CAPTCHA with Cloudflare’s CAPTCHA service. While I’ve managed to load and verify the CAPTCHA successfully, I encounter a ‘405 Method Not Allowed’ error when trying to submit the form.

Below is the existing server-side code that I need help with:

// Config
const whitelist = {
    example.com': '[email protected]',
    'example.pages.dev': '[email protected]',
}
const blacklistedFields = ['g-recaptcha-response', 'h-captcha-response']
const templateID = 10
// End of config

const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Methods': 'POST',
    'Access-Control-Max-Age': '86400',
}

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request))
})

/**
 * Handle POST requests only from whitelisted domains
 * @param {Request} request
 * @returns {Response}
 */
function handleRequest(request) {
    // Handle CORS preflight requests
    if (request.method === 'OPTIONS') return verifyOrigin(request)

    // Only handle POST requests
    if (request.method === 'POST') return handlePostRequest(request)

    // Block all other methods
    return new Response('Method Not Allowed', {
        headers: corsHeaders,
        status: 405
    })
}

/**
 * Block requests from hosts which are not in the whitelist
 * @param {Request} request
 * @returns {Response}
 */
function verifyOrigin(request) {
    const origin = request.headers.get('Origin')
    if (origin !== null) {
        const url = new URL(origin)
        if (!(url.hostname in whitelist))
            return new Response(`${url.hostname} is blacklisted!`, {
                headers: corsHeaders,
                status: 403
            })

        return new Response(null, {
            headers: corsHeaders,
            status: 200,
        })
    }

    return new Response('Invalid request!', {
        headers: corsHeaders,
        status: 400
    })
}

/**
 * Handle URL encoded POST data
 * @param {Request} request
 * @returns {Response}
 */
function handlePostRequest(request) {
    const response = verifyOrigin(request)
    if (response.status !== 200) return response

    // Block requests having non-form-data body
    const contentType = request.headers.get('content-type')
    let errorMessage = null
    if (!contentType) errorMessage = 'Content-Type is not set!'

    const allowedTypes = ['multipart/form-data', 'application/x-www-form-urlencoded']
    if (!(allowedTypes.some(type => contentType.includes(type))))
        errorMessage = 'Data format not supported'

    if (errorMessage)
        return new Response(errorMessage, {
            headers: corsHeaders,
            status: 400
        })

    return parsePayload(request)
}

/**
 * Parse JSON encoded payload in the POST request.
 * @param {Request} request
 * @returns {Response}
 */
async function parsePayload(request) {
    const url = new URL(request.headers.get('Origin'))

    const data = await request.formData()
    const fields = []
    for (const [key, value] of data.entries())
        if (!blacklistedFields.includes(key))
            fields.push({ name: key, value: value })

    var response = await verifyCaptcha(data.get('h-captcha-response'))
    const responseData = await response.json()
    if (!responseData['success'])
        return new Response(`hCaptcha: ${responseData['error-codes'].join(' ')}`, {
            headers: corsHeaders,
            status: 400
        })

    var body = {
        to: [{ email: whitelist[url.hostname] }],
        templateId: templateID,
        params: { fields: fields }
    }

    if (data.has('email')) {
        body['replyTo'] = {
            email: data.get('email')
        }

        if (data.has('name')) {
            body.replyTo['name'] = data.get('name')
        }
    }

    response = await callAPI(body)
    if (response.status === 201)
        return new Response('Thank you! The form was submitted successfully.', {
            headers: corsHeaders,
            status: 200,
        })

    return new Response('The form could not be submitted.', {
        headers: corsHeaders,
        status: response.status
    })
}

/**
 * Call the API with passed `body` to send email (this is an email service used via api)
// api code here.. removed to protect api key


 * Verify hCaptcha response token
 * @param {String} token
 * @returns {Response}
 */
async function verifyCaptcha(token) {
    const body = {
        response: token,
        secret: '000000' //the secret key is removed to protect the key from misuse 
    }
    const request = new Request('https://hcaptcha.com/siteverify', {
        method: 'POST',
        headers: { 'content-type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams(body).toString(),
    })

    const response = await fetch(request)
    return response
}

I appreciate any guidance or assistance you can provide to resolve this issue.

Thank you.

anyone who can help solve this so I can switch from h captcha to Cloudflare captcha thanks

Have you read the developer documentation: Server-side validation · Cloudflare Turnstile docs

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.