yuan-lab-llm--clawmanager
167 行
4.1 KiB
Go
167 行
4.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"clawreef/internal/repository"
|
|
"clawreef/internal/utils"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Auth middleware validates JWT token
|
|
func Auth() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tokenString, ok := extractToken(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Authorization header required",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
claims, err := validateUserAccessToken(tokenString)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Invalid or expired token",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
// Check token type
|
|
if claims.TokenType != "access" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Invalid token type",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
// Set user ID in context
|
|
c.Set("userID", claims.UserID)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// GatewayAuth accepts either a normal user access JWT or an instance lifecycle gateway token.
|
|
func GatewayAuth(instanceRepo repository.InstanceRepository, bindingRepos ...repository.InstanceRuntimeBindingRepository) gin.HandlerFunc {
|
|
var bindingRepo repository.InstanceRuntimeBindingRepository
|
|
if len(bindingRepos) > 0 {
|
|
bindingRepo = bindingRepos[0]
|
|
}
|
|
return func(c *gin.Context) {
|
|
tokenString, ok := extractToken(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Authorization header required",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
if claims, err := validateUserAccessToken(tokenString); err == nil {
|
|
c.Set("userID", claims.UserID)
|
|
c.Set("gatewayAuthType", "user")
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
instance, err := instanceRepo.GetByAccessToken(tokenString)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Invalid gateway token",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
if instance == nil || instance.AccessToken == nil || strings.TrimSpace(*instance.AccessToken) == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"error": "Invalid gateway token",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Set("userID", instance.UserID)
|
|
c.Set("instanceID", instance.ID)
|
|
c.Set("instanceMode", gatewayInstanceMode(instance.InstanceMode, instance.RuntimeType))
|
|
c.Set("runtimeType", strings.TrimSpace(instance.RuntimeType))
|
|
if bindingRepo != nil {
|
|
if binding, err := bindingRepo.GetRunningByInstanceID(c.Request.Context(), instance.ID); err == nil && binding != nil {
|
|
c.Set("gatewayID", strings.TrimSpace(binding.GatewayID))
|
|
c.Set("runtimePodID", binding.RuntimePodID)
|
|
}
|
|
}
|
|
c.Set("gatewayAuthType", "instance")
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func gatewayInstanceMode(instanceMode, runtimeType string) string {
|
|
mode := strings.ToLower(strings.TrimSpace(instanceMode))
|
|
if mode == "lite" || mode == "pro" {
|
|
return mode
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(runtimeType)) {
|
|
case "gateway":
|
|
return "lite"
|
|
case "desktop", "shell":
|
|
return "pro"
|
|
default:
|
|
return mode
|
|
}
|
|
}
|
|
|
|
func extractToken(c *gin.Context) (string, bool) {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader != "" {
|
|
parts := strings.SplitN(authHeader, " ", 2)
|
|
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
|
return "", false
|
|
}
|
|
return parts[1], true
|
|
}
|
|
|
|
// Browsers cannot set custom Authorization headers for native WebSocket
|
|
// handshakes, so allow `?token=` specifically for upgrade requests.
|
|
if strings.EqualFold(c.GetHeader("Upgrade"), "websocket") {
|
|
token := strings.TrimSpace(c.Query("token"))
|
|
if token != "" {
|
|
return token, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
func getJWTSecret() string {
|
|
// Get from environment variable, fallback to default
|
|
// Must match the secret used in config.go
|
|
if secret := os.Getenv("JWT_SECRET"); secret != "" {
|
|
return secret
|
|
}
|
|
return "clawreef-dev-secret-key-change-in-production"
|
|
}
|
|
|
|
func validateUserAccessToken(tokenString string) (*utils.TokenClaims, error) {
|
|
jwtSecret := getJWTSecret()
|
|
claims, err := utils.ValidateToken(tokenString, jwtSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if claims.TokenType != "access" {
|
|
return nil, errors.New("invalid token type")
|
|
}
|
|
return claims, nil
|
|
}
|