package middleware // Reference: Chatwoot PlatformController — AccessToken authentication // Chatwoot authenticates Platform API requests via the api_access_token header, // which maps to a PlatformApp owner. This middleware replicates that pattern: // // 1. Read api_access_token from request headers // 2. Look up the AccessToken record (token is SHA-256 hashed in DB) // 3. Verify the owner is a PlatformApp (not a User personal token) // 4. Set platform_app in Gin context for downstream handlers // // Permissible validation is handled per-resource in each handler, // matching Chatwoot's validate_platform_app_permissible callback. import ( "crypto/sha256" "encoding/hex" "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/pkg/response" "gorm.io/gorm" ) // PlatformAppAuth creates a middleware that authenticates Platform API requests // via the api_access_token header (Chatwoot-compatible). // // The middleware: // - Extracts api_access_token from request headers // - Hashes the token (SHA-256) and queries the access_tokens table // - Verifies the token owner is a PlatformApp // - Loads the PlatformApp with its Permissibles for downstream checks // - Sets platform_app_id and platform_app in Gin context // // If no token is provided, or the token is invalid, or the owner is not // a PlatformApp, the request is rejected with 401 Unauthorized. func PlatformAppAuth(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { // Step 1: Extract api_access_token from headers // Chatwoot reads both :api_access_token and :HTTP_API_ACCESS_TOKEN token := c.GetHeader("api_access_token") if token == "" { token = c.GetHeader("HTTP_API_ACCESS_TOKEN") } if token == "" { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "api_access_token header required") return } // Step 2: Hash the token (SHA-256) to match stored hash hash := sha256.Sum256([]byte(token)) tokenHash := hex.EncodeToString(hash[:]) // Step 3: Query AccessToken by hashed token var accessToken model.AccessToken if err := db.Where("token = ? AND owner_type = ?", tokenHash, model.AccessTokenOwnerTypePlatformApp). First(&accessToken).Error; err != nil { if err == gorm.ErrRecordNotFound { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "Invalid access_token") return } response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "Failed to verify access token") return } // Step 4: Load the PlatformApp with its Permissibles var platformApp model.PlatformApp if err := db.Preload("Permissibles").First(&platformApp, accessToken.OwnerID).Error; err != nil { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "Invalid access_token") return } // Step 5: Set platform_app in Gin context for downstream handlers c.Set("platform_app", platformApp) c.Set("platform_app_id", platformApp.ID) c.Set("access_token_id", accessToken.ID) c.Next() } }