Help us shape the future of Go! Take the 2026 Go Developer Survey

cloudflarecaptcha

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package cloudflarecaptcha verifies Cloudflare Turnstile tokens and optionally protects router handlers. It is vendor-local: no core CAPTCHA contract exists.

Construct one concurrent-safe Verifier with a server secret and trusted hostname allowlist. Verify returns evidence only after provider success and hostname/action checks. It does not authenticate users or replace CSRF checks.

Example:

verifier, err := cloudflarecaptcha.New("server-secret", []string{"example.com"})
if err != nil {
	return err
}
_, err = verifier.Verify(ctx, cloudflarecaptcha.Input{Token: token, ExpectedAction: "signup"})
return err

New does no I/O. Clients and transports are borrowed, never closed. Each verification has a ten-second default deadline, never follows redirects, and makes one HTTP attempt. Custom transports must honor request cancellation and must not implement retries. Turnstile tokens expire after five minutes and are single-use. Although Siteverify supports idempotency keys, this package does not send one or retry; obtain a fresh challenge after an uncertain result. Formatting a Verifier value or pointer emits a fixed redaction marker for its owned secret without traversing the borrowed transport or arbitrary caller objects. Native error causes remain inspectable, not safe to log indiscriminately.

For JSON requests, decode once in the application handler and call Verify before effects. Middleware requires explicit extraction, does not read the body itself, and never trusts proxy headers or forwards a remote IP.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrConfiguration = errors.New("turnstile configuration invalid")

ErrConfiguration indicates invalid local settings or provider credentials. It is a server fault, not a visitor rejection.

Example:

if errors.Is(err, cloudflarecaptcha.ErrConfiguration) {
	// Alert the operator without logging credentials.
}
View Source
var ErrRejected = errors.New("turnstile verification rejected")

ErrRejected means the visitor must supply a fresh acceptable challenge. It includes invalid token input and hostname/action mismatches.

Example:

if errors.Is(err, cloudflarecaptcha.ErrRejected) {
	// Ask the visitor to complete a new challenge.
}
View Source
var ErrUnavailable = errors.New("turnstile verification unavailable")

ErrUnavailable means verification could not be established. Never authorize on this error or blindly retry a potentially consumed token.

Example:

if errors.Is(err, cloudflarecaptcha.ErrUnavailable) {
	// Stop processing and offer a fresh challenge later.
}

Functions

func Middleware

func Middleware(verifier *Verifier, extract ExtractFunc, configure ...MiddlewareBuilderFunc) router.Middleware

Middleware extracts once, verifies once, then invokes next only on success. It returns safe router problems: rejection 403, unavailable 503, configuration 500. Returned errors preserve classification and original causes for errors.Is and errors.As; their text and HTTP bodies omit sensitive diagnostics. Extraction cancellation or deadline errors skip verification and next, return unavailable 503, and do not add ErrRejected. ErrConfiguration takes precedence. No body sniffing, proxy header trust, remote-IP forwarding or evidence context storage is performed. Use Verify directly after decoding JSON once in a handler. Nil verifier, extractor, next handler or construction callback is programmer misuse and panics. A zero verifier instead returns a configuration failure on requests. Callbacks run once directly in order and publication detaches drafts.

Example:

protect := cloudflarecaptcha.Middleware(verifier, func(request *http.Request) (string, error) {
	return request.Header.Get("X-Turnstile-Token"), nil
}, func(policy *cloudflarecaptcha.MiddlewareBuilder) {
	policy.Action("signup")
})
protected := protect(handler)
_ = protected

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder configures a new verifier. Retained drafts cannot change published verifiers; native transports remain shared and must be concurrency-safe.

Example:

v, err := cloudflarecaptcha.New("secret", []string{"example.com"}, func(connection *cloudflarecaptcha.Builder) {
	connection.Timeout(3 * time.Second)
})
_, _ = v, err

func (*Builder) HTTPClient

func (connection *Builder) HTTPClient(client *http.Client) *Builder

HTTPClient borrows client without changing it. Nil selects a fresh standard client. New snapshots its fields, replaces CheckRedirect to reject redirects, and retains its transport and jar; configure those before construction. Neither client nor transport is closed. A shorter native timeout still wins.

Example:

_, err := cloudflarecaptcha.New("secret", []string{"example.com"}, func(connection *cloudflarecaptcha.Builder) {
	connection.HTTPClient(&http.Client{Timeout: time.Second})
})
_ = err

