154 lines
3.4 KiB
Go
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
|
|
}
|
|
}
|