package onboarding import ( "context" "data-terminal-cloud/internal/config" captcha "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/captcha/v20190722" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" "net" "strconv" "strings" ) type CaptchaProvider interface { AppID(client string) string Verify(ctx context.Context, client, ticket, randstr, ip string) error } type captchaAPI interface { DescribeCaptchaResultWithContext(context.Context, *captcha.DescribeCaptchaResultRequest) (*captcha.DescribeCaptchaResultResponse, error) DescribeCaptchaMiniResultWithContext(context.Context, *captcha.DescribeCaptchaMiniResultRequest) (*captcha.DescribeCaptchaMiniResultResponse, error) } type tencentCaptchaProvider struct { api captchaAPI config config.TencentCaptchaConfig } func NewTencentCaptchaProvider(c config.TencentCaptchaConfig) (CaptchaProvider, error) { if err := c.Validate(); err != nil { return nil, err } if c.SecretID == "" { return nil, nil } p := profile.NewClientProfile() p.HttpProfile.Endpoint = "captcha.tencentcloudapi.com" p.HttpProfile.ReqTimeout = 8 p.NetworkFailureMaxRetries = 0 p.RateLimitExceededMaxRetries = 0 p.UnsafeRetryOnConnectionFailure = false api, err := captcha.NewClient(common.NewCredential(c.SecretID, c.SecretKey), "", p) if err != nil { return nil, err } return &tencentCaptchaProvider{api: api, config: c}, nil } func (p *tencentCaptchaProvider) clientConfig(client string) config.CaptchaClientConfig { if client == "mini_program" { return p.config.Mini } if client == "saas" { return p.config.SaaS } return config.CaptchaClientConfig{} } func (p *tencentCaptchaProvider) AppID(client string) string { return p.clientConfig(client).AppID } func (p *tencentCaptchaProvider) Verify(ctx context.Context, client, ticket, randstr, ip string) error { c := p.clientConfig(client) if c.AppID == "" { return captchaUnavailable() } if ticket == "" || len(ticket) > 4096 || strings.HasPrefix(ticket, "trerror") || net.ParseIP(ip) == nil { return captchaInvalid() } id, _ := strconv.ParseUint(c.AppID, 10, 64) if client == "mini_program" { r := captcha.NewDescribeCaptchaMiniResultRequest() r.CaptchaType = common.Uint64Ptr(9) r.CaptchaAppId = &id r.AppSecretKey = &c.AppSecretKey r.Ticket = &ticket r.UserIp = &ip out, err := p.api.DescribeCaptchaMiniResultWithContext(ctx, r) if err != nil { return captchaUnavailable() } if out == nil || out.Response == nil || out.Response.CaptchaCode == nil || *out.Response.CaptchaCode != 1 { return captchaInvalid() } return nil } if randstr == "" || len(randstr) > 256 { return captchaInvalid() } r := captcha.NewDescribeCaptchaResultRequest() r.CaptchaType = common.Uint64Ptr(9) r.CaptchaAppId = &id r.AppSecretKey = &c.AppSecretKey r.Ticket = &ticket r.Randstr = &randstr r.UserIp = &ip out, err := p.api.DescribeCaptchaResultWithContext(ctx, r) if err != nil { return captchaUnavailable() } if out == nil || out.Response == nil || out.Response.CaptchaCode == nil || *out.Response.CaptchaCode != 1 || (out.Response.EvilLevel != nil && *out.Response.EvilLevel != 0) { return captchaInvalid() } return nil } func captchaInvalid() error { return fail(401, "CAPTCHA_INVALID", "人机验证失败或已过期,请重新验证") } func captchaUnavailable() error { return fail(503, "CAPTCHA_UNAVAILABLE", "人机验证暂不可用,请稍后重试") }