package handlers import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strings" "time" "github.com/gin-gonic/gin" "sproutgate-backend/internal/storage" ) const turnstileVerifyURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify" type turnstileVerifyResp struct { Success bool `json:"success"` } // verifyTurnstileToken 向 Cloudflare 验证 Turnstile token,失败返回用户可见错误。 func verifyTurnstileToken(ctx context.Context, secretKey, token, remoteIP string) error { if strings.TrimSpace(token) == "" { return fmt.Errorf("请完成人机验证") } form := url.Values{} form.Set("secret", secretKey) form.Set("response", token) if remoteIP != "" { form.Set("remoteip", remoteIP) } client := &http.Client{Timeout: 8 * time.Second} resp, err := client.PostForm(turnstileVerifyURL, form) if err != nil { return fmt.Errorf("人机验证服务暂时不可用,请稍后重试") } defer resp.Body.Close() var result turnstileVerifyResp if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return fmt.Errorf("人机验证响应解析失败") } if !result.Success { return fmt.Errorf("人机验证未通过,请重试") } return nil } // GetAdminTurnstile // @Summary Turnstile 配置(脱敏) // @Tags admin // @Produce json // @Security AdminToken // @Success 200 {object} map[string]interface{} // @Failure 401 {object} map[string]interface{} // @Router /api/admin/turnstile [get] func (h *Handler) GetAdminTurnstile(c *gin.Context) { cfg := h.store.GetTurnstileConfig() c.JSON(http.StatusOK, gin.H{ "enabled": cfg.Enabled, "siteKey": strings.TrimSpace(cfg.SiteKey), "secretKeySet": strings.TrimSpace(cfg.SecretKey) != "", }) } // PutAdminTurnstile // @Summary 更新 Turnstile 配置 // @Tags admin // @Accept json // @Produce json // @Security AdminToken // @Param body body PutAdminTurnstileRequest true "siteKey、secretKey" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} // @Failure 401 {object} map[string]interface{} // @Failure 500 {object} map[string]interface{} // @Router /api/admin/turnstile [put] func (h *Handler) PutAdminTurnstile(c *gin.Context) { var req PutAdminTurnstileRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } in := storage.TurnstileConfig{ Enabled: req.Enabled, SiteKey: strings.TrimSpace(req.SiteKey), SecretKey: strings.TrimSpace(req.SecretKey), } if err := h.store.UpdateTurnstileConfig(in); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save turnstile config"}) return } h.GetAdminTurnstile(c) }