// Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. package auth import ( "strings" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) // AuthMiddleware 统一认证中间件 // 1. JWT认证: 解析token,验证有效性,设置用户信息 // 2. 组织上下文: 如果请求包含org_id,验证用户成员身份,设置组织信息 func AuthMiddleware() func(*vigo.X) error { return func(x *vigo.X) error { // === 1. JWT 认证部分 === tokenString := extractToken(x) if tokenString == "" { return vigo.ErrNotAuthorized.WithString("missing token") } // 解析token claims, err := jwt.ParseToken(tokenString) if err != nil { if err == jwt.ErrExpiredToken { return vigo.ErrNotAuthorized.WithString("token expired") } return vigo.ErrNotAuthorized.WithString("invalid token") } // 检查token是否在黑名单中 if cache.IsEnabled() { blacklisted, _ := cache.IsTokenBlacklisted(claims.ID) if blacklisted { return vigo.ErrNotAuthorized.WithString("token has been revoked") } } // 将用户信息存入上下文 x.Set("user_id", claims.UserID) x.Set("user_name", claims.Username) x.Set("user_orgs", claims.Orgs) x.Set("token_claims", claims) // === 2. 组织上下文部分 === orgID := x.Request.Header.Get("X-Org-ID") if orgID == "" { orgID = x.Request.URL.Query().Get("org_id") } if orgID == "" { // 没有指定组织,仅完成用户认证 return nil } // 验证用户是否为组织成员 var member models.OrgMember if err := cfg.DB().Where("org_id = ? AND user_id = ? AND status = ?", orgID, claims.UserID, models.MemberStatusActive).First(&member).Error; err != nil { return vigo.ErrForbidden.WithString("you are not a member of this organization") } x.Set("org_id", orgID) x.Set("org_roles", member.RoleIDs) return nil } } func extractToken(x *vigo.X) string { auth := x.Request.Header.Get("Authorization") if auth != "" { if len(auth) > 7 && strings.HasPrefix(auth, "Bearer ") { return auth[7:] } } return x.Request.URL.Query().Get("access_token") }