Update SproutGate
This commit is contained in:
95
sproutgate-backend/internal/handlers/turnstile.go
Normal file
95
sproutgate-backend/internal/handlers/turnstile.go
Normal file
@@ -0,0 +1,95 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user