diff --git a/examples/edge-functions/supabase/functions/_shared/cors.ts b/examples/edge-functions/supabase/functions/_shared/cors.ts index 4be3f02881e..2ac4d89b14a 100644 --- a/examples/edge-functions/supabase/functions/_shared/cors.ts +++ b/examples/edge-functions/supabase/functions/_shared/cors.ts @@ -1,4 +1,4 @@ export const corsHeaders = { 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', } diff --git a/examples/edge-functions/supabase/functions/cloudflare-turnstile/README.md b/examples/edge-functions/supabase/functions/cloudflare-turnstile/README.md new file mode 100644 index 00000000000..4d8bc5008cd --- /dev/null +++ b/examples/edge-functions/supabase/functions/cloudflare-turnstile/README.md @@ -0,0 +1,25 @@ +# Cloudflare Turnstile + +Turnstile is Cloudflare's CAPTCHA alternative: https://developers.cloudflare.com/turnstile/get-started/ + +## Setup + +- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/ +- Add the Cloudflare Turnstile widget to your site: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/ + +## Deploy the server-side validation Edge Functions + +- https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + +```bash +supabase functions deploy cloudflare-turnstile +supabase secrets set CLOUDFLARE_TURNSTILE_SECRET_KEY=your_secret_key +``` + +## Invoke the function from your site + +```js +const { data, error } = await supabase.functions.invoke('cloudflare-turnstile', { + body: { token }, +}) +``` diff --git a/examples/edge-functions/supabase/functions/cloudflare-turnstile/index.ts b/examples/edge-functions/supabase/functions/cloudflare-turnstile/index.ts new file mode 100644 index 00000000000..5d831d18ab7 --- /dev/null +++ b/examples/edge-functions/supabase/functions/cloudflare-turnstile/index.ts @@ -0,0 +1,61 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { corsHeaders } from '../_shared/cors.ts' + +console.log(`Function "cloudflare-turnstile" up and running!`) + +function ips(req: Request) { + return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/) +} + +serve(async (req) => { + // This is needed if you're planning to invoke your function from a browser. + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }) + } + + try { + const { token } = await req.json() + if (!token) throw new Error('Missing token!') + + const clientIps = ips(req) || [''] + + // Validate the token by calling the + // "/siteverify" API endpoint. + const formData = new FormData() + formData.append('secret', Deno.env.get('CLOUDFLARE_TURNSTILE_SECRET_KEY') ?? '') + formData.append('response', token) + formData.append('remoteip', clientIps[0]) + + const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify' + const result = await fetch(url, { + body: formData, + method: 'POST', + }) + + const outcome = await result.json() + console.log(outcome) + if (outcome.success) { + return new Response(JSON.stringify(outcome), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }) + } + + throw new Error('Turnstile validation failed!') + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +}) + +// To invoke: +// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \ +// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ +// --header 'Content-Type: application/json' \ +// --data '{"token":"cf-turnstile-response"}'