96 lines
2.7 KiB
Go
96 lines
2.7 KiB
Go
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)
|
||
}
|