112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrTurnstileVerificationFailed = errors.New("turnstile verification failed")
|
|
ErrTurnstileNotConfigured = errors.New("turnstile not configured")
|
|
)
|
|
|
|
const turnstileVerifyURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
|
|
|
// TurnstileService Turnstile 验证服务
|
|
type TurnstileService struct {
|
|
settingService *SettingService
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// TurnstileVerifyResponse Cloudflare Turnstile 验证响应
|
|
type TurnstileVerifyResponse struct {
|
|
Success bool `json:"success"`
|
|
ChallengeTS string `json:"challenge_ts"`
|
|
Hostname string `json:"hostname"`
|
|
ErrorCodes []string `json:"error-codes"`
|
|
Action string `json:"action"`
|
|
CData string `json:"cdata"`
|
|
}
|
|
|
|
// NewTurnstileService 创建 Turnstile 服务实例
|
|
func NewTurnstileService(settingService *SettingService) *TurnstileService {
|
|
return &TurnstileService{
|
|
settingService: settingService,
|
|
httpClient: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// VerifyToken 验证 Turnstile token
|
|
func (s *TurnstileService) VerifyToken(ctx context.Context, token string, remoteIP string) error {
|
|
// 检查是否启用 Turnstile
|
|
if !s.settingService.IsTurnstileEnabled(ctx) {
|
|
log.Println("[Turnstile] Disabled, skipping verification")
|
|
return nil
|
|
}
|
|
|
|
// 获取 Secret Key
|
|
secretKey := s.settingService.GetTurnstileSecretKey(ctx)
|
|
if secretKey == "" {
|
|
log.Println("[Turnstile] Secret key not configured")
|
|
return ErrTurnstileNotConfigured
|
|
}
|
|
|
|
// 如果 token 为空,返回错误
|
|
if token == "" {
|
|
log.Println("[Turnstile] Token is empty")
|
|
return ErrTurnstileVerificationFailed
|
|
}
|
|
|
|
// 构建请求
|
|
formData := url.Values{}
|
|
formData.Set("secret", secretKey)
|
|
formData.Set("response", token)
|
|
if remoteIP != "" {
|
|
formData.Set("remoteip", remoteIP)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, turnstileVerifyURL, strings.NewReader(formData.Encode()))
|
|
if err != nil {
|
|
return fmt.Errorf("create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
// 发送请求
|
|
log.Printf("[Turnstile] Verifying token for IP: %s", remoteIP)
|
|
resp, err := s.httpClient.Do(req)
|
|
if err != nil {
|
|
log.Printf("[Turnstile] Request failed: %v", err)
|
|
return fmt.Errorf("send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// 解析响应
|
|
var result TurnstileVerifyResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
log.Printf("[Turnstile] Failed to decode response: %v", err)
|
|
return fmt.Errorf("decode response: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
log.Printf("[Turnstile] Verification failed, error codes: %v", result.ErrorCodes)
|
|
return ErrTurnstileVerificationFailed
|
|
}
|
|
|
|
log.Println("[Turnstile] Verification successful")
|
|
return nil
|
|
}
|
|
|
|
// IsEnabled 检查 Turnstile 是否启用
|
|
func (s *TurnstileService) IsEnabled(ctx context.Context) bool {
|
|
return s.settingService.IsTurnstileEnabled(ctx)
|
|
}
|