Files
sub2api/backend/internal/service/turnstile_service.go
NepetaLemon 2270a54ff6 refactor: 移除 infrastructure 目录 (#108)
* refactor: 迁移初始化 db 和 redis 到 repository

* refactor: 迁移 errors 到 pkg
2025-12-31 23:42:01 +08:00

106 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"fmt"
"log"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
var (
ErrTurnstileVerificationFailed = infraerrors.BadRequest("TURNSTILE_VERIFICATION_FAILED", "turnstile verification failed")
ErrTurnstileNotConfigured = infraerrors.ServiceUnavailable("TURNSTILE_NOT_CONFIGURED", "turnstile not configured")
ErrTurnstileInvalidSecretKey = infraerrors.BadRequest("TURNSTILE_INVALID_SECRET_KEY", "invalid turnstile secret key")
)
// TurnstileVerifier 验证 Turnstile token 的接口
type TurnstileVerifier interface {
VerifyToken(ctx context.Context, secretKey, token, remoteIP string) (*TurnstileVerifyResponse, error)
}
// TurnstileService Turnstile 验证服务
type TurnstileService struct {
settingService *SettingService
verifier TurnstileVerifier
}
// 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, verifier TurnstileVerifier) *TurnstileService {
return &TurnstileService{
settingService: settingService,
verifier: verifier,
}
}
// 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
}
log.Printf("[Turnstile] Verifying token for IP: %s", remoteIP)
result, err := s.verifier.VerifyToken(ctx, secretKey, token, remoteIP)
if err != nil {
log.Printf("[Turnstile] Request failed: %v", err)
return fmt.Errorf("send request: %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)
}
// ValidateSecretKey 验证 Turnstile Secret Key 是否有效
func (s *TurnstileService) ValidateSecretKey(ctx context.Context, secretKey string) error {
// 发送一个测试token的验证请求来检查secret_key是否有效
result, err := s.verifier.VerifyToken(ctx, secretKey, "test-validation", "")
if err != nil {
return fmt.Errorf("validate secret key: %w", err)
}
// 检查是否有 invalid-input-secret 错误
for _, code := range result.ErrorCodes {
if code == "invalid-input-secret" {
return ErrTurnstileInvalidSecretKey
}
}
// 其他错误(如 invalid-input-response说明 secret key 是有效的
return nil
}