H-337: restore Web widget reply visibility (#64)

* H-337: restore widget reply delivery

* H-337: harden widget conversation ownership

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-20 22:22:27 +08:00
committed by GitHub
co-authored by rogee
parent abb1bed424
commit b31b1b9562
22 changed files with 847 additions and 83 deletions
+21 -9
View File
@@ -26,7 +26,7 @@ func uintToStr(u uint) string {
//
// Supports two authentication paths:
// 1. Agent/User auth: JWT token (from query param or Authorization header)
// 2. Contact auth: pubsub_token + user_id (Chatwoot RoomChannel pattern)
// 2. Contact auth: pubsub_token (Chatwoot RoomChannel pattern)
type Handler struct {
hub *Hub
authenticator *wspkg.WSAuthenticator
@@ -55,7 +55,7 @@ func NewHandler(hub *Hub, authenticator *wspkg.WSAuthenticator) *Handler {
}
// ServeWS handles the WebSocket upgrade request at /ws.
// URL: /ws?token=<JWT_ACCESS_TOKEN> OR /ws?pubsub_token=<TOKEN>&user_id=<ID>
// URL: /ws?token=<JWT_ACCESS_TOKEN> OR /ws?pubsub_token=<TOKEN>
// On successful upgrade, the handler:
// 1. Authenticates the request via WSAuthenticator (JWT or pubsub_token)
// 2. Authorizes the user/contact for the requested account
@@ -272,8 +272,19 @@ func (h *Handler) handleSubscribe(client *Client, cmd CommandFrame) {
return
}
// Validate account_id matches the client's authenticated account
if identifier.AccountID != client.AccountID {
room := ""
if client.IsContact {
if identifier.Channel != ChannelRoom || identifier.PubsubToken == "" || identifier.PubsubToken != client.PubsubToken {
rejectData, _ := json.Marshal(RejectFrame{
Type: ServerRejectSubscribe,
Identifier: cmd.Identifier,
Reason: "invalid contact RoomChannel subscription",
})
client.Send <- rejectData
return
}
room = pubsubTokenRoomName(identifier.PubsubToken)
} else if identifier.AccountID != client.AccountID {
rejectData, _ := json.Marshal(RejectFrame{
Type: ServerRejectSubscribe,
Identifier: cmd.Identifier,
@@ -283,14 +294,15 @@ func (h *Handler) handleSubscribe(client *Client, cmd CommandFrame) {
return
}
// Determine room name based on channel type (uses Hub's canonical naming)
room := ""
switch identifier.Channel {
case ChannelAccount, ChannelRoom:
// Determine room name based on channel type (uses Hub's canonical naming).
// Contact RoomChannel was resolved above from its authenticated token.
switch {
case room != "":
case identifier.Channel == ChannelAccount || identifier.Channel == ChannelRoom:
// RoomChannel is Chatwoot's single-subscription model — it maps
// to the account room (all account-level events are delivered).
room = accountRoomName(identifier.AccountID)
case ChannelConversation:
case identifier.Channel == ChannelConversation:
if identifier.ConversationID == 0 {
rejectData, _ := json.Marshal(RejectFrame{
Type: ServerRejectSubscribe,