Skip to main content

Turnstile with Workers

还没翻译译自英文(developers.cloudflare.com)本页 12 段都还没译

JavaScriptTypeScriptHonoPython

export default {
	async fetch(request, env) {
		const SITE_KEY = env.SITE_KEY; // The Turnstile Sitekey of your widget (pass as env or secret)
		const TURNSTILE_ATTR_NAME = "your_id_to_replace"; // The id of the element to put a Turnstile widget in
		let res = await fetch(request);

		// Instantiate the API to run on specific elements, for example, `head`, `div`
		let newRes = new HTMLRewriter()

			// `.on` attaches the element handler and this allows you to match on element/attributes or to use the specific methods per the API
			.on("head", {
				element(element) {
					// In this case, you are using `append` to add a new script to the `head` element
					element.append(
						`<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>`,
						{ html: true },
					);
				},
			})
			.on("div", {
				element(element) {
					// Add a turnstile widget element into if an element with the id of TURNSTILE_ATTR_NAME is found
					if (element.getAttribute("id") === TURNSTILE_ATTR_NAME) {
						element.append(
							`<div class="cf-turnstile" data-sitekey="${SITE_KEY}"></div>`,
							{ html: true },
						);
					}
				},
			})
			.transform(res);
		return newRes;
	},
};

Note

This is only half the implementation for Turnstile. The corresponding token that is a result of a widget being rendered also needs to be verified using the Siteverify API. Refer to the example below for one such implementation.

async function handlePost(request, env) {
    const body = await request.formData();
    // Turnstile injects a token in `cf-turnstile-response`.
    const token = body.get('cf-turnstile-response');
    const ip = request.headers.get('CF-Connecting-IP');

    // Validate the token by calling the `/siteverify` API.
    let formData = new FormData();

    // `secret_key` here is the Turnstile Secret key, which should be set using Wrangler secrets
    formData.append('secret', self.env.SECRET_KEY);
    formData.append('response', token);
    formData.append('remoteip', ip); //This is optional.

    const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
    const result = await fetch(url, {
        body: formData,
        method: 'POST',
    });

    const outcome = await result.json();

    if (!outcome.success) {
        return new Response('The provided Turnstile token was not valid!', { status: 401 });
    }
    // The Turnstile token was successfully validated. Proceed with your application logic.
    // Validate login, redirect user, etc.

	// Clone the original request with a new body
    const newRequest = new Request(request, {
        body: request.body, // Reuse the body
        method: request.method,
        headers: request.headers
    });

    return await fetch(newRequest);
}

export default {
	async fetch(request, env) {
		const SITE_KEY = env.SITE_KEY; // The Turnstile Sitekey of your widget (pass as env or secret)
		const TURNSTILE_ATTR_NAME = 'your_id_to_replace'; // The id of the element to put a Turnstile widget in

		let res = await fetch(request)

		if (request.method === 'POST') {
			return handlePost(request, env)
		}

		// Instantiate the API to run on specific elements, for example, `head`, `div`
		let newRes = new HTMLRewriter()
			// `.on` attaches the element handler and this allows you to match on element/attributes or to use the specific methods per the API
			.on('head', {
				element(element) {

					// In this case, you are using `append` to add a new script to the `head` element
					element.append(`<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>`, { html: true });
				},
			})
			.on('div', {
				element(element) {
					// You are using the `getAttribute` method here to retrieve the `id` or `class` of an element
					if (element.getAttribute('id') === <NAME_OF_ATTRIBUTE>) {
						element.append(`<div class="cf-turnstile" data-sitekey="${SITE_KEY}" data-theme="light"></div>`, { html: true });
					}
				},
			})
			.transform(res);
		return newRes
	}
}

Prevent potential errors when accessing request.body

The body of a Request can only be accessed once. If you previously used request.formData() in the same request, you may encounter a TypeError when attempting to access request.body.

To avoid errors, create a clone of the Request object with request.clone() for each subsequent attempt to access a Request's body. Keep in mind that Workers have a memory limit of 128 MB per Worker and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider using Streams.