Files
gochat/internal/middleware/platform_app_auth.go
T
2026-06-04 15:44:48 +08:00

86 lines
3.1 KiB
Go

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()
}
}