Files
SingBox-Gopanel/internal/middleware/node_auth_v2.go
CN-JS-HuiBai f9c34fcf87
Some checks failed
build / build (api, amd64, linux) (push) Failing after -46s
build / build (api, arm64, linux) (push) Failing after -51s
build / build (api.exe, amd64, windows) (push) Failing after -51s
修复节点信息API
2026-04-18 00:52:43 +08:00

154 lines
3.4 KiB
Go

package middleware
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"xboard-go/internal/service"
"github.com/gin-gonic/gin"
)
const nodeAuthPayloadKey = "_node_auth_payload"
func NodeAuth() gin.HandlerFunc {
return func(c *gin.Context) {
token := nodeAuthValue(c, "token")
nodeID := nodeAuthValue(c, "node_id")
nodeType := service.NormalizeServerType(nodeAuthValue(c, "node_type"))
if token == "" || nodeID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"message": "missing node credentials"})
c.Abort()
return
}
if !service.IsValidServerType(nodeType) {
c.JSON(http.StatusBadRequest, gin.H{"message": "invalid node type"})
c.Abort()
return
}
serverToken := service.MustGetString("server_token", "")
if serverToken == "" {
c.JSON(http.StatusInternalServerError, gin.H{"message": "server_token is not configured"})
c.Abort()
return
}
if token != serverToken {
c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid server token"})
c.Abort()
return
}
node, err := service.FindServer(nodeID, nodeType)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"message": "server not found"})
c.Abort()
return
}
c.Set("node", node)
c.Next()
}
}
func nodeAuthValue(c *gin.Context, key string) string {
if value := strings.TrimSpace(c.Query(key)); value != "" {
return value
}
if value := strings.TrimSpace(c.PostForm(key)); value != "" {
return value
}
switch key {
case "token":
if value := strings.TrimSpace(c.GetHeader("X-Server-Token")); value != "" {
return value
}
if auth := strings.TrimSpace(c.GetHeader("Authorization")); len(auth) > 7 && strings.EqualFold(auth[:7], "Bearer ") {
return strings.TrimSpace(auth[7:])
}
case "node_id":
if value := strings.TrimSpace(c.GetHeader("X-Node-Id")); value != "" {
return value
}
case "node_type":
if value := strings.TrimSpace(c.GetHeader("X-Node-Type")); value != "" {
return value
}
}
payload := nodeAuthPayload(c)
if payload == nil {
return ""
}
return nodeAuthString(payload[key])
}
func nodeAuthPayload(c *gin.Context) map[string]any {
if cached, ok := c.Get(nodeAuthPayloadKey); ok {
if payload, ok := cached.(map[string]any); ok {
return payload
}
return nil
}
if c.Request == nil || c.Request.Body == nil {
c.Set(nodeAuthPayloadKey, map[string]any(nil))
return nil
}
contentType := strings.ToLower(c.GetHeader("Content-Type"))
if !strings.Contains(contentType, "application/json") {
c.Set(nodeAuthPayloadKey, map[string]any(nil))
return nil
}
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Request.Body = io.NopCloser(bytes.NewBuffer(nil))
c.Set(nodeAuthPayloadKey, map[string]any(nil))
return nil
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
if len(bytes.TrimSpace(body)) == 0 {
c.Set(nodeAuthPayloadKey, map[string]any(nil))
return nil
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
c.Set(nodeAuthPayloadKey, map[string]any(nil))
return nil
}
c.Set(nodeAuthPayloadKey, payload)
return payload
}
func nodeAuthString(value any) string {
switch typed := value.(type) {
case string:
return strings.TrimSpace(typed)
case json.Number:
return strings.TrimSpace(typed.String())
default:
if value == nil {
return ""
}
text := strings.TrimSpace(fmt.Sprint(value))
if text == "<nil>" {
return ""
}
return text
}
}