security(transport): subprotocol + Authorization header for webchat auth #2

Merged
serge merged 1 commits from feat/webchat-auth-hardening into main 2026-04-27 16:36:16 +03:00
Owner

Résumé

Companion à serge/messenger-bot PR feat/webchat-auth-hardening. Doit être bundlée/déployée en parallèle du rebuild bot (breaking wire format).

Changements

  • WebSocket handshake : passe les credentials via Sec-WebSocket-Protocol subprotocols (messenzy.v1, messenzy-bot.<id>, messenzy-visitor.<id>, messenzy-key.<key>). Le constructor WebSocket(url, protocols) browser ne permet pas de set custom headers, donc subprotocols sont le pattern standard.
  • HTTP fallback (/webchat/msg, /webchat/history) : Authorization: Bearer <apiKey> (browser fetch supporte les custom headers, contrairement au WebSocket ctor).
  • botId et visitorId restent dans body/query (public identifiers). Seul apiKey part de l'URL.

API publique inchangée

createTransport(opts: TransportOpts) prend toujours { botId, apiKey, visitorId, serverUrl, onMessage, onConnectionChange? }. Les consommateurs (Widget component) n'ont rien à modifier.

Pourquoi

Query strings sont log par les reverse proxies (Caddy default access log inclut le path complet), browser history, et HTTP Referer headers cross-origin. La clé widget_api_key était leakable sur tous ces vecteurs. Subprotocol + Bearer header sont invisibles par défaut côté logs.

Test plan

  • tsc --noEmit clean.
  • Build IIFE bundle (vite build) → tester sur une page locale avec un bot setup en mode dev.
  • Devtools Network → confirmer que la WS handshake URL ne contient plus ?apiKey=... mais que Sec-WebSocket-Protocol request header inclut messenzy-key.<...>.
  • Devtools Network → confirmer que /webchat/msg POST request a Authorization: Bearer <...> header.

Cutover

Ce widget bundle doit être (re)déployé EN MÊME TEMPS que le bot rebuild post-merge bot PR. Les vieux clients (avec query string) seront rejetés en 401 par le nouveau bot. Comme le widget est encore en alpha, impact = 0 utilisateur réel.

## Résumé Companion à `serge/messenger-bot` PR `feat/webchat-auth-hardening`. Doit être bundlée/déployée en parallèle du rebuild bot (breaking wire format). ## Changements * WebSocket handshake : passe les credentials via `Sec-WebSocket-Protocol` subprotocols (`messenzy.v1`, `messenzy-bot.<id>`, `messenzy-visitor.<id>`, `messenzy-key.<key>`). Le constructor `WebSocket(url, protocols)` browser ne permet pas de set custom headers, donc subprotocols sont le pattern standard. * HTTP fallback (`/webchat/msg`, `/webchat/history`) : `Authorization: Bearer <apiKey>` (browser fetch supporte les custom headers, contrairement au WebSocket ctor). * `botId` et `visitorId` restent dans body/query (public identifiers). Seul `apiKey` part de l'URL. ## API publique inchangée `createTransport(opts: TransportOpts)` prend toujours `{ botId, apiKey, visitorId, serverUrl, onMessage, onConnectionChange? }`. Les consommateurs (`Widget` component) n'ont rien à modifier. ## Pourquoi Query strings sont log par les reverse proxies (Caddy default access log inclut le path complet), browser history, et HTTP Referer headers cross-origin. La clé `widget_api_key` était leakable sur tous ces vecteurs. Subprotocol + Bearer header sont invisibles par défaut côté logs. ## Test plan - [ ] `tsc --noEmit` clean. - [ ] Build IIFE bundle (`vite build`) → tester sur une page locale avec un bot setup en mode dev. - [ ] Devtools Network → confirmer que la WS handshake URL ne contient plus `?apiKey=...` mais que `Sec-WebSocket-Protocol` request header inclut `messenzy-key.<...>`. - [ ] Devtools Network → confirmer que `/webchat/msg` POST request a `Authorization: Bearer <...>` header. ## Cutover Ce widget bundle doit être (re)déployé EN MÊME TEMPS que le bot rebuild post-merge bot PR. Les vieux clients (avec query string) seront rejetés en 401 par le nouveau bot. Comme le widget est encore en alpha, impact = 0 utilisateur réel.
serge added 1 commit 2026-04-27 16:27:26 +03:00
Match the bot-side hardening
(serge/messenger-bot feat/webchat-auth-hardening): credentials no longer
leak via URL query strings.

  * WebSocket handshake uses Sec-WebSocket-Protocol subprotocols
    (messenzy.v1, messenzy-bot.<id>, messenzy-visitor.<id>,
    messenzy-key.<key>) — the browser WebSocket ctor doesn't accept
    custom headers, so subprotocols are the standard pattern.

  * HTTP fallback (/webchat/msg, /webchat/history) uses
    `Authorization: Bearer <apiKey>` — fetch supports custom headers.

  * botId/visitorId stay in body/query as public identifiers; only the
    apiKey moves off the URL.

No public API change — `createTransport(opts)` takes the same
TransportOpts as before.
serge merged commit c5d1880589 into main 2026-04-27 16:36:16 +03:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: serge/messenzy-widget#2
No description provided.