| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- package service
- import (
- "context"
- "fmt"
- "strconv"
- "strings"
- infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
- "github.com/Wei-Shaw/sub2api/internal/pkg/logger"
- )
- var (
- ErrTencentCaptchaVerificationFailed = infraerrors.BadRequest("TENCENT_CAPTCHA_VERIFICATION_FAILED", "tencent captcha verification failed")
- ErrTencentCaptchaNotConfigured = infraerrors.ServiceUnavailable("TENCENT_CAPTCHA_NOT_CONFIGURED", "tencent captcha not configured")
- )
- type TencentCaptchaProof struct {
- Ticket string
- Randstr string
- }
- type TencentCaptchaCredentials struct {
- AppID uint64
- AppSecretKey string
- CloudSecretID string
- CloudSecretKey string
- // Endpoint 服务端票据校验接入点,由地域推导,repository 层直接使用
- Endpoint string
- }
- const (
- // TencentCaptchaRegionCN 中国站(cloud.tencent.com);TencentCaptchaRegionINTL 国际站(tencentcloud.com)。
- // 该值同时决定前端加载的 SDK 脚本与服务端校验接入点,两端必须一致:
- // 国际站 CaptchaAppId 配国内站 SDK 会被腾讯直接判为「appid 所属地域与实际使用地域不符」。
- TencentCaptchaRegionCN = "cn"
- TencentCaptchaRegionINTL = "intl"
- tencentCaptchaEndpointCN = "captcha.tencentcloudapi.com"
- tencentCaptchaEndpointINTL = "captcha.intl.tencentcloudapi.com"
- )
- // tencentCaptchaEndpoint 按后台配置的地域返回服务端接入点,未知值回退中国站
- func tencentCaptchaEndpoint(region string) string {
- if region == TencentCaptchaRegionINTL {
- return tencentCaptchaEndpointINTL
- }
- return tencentCaptchaEndpointCN
- }
- // normalizeTencentCaptchaRegion 非法值一律视为中国站
- func normalizeTencentCaptchaRegion(value string) string {
- if value == TencentCaptchaRegionINTL {
- return TencentCaptchaRegionINTL
- }
- return TencentCaptchaRegionCN
- }
- type TencentCaptchaVerifyResponse struct {
- CaptchaCode int64
- CaptchaMsg string
- RequestID string
- }
- type TencentCaptchaVerifier interface {
- VerifyTicket(context.Context, TencentCaptchaCredentials, TencentCaptchaProof, string) (*TencentCaptchaVerifyResponse, error)
- }
- type TencentCaptchaService struct {
- settingService *SettingService
- verifier TencentCaptchaVerifier
- }
- func NewTencentCaptchaService(settingService *SettingService, verifier TencentCaptchaVerifier) *TencentCaptchaService {
- return &TencentCaptchaService{settingService: settingService, verifier: verifier}
- }
- func (s *TencentCaptchaService) VerifyTicket(ctx context.Context, ticket, randstr, remoteIP string) error {
- if s == nil || s.settingService == nil {
- return ErrTencentCaptchaNotConfigured
- }
- providerConfig, err := s.settingService.GetCaptchaProviderConfig(ctx)
- if err != nil {
- logger.LegacyPrintf("service.tencent_captcha", "%s", "[TencentCaptcha] failed to read captcha provider settings")
- return ErrServiceUnavailable
- }
- config := providerConfig.Tencent
- if !config.Enabled {
- return nil
- }
- return s.VerifyTicketWithConfig(ctx, config, ticket, randstr, remoteIP)
- }
- func (s *TencentCaptchaService) VerifyTicketWithConfig(ctx context.Context, config TencentCaptchaConfig, ticket, randstr, remoteIP string) error {
- credentials, ok := parseTencentCaptchaCredentials(config)
- if !ok || s.verifier == nil {
- return ErrTencentCaptchaNotConfigured
- }
- proof := TencentCaptchaProof{
- Ticket: strings.TrimSpace(ticket),
- Randstr: strings.TrimSpace(randstr),
- }
- if proof.Ticket == "" || proof.Randstr == "" || strings.HasPrefix(proof.Ticket, "trerror_") {
- return ErrTencentCaptchaVerificationFailed
- }
- result, err := s.verifier.VerifyTicket(ctx, credentials, proof, remoteIP)
- if err != nil {
- logger.LegacyPrintf(
- "service.tencent_captcha",
- "[TencentCaptcha] verification request failed region=%s endpoint=%s error=%v",
- normalizeTencentCaptchaRegion(config.Region),
- credentials.Endpoint,
- err,
- )
- return fmt.Errorf("%w: verifier request failed", ErrTencentCaptchaVerificationFailed)
- }
- if result == nil || result.CaptchaCode != 1 {
- if result != nil {
- logger.LegacyPrintf(
- "service.tencent_captcha",
- "[TencentCaptcha] rejected region=%s code=%d message=%q request_id=%q",
- normalizeTencentCaptchaRegion(config.Region),
- result.CaptchaCode,
- result.CaptchaMsg,
- result.RequestID,
- )
- } else {
- logger.LegacyPrintf(
- "service.tencent_captcha",
- "[TencentCaptcha] rejected region=%s empty_response=true",
- normalizeTencentCaptchaRegion(config.Region),
- )
- }
- return ErrTencentCaptchaVerificationFailed
- }
- return nil
- }
- func parseTencentCaptchaCredentials(config TencentCaptchaConfig) (TencentCaptchaCredentials, bool) {
- appID, err := strconv.ParseUint(strings.TrimSpace(config.AppID), 10, 64)
- if err != nil || appID == 0 {
- return TencentCaptchaCredentials{}, false
- }
- credentials := TencentCaptchaCredentials{
- AppID: appID,
- AppSecretKey: strings.TrimSpace(config.AppSecretKey),
- CloudSecretID: strings.TrimSpace(config.CloudSecretID),
- CloudSecretKey: strings.TrimSpace(config.CloudSecretKey),
- Endpoint: tencentCaptchaEndpoint(config.Region),
- }
- if credentials.AppSecretKey == "" || credentials.CloudSecretID == "" || credentials.CloudSecretKey == "" {
- return TencentCaptchaCredentials{}, false
- }
- return credentials, true
- }
|