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"), }}) }