2026-04-02 15:49:30 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"time"
|
2026-04-02 18:02:25 +00:00
|
|
|
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
2026-04-02 15:49:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const turnstileVerifyURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
|
|
|
|
|
2026-04-02 18:02:25 +00:00
|
|
|
var turnstileClient = &http.Client{Timeout: 10 * time.Second}
|
|
|
|
|
|
2026-04-02 15:49:30 +00:00
|
|
|
type turnstileResponse struct {
|
|
|
|
|
Success bool `json:"success"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VerifyTurnstile verifies a Cloudflare Turnstile token with the given secret key.
|
|
|
|
|
func VerifyTurnstile(secretKey, token, remoteIP string) bool {
|
|
|
|
|
form := url.Values{
|
|
|
|
|
"secret": {secretKey},
|
|
|
|
|
"response": {token},
|
|
|
|
|
}
|
|
|
|
|
if remoteIP != "" {
|
|
|
|
|
form.Set("remoteip", remoteIP)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 18:02:25 +00:00
|
|
|
resp, err := turnstileClient.PostForm(turnstileVerifyURL, form)
|
2026-04-02 15:49:30 +00:00
|
|
|
if err != nil {
|
2026-04-02 18:02:25 +00:00
|
|
|
logger.Warning("Turnstile verification request failed (network error):", err)
|
2026-04-02 15:49:30 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
2026-04-02 18:02:25 +00:00
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
2026-04-02 15:49:30 +00:00
|
|
|
if err != nil {
|
2026-04-02 18:02:25 +00:00
|
|
|
logger.Warning("Turnstile verification failed to read response:", err)
|
2026-04-02 15:49:30 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result turnstileResponse
|
|
|
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
2026-04-02 18:02:25 +00:00
|
|
|
logger.Warning("Turnstile verification failed to parse response:", err)
|
2026-04-02 15:49:30 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return result.Success
|
|
|
|
|
}
|