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
@@ -62,7 +62,8 @@ func newTestUser(t *testing.T, store *storage.Store, email string) (*domain.User
|
||||
func openSSE(t *testing.T, ts *httptest.Server, token string) *sseStream {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+"/api/v1/events?token="+token, nil)
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+"/api/v1/events", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
@@ -271,8 +272,8 @@ func TestReorderRejectsForeignTasks(t *testing.T) {
|
||||
|
||||
func dialWS(t *testing.T, ts *httptest.Server, token string) *websocket.Conn {
|
||||
t.Helper()
|
||||
url := "ws" + strings.TrimPrefix(ts.URL, "http") + "/api/v1/ws?token=" + token
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
url := "ws" + strings.TrimPrefix(ts.URL, "http") + "/api/v1/ws"
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url, http.Header{"Authorization": {"Bearer " + token}})
|
||||
if err != nil {
|
||||
t.Fatalf("dial ws: %v", err)
|
||||
}
|
||||
@@ -336,3 +337,20 @@ func TestDocChangesArePushedToTheNoteRoom(t *testing.T) {
|
||||
t.Fatalf("no DOC_SEQ push for the note: %+v", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutEndsLiveStreams(t *testing.T) {
|
||||
srv, _, ts, owner, token := startRealtimeServer(t)
|
||||
st := openSSE(t, ts, token)
|
||||
srv.wsHub.DisconnectUser(owner.ID)
|
||||
deadline := time.After(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-st.events:
|
||||
if !ok {
|
||||
return // stream closed by the server
|
||||
}
|
||||
case <-deadline:
|
||||
t.Fatal("stream still open after DisconnectUser")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user