package middleware // Reference: P2E ยง2.6 โ€” PolicyMiddleware // Gin middleware that enforces RBAC permission checks for each request. // Replaces Chatwoot's Pundit policy enforcement in controllers. import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/auth" "github.com/gochat/gochat/pkg/response" ) // PolicyMiddleware creates a Gin middleware that checks whether the current user // (identified by PolicyContext in the Gin context) has permission to perform // the specified action on the specified resource. // // Usage in routes: // router.GET("/conversations", PolicyMiddleware("conversation", "read"), listConversations) // router.POST("/conversations", PolicyMiddleware("conversation", "create"), createConversation) // router.DELETE("/conversations/:id", PolicyMiddleware("conversation", "delete"), deleteConversation) // // The middleware expects a PolicyContext to be set in the Gin context under key "policy_context". // This is typically set by the AccountScope middleware after loading AccountUser data. func PolicyMiddleware(resource, action string) gin.HandlerFunc { return func(c *gin.Context) { pc, exists := c.Get("policy_context") if !exists { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Policy context not found โ€” ensure AccountScope middleware is applied first") return } policyCtx, ok := pc.(*auth.PolicyContext) if !ok { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Invalid policy context type") return } if !policyCtx.Can(action, resource) { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Permission denied: cannot perform '"+action+"' on '"+resource+"'") return } c.Next() } } // PolicyScopeMiddleware injects a scope-filtered GORM DB instance into the Gin context. // This allows handlers to use a pre-filtered DB that only returns records the user // is allowed to see. // // Usage: // router.GET("/conversations", PolicyScopeMiddleware("conversation"), listConversations) // // The handler can then retrieve the scoped DB: // scopedDB := c.MustGet("scoped_db").(*gorm.DB) func PolicyScopeMiddleware(resource string) gin.HandlerFunc { return func(c *gin.Context) { pc, exists := c.Get("policy_context") if !exists { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Policy context not found โ€” ensure AccountScope middleware is applied first") return } policyCtx, ok := pc.(*auth.PolicyContext) if !ok { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Invalid policy context type") return } // Store policy context for downstream handlers/services c.Set("policy_context", policyCtx) c.Next() } }