Files

4.0 KiB

turnstile

Cloudflare Turnstile: widget (client) + weryfikacja (server). Klucze z SiteIntegrations (panel, nie env). Widget dostaje siteKey jako prop; verify czyta secret server-side.

Config

Brak opcji — pola turnstileSiteKey / turnstileSecretKey są w SiteIntegrations (tab Turnstile). Edytor wpisuje klucze w panelu.

Front — widget (client)

Import z @intecion/ipal-kit/client. siteKey pobierz server-side i przekaż jako prop:

// server component — pobiera publiczny siteKey z panelu
import { getSiteIntegrations } from '@intecion/ipal-kit'

const { turnstileSiteKey } = await getSiteIntegrations(payload)
// przekaż do swojego client-formularza → widget
'use client'
import { Turnstile } from '@intecion/ipal-kit/client'
import { useState } from 'react'

function ContactForm({ siteKey }) {
  const [token, setToken] = useState<string | null>(null)
  return (
    <form>
      {/* pola */}
      <Turnstile siteKey={siteKey} onToken={setToken} theme="auto" />
      <button disabled={!token}>Wyślij</button>
    </form>
  )
}

Widget ładuje skrypt Turnstile sam (bez next/script), zwraca token przez onToken (null przy wygaśnięciu/błędzie).

Front — verify (server)

import { verifyTurnstile } from '@intecion/ipal-kit/server'

const ok = await verifyTurnstile({ token, payload, ip })
if (!ok) {
  // odrzuć zgłoszenie
}

verifyTurnstile czyta secret z SiteIntegrations (Local API), woła Cloudflare. Zwraca false na każdy problem (brak klucza, sieć, odrzucenie) — traktuj false jako „nie ufaj temu zgłoszeniu". server-only gwarantuje, że nie trafi do bundla przeglądarki.

W formularzach zwykle nie wołasz verifyTurnstile wprost — robi to submitForm (patrz forms.md).

Uproszczone wpięcie — TurnstileProvider + useTurnstile (zalecane)

Jak CookieBanner: siteKey raz w layoutcie, formularze biorą z kontekstu. Koniec przekazywania siteKey do każdego formularza.

1. Provider w layoutcie (raz, siteKey z serwera)

// app/(frontend)/[locale]/layout.tsx (server)
import { TurnstileProvider } from '@intecion/ipal-kit/client'
import { getTurnstileSiteKey } from '@/lib/payload'   // Twój helper server-side

export default async function Layout({ children }) {
  const siteKey = await getTurnstileSiteKey()   // z panelu (SiteIntegrations)
  return (
    <html>
      <body>
        <TurnstileProvider siteKey={siteKey}>
          {children}
        </TurnstileProvider>
      </body>
    </html>
  )
}

2. Formularz — useTurnstile (zero plumbingu siteKey)

'use client'
import { useTurnstile } from '@intecion/ipal-kit/client'

function ContactForm() {
  const { token, TurnstileWidget, reset, enabled } = useTurnstile()

  async function handleSubmit(data) {
    const result = await submitForm({ ...data, turnstileToken: token })
    if (result.ok) reset()   // wyczyść token na następne wysłanie
  }

  return (
    <form onSubmit={...}>
      {/* pola formularza */}
      <TurnstileWidget />           {/* widget tam, gdzie ma być */}
      <button type="submit">Wyślij</button>
    </form>
  )
}

token → do submitForm. TurnstileWidget → wstaw gdzie ma być. reset() → po wysłaniu. enabled → false gdy brak klucza (dev bez Turnstile).

Dlaczego Turnstile NIE jest w pełni "wstaw i zapomnij" jak CookieBanner

CookieBanner jest samodzielny (renderuje się, zarządza zgodą, zero interakcji). Turnstile z natury jest CZĘŚCIĄ formularza — zwraca token, który formularz musi wysłać przy submit i zweryfikować server-side. Nie da się go „wstawić gdziekolwiek" — musi być w formularzu, przy jego logice wysyłki.

Provider+hook to maksimum uproszczenia: siteKey raz (jak CookieBanner), a w formularzu tylko <TurnstileWidget/> + token. Reszta (weryfikacja) dzieje się w submitForm automatycznie.

Stary sposób (nadal działa)

<Turnstile siteKey={...} onToken={...} /> bezpośrednio — jeśli potrzebujesz pełnej kontroli albo masz nietypowy przypadek. Provider to warstwa wygody nad tym.