func (*Builder) Timeout

func (connection *Builder) Timeout(timeout time.Duration) *Builder

Timeout sets the total operation limit. Zero restores ten seconds; negative values fail construction with ErrConfiguration. Earlier context deadlines win.

Example:

_, err := cloudflarecaptcha.New("secret", []string{"example.com"}, func(connection *cloudflarecaptcha.Builder) {
	connection.Timeout(2 * time.Second)
})
_ = err

type BuilderFunc

type BuilderFunc = func(*Builder)

BuilderFunc edits one construction draft. New calls callbacks directly once, synchronously in order; nil callbacks panic and callback panics propagate.

Example:

var policy cloudflarecaptcha.BuilderFunc = func(connection *cloudflarecaptcha.Builder) {
	connection.Timeout(time.Second)
}
_, err := cloudflarecaptcha.New("secret", []string{"example.com"}, policy)
_ = err

type ExtractFunc

type ExtractFunc = func(*http.Request) (string, error)

ExtractFunc obtains a sensitive token using application-owned request rules. Middleware does not provide a default extractor or inspect the body. Ordinary extraction errors mean ErrRejected. Errors wrapping context.Canceled or context.DeadlineExceeded mean ErrUnavailable, not visitor rejection. Errors wrapping ErrUnavailable or ErrConfiguration retain that classification; configuration takes precedence. Error strings are not rendered. Extractors shared by requests must be concurrency-safe.

Example:

var extract cloudflarecaptcha.ExtractFunc = func(request *http.Request) (string, error) {
	return request.Header.Get("X-Turnstile-Token"), nil
}
protect := cloudflarecaptcha.Middleware(verifier, extract)
_ = protect

type Input

type Input struct {
	// Token is the single-use widget response, from 1 through 2048 bytes.
	//
	// Example:
	//
	//	in := cloudflarecaptcha.Input{Token: token}
	Token string

	// ExpectedAction requires exact action equality when nonempty. Empty skips
	// only the action check, never the hostname check.
	//
	// Example:
	//
	//	in := cloudflarecaptcha.Input{Token: token, ExpectedAction: "login"}
	ExpectedAction string

	// RemoteIP is optional; an invalid address is omitted. Supply it only after
	// applying your trusted-proxy policy; no headers are interpreted here.
	//
	// Example:
	//
	//	in := cloudflarecaptcha.Input{Token: token, RemoteIP: netip.MustParseAddr("192.0.2.1")}
	RemoteIP netip.Addr
}

Input describes one verification. Tokens are sensitive and must not be logged. ExpectedAction must come from server policy, not from untrusted request data.

Example:

in := cloudflarecaptcha.Input{Token: token, ExpectedAction: "signup"}
_, err := verifier.Verify(ctx, in)
_ = err

type MiddlewareBuilder

type MiddlewareBuilder struct {
	// contains filtered or unexported fields
}

MiddlewareBuilder selects the immutable request policy. It owns no request data; mutating a retained draft after construction does not affect middleware.

Example:

protect := cloudflarecaptcha.Middleware(verifier, extract, func(policy *cloudflarecaptcha.MiddlewareBuilder) {
	policy.Action("signup")
})
_ = protect

func (*MiddlewareBuilder) Action

func (policy *MiddlewareBuilder) Action(action string) *MiddlewareBuilder

Action replaces the exact expected provider action. Empty disables only the action comparison. Choose it from server policy, not from request input.

Example:

protect := cloudflarecaptcha.Middleware(verifier, extract, func(policy *cloudflarecaptcha.MiddlewareBuilder) {
	policy.Action("login")
})
_ = protect

type MiddlewareBuilderFunc

type MiddlewareBuilderFunc = func(*MiddlewareBuilder)

MiddlewareBuilderFunc edits a fresh policy directly once in order. Nil callbacks panic; callback panics propagate without being recovered.

Example:

var signup cloudflarecaptcha.MiddlewareBuilderFunc = func(policy *cloudflarecaptcha.MiddlewareBuilder) {
	policy.Action("signup")
}
protect := cloudflarecaptcha.Middleware(verifier, extract, signup)
_ = protect

type Result

