From bafcfbde3a5bc855f7977d42db52c583bf5b3815 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Mon, 23 Feb 2026 14:40:29 +0800 Subject: [PATCH] fix(backend): allow WHMCS API login when captcha is enabled --- go-backend/internal/http/handler/handler.go | 25 +++++++++++-------- .../tests/contract/migration_contract_test.go | 12 +++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/go-backend/internal/http/handler/handler.go b/go-backend/internal/http/handler/handler.go index d588335..de94a63 100644 --- a/go-backend/internal/http/handler/handler.go +++ b/go-backend/internal/http/handler/handler.go @@ -219,7 +219,7 @@ func (h *Handler) login(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, response.Err(-2, err.Error())) return } - if captchaEnabled { + if captchaEnabled && !h.apiClientCaptchaBypassEnabled(r) { captchaID := strings.TrimSpace(req.CaptchaID) if captchaID == "" { response.WriteJSON(w, response.ErrDefault("验证码校验失败")) @@ -988,21 +988,24 @@ func (h *Handler) captchaEnabled() (bool, error) { if err != nil { return false, err } - if cfg == nil || !strings.EqualFold(cfg.Value, "true") { + if cfg == nil { return false, nil } + return strings.EqualFold(cfg.Value, "true"), nil +} - // captcha_enabled=true, but we need to verify cloudflare_secret_key is configured - // If secret key is not configured, treat captcha as disabled to avoid blocking login - secretKey, err := h.repo.GetConfigByName("cloudflare_secret_key") - if err != nil { - return false, err - } - if secretKey == nil || strings.TrimSpace(secretKey.Value) == "" { - return false, nil +func (h *Handler) apiClientCaptchaBypassEnabled(r *http.Request) bool { + if r == nil { + return false } - return true, nil + client := strings.ToLower(strings.TrimSpace(r.Header.Get("X-FLVX-API-Client"))) + switch client { + case "whmcs", "whmcs-module": + return true + default: + return false + } } func (h *Handler) markCaptchaToken(token string) { diff --git a/go-backend/tests/contract/migration_contract_test.go b/go-backend/tests/contract/migration_contract_test.go index 89e51a5..02201fe 100644 --- a/go-backend/tests/contract/migration_contract_test.go +++ b/go-backend/tests/contract/migration_contract_test.go @@ -45,6 +45,18 @@ func TestCaptchaVerifyLoginContract(t *testing.T) { assertCodeMsg(t, resp, -1, "验证码校验失败") }) + t.Run("whmcs api client bypasses captcha", func(t *testing.T) { + body := bytes.NewBufferString(`{"username":"admin_user","password":"admin_user","captchaId":""}`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/user/login", body) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-FLVX-API-Client", "whmcs") + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assertCode(t, resp, 0) + }) + t.Run("captcha token is one-time and consumed by login", func(t *testing.T) { verifyReq := httptest.NewRequest(http.MethodPost, "/api/v1/captcha/verify", bytes.NewBufferString(`{"id":"captcha-token-1","data":"ok"}`)) verifyReq.Header.Set("Content-Type", "application/json")