[bugfix] websocket header token not always returned (#4009)

* always include headerToken response if provided, because Chrome *sigh*

* wording

* Update internal/api/client/streaming/stream.go

Co-authored-by: Ilia Pozdnyakov <iliazeus@proton.me>

---------

Co-authored-by: Ilia Pozdnyakov <iliazeus@proton.me>
This commit is contained in:
kim
2025-04-14 13:12:09 +00:00
committed by GitHub
parent 67fc1fd904
commit f5ce219844

View File

@@ -18,6 +18,7 @@
package streaming package streaming
import ( import (
"cmp"
"context" "context"
"net/http" "net/http"
"slices" "slices"
@@ -153,25 +154,22 @@ var pingMsg = []byte("ping!")
// description: bad request // description: bad request
func (m *Module) StreamGETHandler(c *gin.Context) { func (m *Module) StreamGETHandler(c *gin.Context) {
var ( var (
token string
tokenInHeader bool
account *gtsmodel.Account account *gtsmodel.Account
errWithCode gtserror.WithCode errWithCode gtserror.WithCode
) )
if t := c.Query(AccessTokenQueryKey); t != "" { // Check both query parameter AND header "Sec-Websocket-Protocol"
// Token was provided as // value for a token. The latter is hacky and not technically
// query param, no problem. // correct, but some client do it since Mastodon allows it, so
token = t // we must allow it for compatibility.
} else if t := c.GetHeader(AccessTokenHeader); t != "" {
// Token was provided in "Sec-Websocket-Protocol" header.
// //
// This is hacky and not technically correct but some // Chrome also *always* expects the "Sec-Websocket-Protocol"
// clients do it since Mastodon allows it, so we must // response value to match input, so it must always be checked.
// also allow it to avoid breaking expectations. queryToken := c.Query(AccessTokenQueryKey)
token = t headerToken := c.GetHeader(AccessTokenHeader)
tokenInHeader = true
} // Prefer query token else use header token.
token := cmp.Or(queryToken, headerToken)
if token != "" { if token != "" {
@@ -229,8 +227,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) {
return return
} }
l := log. l := log.WithContext(c.Request.Context()).
WithContext(c.Request.Context()).
WithField("streamID", id.NewULID()). WithField("streamID", id.NewULID()).
WithField("username", account.Username) WithField("username", account.Username)
@@ -241,12 +238,14 @@ func (m *Module) StreamGETHandler(c *gin.Context) {
// If the upgrade fails, then Upgrade replies to the client // If the upgrade fails, then Upgrade replies to the client
// with an HTTP error response. // with an HTTP error response.
var responseHeader http.Header var responseHeader http.Header
if tokenInHeader { if headerToken != "" {
// Return the token in the response,
// else Chrome fails to connect. // Chrome always requires the input header
// token to be provided in response, even
// if it also provided via query... *shrugs*
// //
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism#sec-websocket-protocol // https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism#sec-websocket-protocol
responseHeader = http.Header{AccessTokenHeader: {token}} responseHeader = http.Header{AccessTokenHeader: {headerToken}}
} }
wsConn, err := m.wsUpgrade.Upgrade(c.Writer, c.Request, responseHeader) wsConn, err := m.wsUpgrade.Upgrade(c.Writer, c.Request, responseHeader)