From 4d8974bc6699089c879de7296b31b800a7d967a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Sun, 1 Mar 2026 23:07:38 +0100 Subject: [PATCH] Restrict status endpoints to user's characters Filter live status responses so callers only see automations, workflows, and pipelines associated with the current user's characters. Backend: use get_user_character_names in automations.get_all_statuses, workflows.get_all_workflow_statuses, and pipelines.get_all_pipeline_statuses (pipeline filtering checks character_states). Frontend: clear React Query cache when a token is set or removed (import useQueryClient and add it to callback deps) so cached data is refreshed after auth changes. --- backend/app/api/automations.py | 5 +++-- backend/app/api/pipelines.py | 8 ++++++-- backend/app/api/workflows.py | 5 +++-- frontend/src/components/auth/auth-provider.tsx | 9 +++++++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/backend/app/api/automations.py b/backend/app/api/automations.py index f5390a7..8d31380 100644 --- a/backend/app/api/automations.py +++ b/backend/app/api/automations.py @@ -208,9 +208,10 @@ async def resume_automation(config_id: int, request: Request) -> None: @router.get("/status/all", response_model=list[AutomationStatusResponse]) async def get_all_statuses(request: Request) -> list[AutomationStatusResponse]: - """Get live status for all active automations.""" + """Get live status for active automations belonging to the current user.""" manager = _get_manager(request) - return manager.get_all_statuses() + user_chars = set(await get_user_character_names(request)) + return [s for s in manager.get_all_statuses() if s.character_name in user_chars] @router.get("/{config_id}/status", response_model=AutomationStatusResponse) diff --git a/backend/app/api/pipelines.py b/backend/app/api/pipelines.py index 48d72ba..90feedc 100644 --- a/backend/app/api/pipelines.py +++ b/backend/app/api/pipelines.py @@ -91,9 +91,13 @@ async def create_pipeline( @router.get("/status/all", response_model=list[PipelineStatusResponse]) async def get_all_pipeline_statuses(request: Request) -> list[PipelineStatusResponse]: - """Get live status for all active pipelines.""" + """Get live status for active pipelines belonging to the current user.""" manager = _get_manager(request) - return manager.get_all_pipeline_statuses() + user_chars = set(await get_user_character_names(request)) + return [ + s for s in manager.get_all_pipeline_statuses() + if any(cs.character_name in user_chars for cs in s.character_states) + ] @router.get("/{pipeline_id}", response_model=PipelineConfigDetailResponse) diff --git a/backend/app/api/workflows.py b/backend/app/api/workflows.py index a6f1003..ebe7cf7 100644 --- a/backend/app/api/workflows.py +++ b/backend/app/api/workflows.py @@ -83,9 +83,10 @@ async def create_workflow( @router.get("/status/all", response_model=list[WorkflowStatusResponse]) async def get_all_workflow_statuses(request: Request) -> list[WorkflowStatusResponse]: - """Get live status for all active workflows.""" + """Get live status for active workflows belonging to the current user.""" manager = _get_manager(request) - return manager.get_all_workflow_statuses() + user_chars = set(await get_user_character_names(request)) + return [s for s in manager.get_all_workflow_statuses() if s.character_name in user_chars] @router.get("/{workflow_id}", response_model=WorkflowConfigDetailResponse) diff --git a/frontend/src/components/auth/auth-provider.tsx b/frontend/src/components/auth/auth-provider.tsx index ee388c3..95d1e0b 100644 --- a/frontend/src/components/auth/auth-provider.tsx +++ b/frontend/src/components/auth/auth-provider.tsx @@ -7,6 +7,7 @@ import { useEffect, useCallback, } from "react"; +import { useQueryClient } from "@tanstack/react-query"; import { setAuthToken, type AuthStatus, @@ -33,6 +34,7 @@ export function useAuth() { } export function AuthProvider({ children }: { children: React.ReactNode }) { + const queryClient = useQueryClient(); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(true); @@ -54,6 +56,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const result = await setAuthToken(token); if (result.success) { localStorage.setItem(STORAGE_KEY, token); + // Clear all cached data so the new user gets fresh data + queryClient.clear(); setStatus({ has_token: true, source: "user" }); return { success: true }; } @@ -65,13 +69,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }; } }, - [] + [queryClient] ); const handleRemoveToken = useCallback(async () => { localStorage.removeItem(STORAGE_KEY); + queryClient.clear(); setStatus({ has_token: false, source: "none" }); - }, []); + }, [queryClient]); return (