fix(security): stream tickets instead of session tokens in URLs (FLAW-22)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m32s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m32s
WebSocket and EventSource put the 30-day session token into the URL, where proxies log it. The app now gets a one-time ticket right before connecting (POST /auth/stream-ticket, 60 s, bound to the token version); ?token= is no longer accepted for streams, non-browser clients may use the Authorization header. With BASE_URL set, CORS allows only that origin and WebSocket upgrades only it or the requested host; credentials are off (no cookies exist). Logout, ending all sessions, a new password and deactivation close all live streams of the account; clients with a valid session reconnect with a fresh ticket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c711c2cf7
commit
f57a9884d1
17 files changed
+335
-41
No files matched your search
+26
-1
@@ -17,10 +17,22 @@ var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 4096,
|
||||
WriteBufferSize: 4096,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // Self-hosted reverse proxy compatible
|
||||
return originCheck(r)
|
||||
},
|
||||
}
|
||||
|
||||
// originCheck decides which browser origins may open a WebSocket (see SetOriginCheck). Without a
|
||||
// configured check every origin is accepted: proxies often rewrite the Host header, so a plain
|
||||
// same-host comparison would break working setups.
|
||||
var originCheck = func(*http.Request) bool { return true }
|
||||
|
||||
// SetOriginCheck installs the origin rule for WebSocket upgrades.
|
||||
func (h *Hub) SetOriginCheck(f func(*http.Request) bool) {
|
||||
if f != nil {
|
||||
originCheck = f
|
||||
}
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
hub *Hub
|
||||
conn *websocket.Conn
|
||||
@@ -371,6 +383,19 @@ func (h *Hub) BroadcastToUser(userID, event string, payload interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// DisconnectUser ends every live connection of a user (logout, all sessions ended, new password,
|
||||
// account deactivated). Clients reconnect with a fresh ticket if their session is still valid.
|
||||
func (h *Hub) DisconnectUser(userID string) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
for client := range h.userRooms[userID] {
|
||||
client.closeSend()
|
||||
}
|
||||
for sseClient := range h.sseUserRooms[userID] {
|
||||
sseClient.closeSend()
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastSystem sends an event to every connection. Only for events without user data (e.g. the
|
||||
// demo reset notice).
|
||||
func (h *Hub) BroadcastSystem(event string) {
|
||||
|
||||
Reference in new issue
Block a user