package middleware import ( "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/sundynix/sundynix-gateway/internal/auth" ) // CtxUserID 是鉴权后写入 gin.Context 的已验证用户 ID 键。 const CtxUserID = "uid" // Auth 解析 Authorization: Bearer ,校验通过则把已验证 userID 写入上下文。 // 非阻断:无 token / 无效 token 时不报错,由各 handler(经 userID 兜底 header)或 // 后续 RequireAuth 决定是否放行。 func Auth() gin.HandlerFunc { return func(c *gin.Context) { h := c.GetHeader("Authorization") if strings.HasPrefix(h, "Bearer ") { if uid, err := auth.Parse(strings.TrimSpace(h[len("Bearer "):])); err == nil { c.Set(CtxUserID, uid) } } c.Next() } } // RequireAuth 在 Auth 之后使用:上下文无已验证 userID 则 401 拒绝。 // 用于 owner 作用域的业务路由;SSE/导出等按 task_id 寻址的端点不挂(EventSource 无法带头)。 func RequireAuth() gin.HandlerFunc { return func(c *gin.Context) { if v, ok := c.Get(CtxUserID); ok { if s, _ := v.(string); s != "" { c.Next() return } } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"}) } } // RequireAdmin 保护运维控制面:必须登录,且(设了 ADMIN_USER_IDS 时)uid 须在白名单内。 // ADMIN_USER_IDS 为空:开发期放行任意登录用户;生产期(APP_ENV=prod/GIN_MODE=release)直接拒绝 // ——逼运维显式配置管理员,杜绝"任意账号改模型/密钥配置"。 func RequireAdmin() gin.HandlerFunc { allow := splitEnv("ADMIN_USER_IDS") prod := strings.EqualFold(os.Getenv("APP_ENV"), "production") || strings.EqualFold(os.Getenv("APP_ENV"), "prod") || strings.EqualFold(os.Getenv("GIN_MODE"), "release") return func(c *gin.Context) { uid, _ := c.Get(CtxUserID) id, _ := uid.(string) if id == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"}) return } if len(allow) == 0 { if prod { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "未配置管理员(ADMIN_USER_IDS)"}) return } c.Next() // 开发期放行 return } for _, a := range allow { if a == id { c.Next() return } } c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"}) } } func splitEnv(key string) []string { var out []string for _, p := range strings.Split(os.Getenv(key), ",") { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out }