1+ package main
2+
3+ import (
4+ "bytes"
5+ "context"
6+ "encoding/json"
7+ "fmt"
8+ "log"
9+ "net/http"
10+ "time"
11+ )
12+
13+ type TurnstileResponse struct {
14+ Success bool `json:"success"`
15+ ErrorCodes []string `json:"error-codes,omitempty"`
16+ }
17+
18+ var turnStileHttpClient = & http.Client {Timeout : 15 * time .Second }
19+
20+ func ValidateTurnstile (ctx context.Context , token string , remoteIP string , secretKey string ) error {
21+ if secretKey == "" {
22+ log .Printf ("warning: Turnstile secretKey is empty, skipping validation" )
23+ return nil
24+ }
25+ if remoteIP == "" {
26+ log .Printf ("warning: Turnstile remoteIP is empty, skipping validation" )
27+ return nil
28+ }
29+ requestBody , err := json .Marshal (map [string ]string {
30+ "secret" : secretKey ,
31+ "response" : token ,
32+ "remoteip" : remoteIP ,
33+ })
34+ if err != nil {
35+ return fmt .Errorf ("failed to marshal request body: %w" , err )
36+ }
37+
38+ req , err := http .NewRequestWithContext (ctx , "POST" , "https://challenges.cloudflare.com/turnstile/v0/siteverify" , bytes .NewBuffer (requestBody ))
39+ if err != nil {
40+ return fmt .Errorf ("failed to create request: %w" , err )
41+ }
42+ req .Header .Set ("Content-Type" , "application/json" )
43+
44+ resp , err := turnStileHttpClient .Do (req )
45+ if err != nil {
46+ return fmt .Errorf ("failed to send request: %w" , err )
47+ }
48+ defer resp .Body .Close ()
49+
50+ if resp .StatusCode != http .StatusOK {
51+ return fmt .Errorf ("unexpected status code: %v" , resp .StatusCode )
52+ }
53+
54+ var result TurnstileResponse
55+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
56+ return fmt .Errorf ("failed to parse response: %w" , err )
57+ }
58+
59+ if ! result .Success {
60+ return fmt .Errorf ("turnstile validation failed: %v" , result .ErrorCodes )
61+ }
62+
63+ return nil
64+ }
0 commit comments