145 lines
4.6 KiB
Go
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"),
|
|
}})
|
|
}
|