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
@@ -4747,6 +4747,55 @@ func (s *Store) GetTaskListShares(taskListID string) ([]domain.TaskListShare, er
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
// queryUserIDs runs a query returning a single user_id column and collects the distinct IDs.
|
||||
func (s *Store) queryUserIDs(query string, args ...interface{}) []string {
|
||||
rows, err := s.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
defer rows.Close()
|
||||
ids := make([]string, 0)
|
||||
seen := make(map[string]bool)
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if rows.Scan(&id) == nil && id != "" && !seen[id] {
|
||||
seen[id] = true
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetTaskListUserIDs returns every user who can see the task list: owner, direct shares, members of shared teams
|
||||
// and members of the legacy team_id.
|
||||
func (s *Store) GetTaskListUserIDs(listID string) []string {
|
||||
if listID == "" {
|
||||
return []string{}
|
||||
}
|
||||
return s.queryUserIDs(
|
||||
`SELECT user_id FROM task_lists WHERE id = ?
|
||||
UNION SELECT target_id FROM task_list_shares WHERE task_list_id = ? AND target_type = 'user'
|
||||
UNION SELECT tm.user_id FROM team_members tm
|
||||
WHERE tm.team_id IN (SELECT target_id FROM task_list_shares WHERE task_list_id = ? AND target_type = 'team')
|
||||
OR tm.team_id IN (SELECT team_id FROM task_lists WHERE id = ? AND team_id != '')`,
|
||||
listID, listID, listID, listID,
|
||||
)
|
||||
}
|
||||
|
||||
// GetSharedFolderUserIDs returns the owner of a shared workspace folder and everyone it is shared with.
|
||||
func (s *Store) GetSharedFolderUserIDs(folderID string) []string {
|
||||
if folderID == "" {
|
||||
return []string{}
|
||||
}
|
||||
return s.queryUserIDs(
|
||||
`SELECT owner_id FROM workspace_shared_folders WHERE id = ?
|
||||
UNION SELECT target_id FROM workspace_folder_shares WHERE folder_id = ? AND target_type = 'user'
|
||||
UNION SELECT tm.user_id FROM team_members tm
|
||||
WHERE tm.team_id IN (SELECT target_id FROM workspace_folder_shares WHERE folder_id = ? AND target_type = 'team')`,
|
||||
folderID, folderID, folderID,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Store) getMembersForTaskList(tl *domain.TaskList) []domain.TaskListMember {
|
||||
memberMap := make(map[string]domain.TaskListMember)
|
||||
|
||||
|
||||
Reference in new issue
Block a user