项目文件夹

文件
2026-06-14 14:40:49 +08:00

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
}