Files
gochat/backend/internal/middleware/connector_service_auth.go
T

145 lines
4.6 KiB
Go

package middleware
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/model"
"github.com/google/uuid"
"gorm.io/gorm"
)
const ConnectorPlatformAppIDKey = "connector_platform_app_id"
func ConnectorServiceAuth(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
setConnectorRequestID(c)
app, accessToken, ok := authenticateShangwutongConnector(c, db)
if !ok {
connectorAuthError(c)
return
}
setConnectorPrincipal(c, db, app, accessToken)
c.Next()
}
}
func AuthMiddlewareWithConnectorAllowlist(jwtService *auth.JWTService, db *gorm.DB) gin.HandlerFunc {
userAuth := AuthMiddlewareWithServiceAndDB(jwtService, db)
return func(c *gin.Context) {
if connectorApplicationRoute(c) {
setConnectorRequestID(c)
if app, accessToken, ok := authenticateShangwutongConnector(c, db); ok {
accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64)
if err != nil || accountID == 0 {
connectorPermissionError(c)
return
}
var count int64
if err := db.WithContext(c.Request.Context()).Model(&model.Permissible{}).Where(
"platform_app_id = ? AND permissible_type = ? AND permissible_id = ?", app.ID, model.PermissibleTypeAccount, uint(accountID),
).Count(&count).Error; err != nil || count == 0 {
connectorPermissionError(c)
return
}
setConnectorPrincipal(c, db, app, accessToken)
c.Set("connector_account_authorized", true)
c.Set("account_id", uint(accountID))
c.Next()
return
}
}
userAuth(c)
}
}
func connectorApplicationRoute(c *gin.Context) bool {
switch c.FullPath() {
case "/api/v1/accounts/:account_id/conversations/:conversation_id/messages":
return c.Request.Method == http.MethodPost
case "/api/v1/accounts/:account_id/conversations/:conversation_id/custom_attributes",
"/api/v1/accounts/:account_id/conversations/:conversation_id/toggle_status":
return c.Request.Method == http.MethodPost
case "/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id":
return c.Request.Method == http.MethodDelete
default:
return false
}
}
func authenticateShangwutongConnector(c *gin.Context, db *gorm.DB) (*model.PlatformApp, *model.AccessToken, bool) {
parts := strings.Fields(c.GetHeader("Authorization"))
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") || strings.TrimSpace(parts[1]) == "" {
return nil, nil, false
}
hash := sha256.Sum256([]byte(parts[1]))
var accessToken model.AccessToken
if err := db.WithContext(c.Request.Context()).Where(
"token = ? AND owner_type = ?", hex.EncodeToString(hash[:]), model.AccessTokenOwnerTypePlatformApp,
).First(&accessToken).Error; err != nil {
return nil, nil, false
}
var app model.PlatformApp
if err := db.WithContext(c.Request.Context()).First(&app, accessToken.OwnerID).Error; err != nil || !app.IsActive() || !isShangwutongConnectorApp(&app) {
return nil, nil, false
}
return &app, &accessToken, true
}
func setConnectorPrincipal(c *gin.Context, db *gorm.DB, app *model.PlatformApp, accessToken *model.AccessToken) {
c.Set(ConnectorPlatformAppIDKey, app.ID)
c.Set("connector_service_principal", *app)
_ = db.WithContext(c.Request.Context()).Model(&model.AccessToken{}).Where("id = ?", accessToken.ID).Update("last_used_at", gorm.Expr("CURRENT_TIMESTAMP")).Error
}
func setConnectorRequestID(c *gin.Context) {
requestID := strings.TrimSpace(c.GetHeader("X-Request-ID"))
if requestID == "" {
requestID = uuid.NewString()
}
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
}
func ConnectorPlatformAppID(c *gin.Context) uint {
value, _ := c.Get(ConnectorPlatformAppIDKey)
id, _ := value.(uint)
return id
}
func IsConnectorService(c *gin.Context) bool {
return ConnectorPlatformAppID(c) != 0
}
func isShangwutongConnectorApp(app *model.PlatformApp) bool {
if app == nil || app.Type != "integration" {
return false
}
config := map[string]any{}
if len(app.Config) == 0 || json.Unmarshal(app.Config, &config) != nil {
return false
}
connector, _ := config["connector"].(string)
return connector == "shangwutong"
}
func connectorAuthError(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": gin.H{
"code": "unauthorized", "message": "connector authentication failed", "retryable": false,
"request_id": c.GetString("request_id"),
}})
}
func connectorPermissionError(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": gin.H{
"code": "forbidden", "message": "connector account access denied", "retryable": false,
"request_id": c.GetString("request_id"),
}})
}