fix(realtime): scope events to authorized users, authorize rooms, keep SSE alive
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m6s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m6s
Phase 0 of the collaboration plan: - Remove BroadcastToAll; task, task list and workspace folder events now only reach users who can see the resource. Task list events carry only the list ID, clients reload with their own permissions. - Reject unauthenticated WebSocket/SSE connections (401). Every room join (URL room, JOIN_ROOM, SSE rooms) is checked via a room authorizer; /events/rooms requires login and only accepts the caller's own sseId. - Reorder rejects tasks the caller cannot access or edit (IDOR). - Lift the server WriteTimeout for SSE streams so they are not cut after 30s. - Fix folder share removal sending DOC_DELETED to the share ID. - Tests for all of the above; docs and flaw list updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
74797e3f44
commit
bcc06da3ee
10 files changed
+526
-86
No files matched your search
+36
-2
@@ -22,7 +22,7 @@ func TestHubCloseAndGracefulShutdown(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify broadcast works
|
||||
hub.BroadcastToAll("TEST_EVENT", map[string]string{"msg": "hello"})
|
||||
hub.BroadcastToUser("user-1", "TEST_EVENT", map[string]string{"msg": "hello"})
|
||||
|
||||
// Concurrently invoke Close() and Broadcast() to verify no panics or deadlocks
|
||||
var wg sync.WaitGroup
|
||||
@@ -31,7 +31,7 @@ func TestHubCloseAndGracefulShutdown(t *testing.T) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 50; i++ {
|
||||
hub.BroadcastToAll("SPAM", map[string]int{"i": i})
|
||||
hub.BroadcastToUser("user-1", "SPAM", map[string]int{"i": i})
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
@@ -56,3 +56,37 @@ func TestHubCloseAndGracefulShutdown(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomJoinsRequireAuthorization(t *testing.T) {
|
||||
hub := NewHub()
|
||||
c := &Client{hub: hub, userID: "user-1", send: make(chan []byte, 10)}
|
||||
|
||||
if hub.JoinRoom(c, "lst_1") {
|
||||
t.Fatal("join must be denied without an authorizer")
|
||||
}
|
||||
|
||||
hub.SetRoomAuthorizer(func(userID, roomID string) bool {
|
||||
return userID == "user-1" && roomID == "lst_allowed"
|
||||
})
|
||||
if hub.JoinRoom(c, "lst_other") {
|
||||
t.Fatal("join to an unauthorized room must be denied")
|
||||
}
|
||||
if !hub.JoinRoom(c, "lst_allowed") {
|
||||
t.Fatal("join to an authorized room must succeed")
|
||||
}
|
||||
|
||||
anon := &Client{hub: hub, userID: "anonymous", send: make(chan []byte, 10)}
|
||||
if hub.JoinRoom(anon, "lst_allowed") {
|
||||
t.Fatal("anonymous clients must never join rooms")
|
||||
}
|
||||
|
||||
sse := &SSEClient{id: "sse_1", userID: "user-1", send: make(chan []byte, 10),
|
||||
subscribedRooms: map[string]bool{"lst_allowed": true, "doc:secret": true}}
|
||||
hub.RegisterSSE(sse)
|
||||
if sse.subscribedRooms["doc:secret"] {
|
||||
t.Fatal("unauthorized initial SSE rooms must be dropped")
|
||||
}
|
||||
if hub.JoinSSERoomByID("sse_1", "user-2", "lst_allowed") {
|
||||
t.Fatal("joining with another user's sseId must fail")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user