Turnstile<\/a> from Cloudflare.<\/p>\n\n\n\nYou don’t have to click on pictures or write an alphanumeric code.<\/p>\n\n\n\n
It’s a fully automated validation, and manual verification is a simple checkbox.<\/p>\n\n\n\n
Wow, that’s an improvement.<\/p>\n\n\n\n
The form<\/h2>\n\n\n\n
Specific point: no action!
Effectively, it’s a static form handled automatically by Cloudflare.<\/p>\n\n\n\n
<form data-static-form-name=\"contact\">\n <div class=\"mb-3\">\n <label for=\"tags\" class=\"form-label\">Tags<\/label>\n <div id=\"tagsHelp\" class=\"form-text\">Multiple values can be selected<\/div>\n <select class=\"form-select\" name=\"tags\" id=\"tags\" multiple aria-describedby=\"tagsHelp\">\n <option value=\"C2C\">C2C<\/option>\n <option value=\"Contract\">Contract<\/option>\n <option value=\"Digital Nomad friendly\">Digital Nomad friendly<\/option>\n <option value=\"Full Remote\">Full Remote<\/option>\n <option value=\"Permanent\">Permanent<\/option>\n <option value=\"Other\">Other<\/option>\n <\/select>\n <\/div>\n <div class=\"mb-3\">\n <label for=\"first_name\" class=\"form-label\">First name<\/label>\n <input type=\"text\" class=\"form-control\" name=\"first_name\" id=\"first_name\">\n <\/div>\n <div class=\"mb-3\">\n <label for=\"last_name\" class=\"form-label\">Last name<\/label>\n <input type=\"text\" class=\"form-control\" name=\"last_name\" id=\"last_name\">\n <\/div>\n <div class=\"mb-3\">\n <label for=\"company\" class=\"form-label\">Company<\/label>\n <input type=\"text\" class=\"form-control\" name=\"company\" id=\"company\">\n <\/div>\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">Email address<\/label>\n <input type=\"email\" class=\"form-control\" name=\"email\" id=\"email\" aria-describedby=\"emailHelp\">\n <div id=\"emailHelp\" class=\"form-text\">Will be used to answer you.<\/div>\n <\/div>\n <div class=\"mb-3\">\n <label for=\"message\" class=\"form-label\">Message<\/label>\n <textarea class=\"form-control\" name=\"message\" id=\"message\"><\/textarea>\n <\/div>\n <script src=\"https:\/\/challenges.cloudflare.com\/turnstile\/v0\/api.js\" async defer><\/script>\n <div class=\"cf-turnstile\" data-sitekey=\"{{ $cloudflare.turnstile_key }}\" data-callback=\"javascriptCallback\" data-name=\"contact-form\"><\/div>\n <button type=\"submit\" class=\"btn btn-info\">Submit<\/button>\n<\/form><\/code><\/pre>\n\n\n\nBasic form with two lines dedicated to the captcha:<\/p>\n\n\n\n
\n- load the JS file<\/li>\n\n\n\n
- the div where the captcha will be visible<\/li>\n<\/ol>\n\n\n\n
The function<\/h2>\n\n\n\n
At the root of your repository, a little step:<\/p>\n\n\n\n
mkdir functions\necho '{\"compilerOptions\":{\"target\":\"ES2020\",\"module\":\"CommonJS\",\"lib\":[\"ES2020\"],\"types\":[\"@cloudflare\/workers-types\"]}}\n' > functions\/tsconfig.json<\/code><\/pre>\n\n\n\nMy function file is contact.ts<\/code>, which applies on \/contact<\/code>.<\/p>\n\n\n\nBig difference between a function and a worker<\/h3>\n\n\n\n
You can’t access environment variables\/secrets, so you need to add them directly to your code. It’s the critical part that I learned doing this first function.<\/p>\n\n\n\n
My big function<\/h3>\n\n\n\n
I want a function doing some simple steps:<\/p>\n\n\n\n
\n- Verify the captcha (token)<\/li>\n\n\n\n
- Get form values<\/li>\n\n\n\n
- POST form values to an API endpoint (I don’t use emails!)<\/li>\n\n\n\n
- Notify me on Telegram<\/li>\n\n\n\n
- Thank you page<\/li>\n<\/ol>\n\n\n\n
Static forms<\/h4>\n\n\n\n
I use the static-forms<\/code> provided by Cloudflare with a small change to have the possibility to run async<\/strong> code.<\/p>\n\n\n\nimport staticFormsPlugin from \"@cloudflare\/pages-plugin-static-forms\";\n\nexport const onRequest: PagesFunction = staticFormsPlugin({\n respondWith: async ({formData, name}) => {<\/code><\/pre>\n\n\n\nA quick override adding async<\/code> at the respondWith<\/code> .<\/p>\n\n\n\nVerify the captcha (token)<\/h4>\n\n\n\nconst turnstile = `secret=${MY_SECRET}&response=${formData.get(\"cf-turnstile-response\")}`;\nconst result = await fetch(\"https:\/\/challenges.cloudflare.com\/turnstile\/v0\/siteverify\", {\n body: turnstile.toString(), headers: {\"Content-Type\": \"application\/x-www-form-urlencoded\"}, method: 'POST',\n}).then(function (response) {\n return response.json();\n});\nconst outcome = await result;\nif (outcome[\"success\"]) {}<\/code><\/pre>\n\n\n\nIt’s like the snippet in the documentation, but I needed to add the header, or it won’t work.
When the captcha is ok, I go to the next step<\/p>\n\n\n\n
Parsing form values<\/h4>\n\n\n\n
I prepare the body for the request.<\/p>\n\n\n\n
const body = {\n fields: {\n Tags: formData.getAll(\"tags\"),\n \"First Name\": formData.get(\"first_name\"),\n \"Last Name\": formData.get(\"last_name\"),\n Company: formData.get(\"company\"),\n \"Email Address\": formData.get(\"email\"),\n Message: formData.get(\"message\")\n }\n};<\/code><\/pre>\n\n\n\nI can have multiple tags, so I need to do the getAll.<\/p>\n\n\n\n
Preparing Telegram notification body<\/h4>\n\n\n\nconst telegram_message = `New contact form submitted by ${formData.get(\"email\")}`\nconst telegram_body = {\n chat_id: 0000000000, text: telegram_message.toString(), allow_sending_without_reply: true\n};<\/code><\/pre>\n\n\n\nSend form values to the API<\/h4>\n\n\n\nawait fetch(\"https:\/\/MY_SUBDOMAIN\/MY_ENDPOINT\", {\n method: \"POST\", body: JSON.stringify(body), headers: {\n x-autho: `${MY_AUTH_TOKEN}`, \"Content-type\": `application\/json`,\n }\n});<\/code><\/pre>\n\n\n\nSend Telegram notification<\/h4>\n\n\n\nawait fetch(\"https:\/\/api.telegram.org\/botTELEGRAM_BOT_TOKEN\/sendMessage\", {\n body: JSON.stringify(telegram_body), method: \"POST\", headers: {\n \"Content-Type\": \"application\/json\"\n },\n});<\/code><\/pre>\n\n\n\nThank you page<\/h4>\n\n\n\n
Just creating a Response with the good Content-Type<\/p>\n\n\n\n
const res = await new Response(`<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <title>Thank you!<\/title>\n <\/head\n <body>\n <\/body>\n <\/html>`);\n res.headers.set(\"Content-Type\", 'text\/html;charset=UTF-8');\n return res<\/code><\/pre>\n\n\n\nConclusion<\/h2>\n\n\n\n
Functions is the perfect extension of Pages without needing to use a full-featured worker.<\/p>\n\n\n\n
In addition, I’m impatient to have Rust\/WASM available for Functions because JS\/TS isn’t for me.<\/p>\n","protected":false},"excerpt":{"rendered":"
During the weekend, I decided to change my contact form. Objective: replacing the embed form via an iframe with a static form What’s used: BotFather is the tool to create a Telegram bot.<\/p>\n","protected":false},"author":1,"featured_media":77,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"saved_in_kubio":false,"footnotes":""},"categories":[28],"tags":[23],"_links":{"self":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/265"}],"collection":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/comments?post=265"}],"version-history":[{"count":1,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/265\/revisions"}],"predecessor-version":[{"id":266,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/265\/revisions\/266"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/media\/77"}],"wp:attachment":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/media?parent=265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/categories?post=265"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/tags?post=265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}