package middleware // Reference: P2E ยง2.8 โ€” Feature flag middleware // Feature gating middleware. In GoChat (self-hosted), all features are available // by default. This middleware exists for API compatibility with Chatwoot's // enterprise feature gating, but defaults to allow when no flags are configured. import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/pkg/response" ) // Known feature flags from Chatwoot enterprise features: // Reference: Chatwoot app/models/account.rb โ€” FEATURE_FLAGS constant const ( FeatureAutoAssignment = "auto_assignment" FeatureCustomRoles = "custom_roles" FeatureAutomation = "automation" FeatureKnowledgeBase = "knowledge_base" FeatureReporting = "reporting" FeatureCSAT = "csat" FeatureHelpCenter = "help_center" FeatureAgentBots = "agent_bots" FeatureMFA = "mfa" FeatureChannelFacebook = "channel_facebook" FeatureChannelTwilio = "channel_twilio" FeatureChannelWhatsApp = "channel_whatsapp" FeatureChannelEmail = "channel_email" FeatureChannelTelegram = "channel_telegram" FeatureChannelLine = "channel_line" FeatureEnterpriseReporting = "enterprise_reporting" FeatureMacro = "macro" FeatureCampaign = "campaign" FeatureTeamInboxes = "team_inboxes" ) // FeatureFlagCheck creates a middleware that verifies the current account // has the specified feature flag enabled. // // In GoChat (self-hosted), all features are available by default. // If feature flags are present in the context, they are checked. // If no flags are present, the request is allowed (self-hosted = all features enabled). func FeatureFlagCheck(flag string) gin.HandlerFunc { return func(c *gin.Context) { featureFlags, exists := c.Get("feature_flags") if !exists { // Try to load from account if it was set accountFlags, accExists := c.Get("account_feature_flags") if accExists { featureFlags = accountFlags exists = true } } if !exists { // No feature flags in context โ€” allow in self-hosted mode. // GoChat is not a SaaS; all features are available. c.Next() return } if hasFeatureFlag(featureFlags, flag) { c.Next() return } response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "Feature '"+flag+"' is not enabled on this account") } } // hasFeatureFlag checks if a flag is enabled in the feature flags. // Feature flags can be stored as: // - JSON object: {"help_center": true, "csat": false} // - JSON array: ["help_center", "csat"] // - Comma-separated string: "help_center,csat" // - []string: {"help_center", "csat"} func hasFeatureFlag(raw interface{}, flag string) bool { switch v := raw.(type) { case []string: for _, f := range v { if f == flag { return true } } return false case string: if v == "" { return false } // Try JSON object first: {"flag": true} var obj map[string]interface{} if err := json.Unmarshal([]byte(v), &obj); err == nil { if val, ok := obj[flag]; ok { if b, ok := val.(bool); ok { return b } return true // non-bool value = present = enabled } return false } // Try JSON array: ["flag"] var arr []string if err := json.Unmarshal([]byte(v), &arr); err == nil { for _, f := range arr { if f == flag { return true } } return false } return false case map[string]interface{}: if val, ok := v[flag]; ok { if b, ok := val.(bool); ok { return b } return true } return false case []interface{}: for _, item := range v { if s, ok := item.(string); ok && s == flag { return true } } return false default: return false } }