type Result struct {
	// Hostname is the exact configured hostname matched by the response.
	//
	// Example:
	//
	//	hostname := evidence.Hostname
	Hostname string

	// Action is the provider action, possibly empty when not required.
	//
	// Example:
	//
	//	action := evidence.Action
	Action string

	// ChallengeAt is the provider's RFC3339 challenge timestamp, not local time.
	//
	// Example:
	//
	//	solvedAt := evidence.ChallengeAt
	ChallengeAt time.Time

	// CData is the provider's custom data; treat it as untrusted and sensitive.
	//
	// Example:
	//
	//	data := evidence.CData
	CData string
}

Result is provider evidence returned only on successful verification. CData remains client-originated metadata, not an authorization claim or a score.

Example:

evidence, err := verifier.Verify(ctx, cloudflarecaptcha.Input{Token: token})
if err != nil {
	return err
}
_ = evidence.Hostname

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier is an immutable concurrent-safe Turnstile verifier constructed by New. Its zero value and a nil receiver fail closed with ErrConfiguration.

Example:

v, err := cloudflarecaptcha.New("secret", []string{"example.com"})
if err != nil {
	return err
}
_, err = v.Verify(ctx, cloudflarecaptcha.Input{Token: token})
return err

func New

func New(secret string, hostnames []string, configure ...BuilderFunc) (*Verifier, error)

New validates completed settings without network I/O. Secret must be nonblank; hostnames must contain at least one nonempty exact hostname, without schemes, ports, wildcards, paths or whitespace. Matching is case-sensitive and has no implicit subdomain support. The allowlist is copied before callbacks run. Callbacks run once in order even when required inputs are invalid; nil entries panic. Empty callback slices omit edits. The returned verifier owns its settings independently of retained drafts and has no lifecycle or readiness methods.

Example:

v, err := cloudflarecaptcha.New("server-secret", []string{"example.com"})
if err != nil {
	return err
}
_, err = v.Verify(ctx, cloudflarecaptcha.Input{Token: token})
return err

func (Verifier) Format

func (verifier Verifier) Format(state fmt.State, _ rune)

Format redacts the verifier's owned secret in value and pointer diagnostics. It does not traverse the borrowed native transport, jar or caller objects and performs no I/O other than writing the fixed diagnostic marker.

Example:

text := fmt.Sprintf("%#v", verifier)
_ = text // [REDACTED Turnstile verifier]

func (*Verifier) Verify

func (verifier *Verifier) Verify(ctx context.Context, input Input) (Result, error)

Verify makes one POST to Cloudflare's fixed HTTPS Siteverify endpoint. A nil error means provider success, a nonzero timestamp, a trusted hostname, and the requested action matched. Every failure returns a zero Result. Empty or oversized tokens reject before I/O. Responses are limited to 64 KiB; malformed, contradictory and unknown failure responses fail closed as ErrUnavailable. Context cancellation/deadlines and native causes remain inspectable through errors.Is/As, but Error text never includes token, secret or raw response. Do not log unwrapped causes: custom transports may put secrets in errors. Nil context is programmer misuse and panics. No retry or idempotency key is used.

Example:

evidence, err := verifier.Verify(ctx, cloudflarecaptcha.Input{Token: token, ExpectedAction: "login"})
if err != nil {
	return err
}
_ = evidence.Hostname
Example

ExampleVerifier_Verify demonstrates successful verification without external network access. Production callers use their widget token and server secret.

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"

	"arkoren.dev/driver/cloudflare/cloudflarecaptcha"
)

func main() {
	client := &http.Client{Transport: transport(func(*http.Request) (*http.Response, error) {
		return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(validResponse))}, nil
	})}
	verifier, err := cloudflarecaptcha.New("server-secret", []string{"example.com"}, func(connection *cloudflarecaptcha.Builder) {
		connection.HTTPClient(client)
	})

	if err != nil {
		fmt.Println("invalid configuration")

		return
	}

	evidence, err := verifier.Verify(context.Background(), cloudflarecaptcha.Input{
		Token: "widget-token", ExpectedAction: "signup",
	})

	if err != nil {
		fmt.Println("verification failed")

		return
	}

	fmt.Println(evidence.Hostname, evidence.Action)
}

// transport substitutes deterministic wire behavior without real network access.
type transport func(*http.Request) (*http.Response, error)

// RoundTrip delegates one request to the test's wire assertion.
func (transport transport) RoundTrip(request *http.Request) (*http.Response, error) {
	return transport(request)
}

// validResponse is provider-native evidence shared by success scenarios.
const validResponse = `{"success":true,"hostname":"example.com","action":"signup","challenge_ts":"2026-10-24T10:00:00Z","cdata":"opaque"}`
Output:
example.com signup

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL