package middleware import ( "errors" "net/http" "slices" "strings" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/repository" "hyapp-admin-server/internal/response" "hyapp-admin-server/internal/service" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type AppAccessStore interface { AppAccessForUser(userID uint) (repository.AppAccess, error) } const ( ContextUserID = "userID" ContextUsername = "username" ContextPermissions = "permissions" ) func CORS(cfg config.Config) gin.HandlerFunc { allowed := map[string]struct{}{} for _, origin := range cfg.CORSAllowedOrigins { allowed[origin] = struct{}{} } return func(c *gin.Context) { origin := c.Request.Header.Get("Origin") if _, ok := allowed[origin]; ok { c.Header("Access-Control-Allow-Origin", origin) c.Header("Vary", "Origin") c.Header("Access-Control-Allow-Credentials", "true") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-ID, "+appctx.HeaderAppCode) c.Header("Access-Control-Expose-Headers", "X-Request-ID") c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS") } if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } func AppCode() gin.HandlerFunc { return func(c *gin.Context) { appCode := appctx.Normalize(c.GetHeader(appctx.HeaderAppCode)) c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), appCode)) c.Header(appctx.HeaderAppCode, appCode) c.Next() } } func AuthRequired(auth *service.AuthService) gin.HandlerFunc { return func(c *gin.Context) { header := c.GetHeader("Authorization") if !strings.HasPrefix(header, "Bearer ") { response.Unauthorized(c, "缺少访问凭证") c.Abort() return } claims, err := auth.ParseAccessToken(strings.TrimPrefix(header, "Bearer ")) if err != nil { response.Unauthorized(c, "访问凭证已失效") c.Abort() return } c.Set(ContextUserID, claims.UserID) c.Set(ContextUsername, claims.Username) c.Set(ContextPermissions, claims.Permissions) c.Next() } } func RequireAppScope(store AppAccessStore) gin.HandlerFunc { return func(c *gin.Context) { // App 级路由必须显式携带 header,不能复用 appctx 的 lalu 默认值;否则无 App 用户省略 header 仍会落入 Lalu 数据域。 appCode := strings.ToLower(strings.TrimSpace(c.GetHeader(appctx.HeaderAppCode))) if appCode == "" { response.Forbidden(c, "当前用户没有可访问的 App") c.Abort() return } if store == nil { response.ServerError(c, "App 权限服务不可用") c.Abort() return } access, err := store.AppAccessForUser(CurrentUserID(c)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { response.Unauthorized(c, "用户不存在") } else { response.ServerError(c, "校验 App 权限失败") } c.Abort() return } if !access.Allows(appCode) { response.Forbidden(c, "当前用户无权访问该 App") c.Abort() return } c.Next() } } func RequirePermission(code string) gin.HandlerFunc { return func(c *gin.Context) { if HasPermission(c, code) { c.Next() return } response.Forbidden(c, "没有操作权限") c.Abort() } } // RequireAnyPermission is used by transitional RBAC routes where the new // button-level permission and the old broad permission must both work. func RequireAnyPermission(codes ...string) gin.HandlerFunc { return func(c *gin.Context) { if HasAnyPermission(c, codes...) { c.Next() return } response.Forbidden(c, "没有操作权限") c.Abort() } } // HasAnyPermission keeps privilege checks in handlers readable when one action // is allowed by multiple compatible permission codes. func HasAnyPermission(c *gin.Context, codes ...string) bool { for _, code := range codes { if HasPermission(c, code) { return true } } return false } // HasPermission reads only the JWT-derived permission list. Database state is // refreshed by login/refresh, so every request uses one consistent permission snapshot. func HasPermission(c *gin.Context, code string) bool { return slices.Contains(CurrentPermissions(c), code) } func CurrentUserID(c *gin.Context) uint { value, ok := c.Get(ContextUserID) if !ok { return 0 } id, ok := value.(uint) if !ok { return 0 } return id } func CurrentUsername(c *gin.Context) string { value, ok := c.Get(ContextUsername) if !ok { return "" } username, ok := value.(string) if !ok { return "" } return username } func CurrentPermissions(c *gin.Context) []string { value, ok := c.Get(ContextPermissions) if !ok { return nil } permissions, ok := value.([]string) if !ok { return nil } return permissions }