package middleware import ( "net/http" "github.com/gin-gonic/gin" "switchboard-core/auth" "switchboard-core/store" ) const permCacheKey = "resolved_permissions" // RequirePermission returns middleware that enforces a named permission. // Permission resolution is cached in the request context — computed at most // once per request regardless of how many RequirePermission middlewares are // chained. Admins receive permissions through the Admins group. func RequirePermission(perm string, stores store.Stores) gin.HandlerFunc { return func(c *gin.Context) { userID := c.GetString("user_id") perms, err := resolveAndCachePerms(c, stores, userID) if err != nil || !perms[perm] { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "error": "permission required: " + perm, }) return } c.Next() } } // resolveAndCachePerms loads the user's effective permissions once per request. func resolveAndCachePerms(c *gin.Context, stores store.Stores, userID string) (map[string]bool, error) { if cached, exists := c.Get(permCacheKey); exists { return cached.(map[string]bool), nil } perms, err := auth.ResolvePermissions(c.Request.Context(), stores, userID) if err != nil { return nil, err } c.Set(permCacheKey, perms) return perms, nil } // GetResolvedPermissions returns the cached permission set for the current // request. Returns nil if not yet resolved (i.e. RequirePermission was not // earlier in the chain). Callers in handlers can use this for conditional // logic without triggering an extra DB round-trip. func GetResolvedPermissions(c *gin.Context) map[string]bool { if cached, exists := c.Get(permCacheKey); exists { return cached.(map[string]bool) } return nil }