From b54c2978c3fec77e33381d941586529e32e99713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=8A?= Date: Mon, 30 Mar 2026 08:05:44 +0530 Subject: [PATCH] Restrict podcast task status access by owner --- backend/api/podcast/handlers/dubbing.py | 14 ++++++++++---- backend/api/podcast/handlers/video.py | 12 +++++++++--- backend/api/podcast/router.py | 9 ++++++--- backend/api/story_writer/task_manager.py | 21 ++++++++++++++++++--- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/backend/api/podcast/handlers/dubbing.py b/backend/api/podcast/handlers/dubbing.py index 224de8ed..846790d9 100644 --- a/backend/api/podcast/handlers/dubbing.py +++ b/backend/api/podcast/handlers/dubbing.py @@ -203,7 +203,10 @@ async def create_audio_dubbing_task( """ user_id = require_authenticated_user(current_user) - task_id = task_manager.create_task("audio_dubbing") + task_id = task_manager.create_task( + "audio_dubbing", + metadata={"owner_user_id": user_id}, + ) background_tasks.add_task( _execute_dubbing_task, @@ -240,7 +243,7 @@ async def get_dubbing_result( """ user_id = require_authenticated_user(current_user) - task_status = task_manager.get_task_status(task_id) + task_status = task_manager.get_task_status(task_id, requester_user_id=user_id) if not task_status: raise HTTPException(status_code=404, detail="Task not found") @@ -403,7 +406,10 @@ async def create_voice_clone_task( """ user_id = require_authenticated_user(current_user) - task_id = task_manager.create_task("voice_clone") + task_id = task_manager.create_task( + "voice_clone", + metadata={"owner_user_id": user_id}, + ) background_tasks.add_task( _execute_voice_clone_task, @@ -434,7 +440,7 @@ async def get_voice_clone_result( """ user_id = require_authenticated_user(current_user) - task_status = task_manager.get_task_status(task_id) + task_status = task_manager.get_task_status(task_id, requester_user_id=user_id) if not task_status: raise HTTPException(status_code=404, detail="Task not found") diff --git a/backend/api/podcast/handlers/video.py b/backend/api/podcast/handlers/video.py index 0d7c9c8b..1f3ecef7 100644 --- a/backend/api/podcast/handlers/video.py +++ b/backend/api/podcast/handlers/video.py @@ -222,7 +222,7 @@ def _execute_podcast_video_task( ) # Verify the task status was updated correctly - updated_status = task_manager.get_task_status(task_id) + updated_status = task_manager.get_task_status(task_id, requester_user_id=user_id) logger.info( f"[Podcast] Task status after update: task_id={task_id}, status={updated_status.get('status') if updated_status else 'None'}, has_result={bool(updated_status.get('result') if updated_status else False)}, video_url={updated_status.get('result', {}).get('video_url') if updated_status else 'N/A'}" ) @@ -358,7 +358,10 @@ async def generate_podcast_video( logger.warning(f"[Podcast] Failed to extract auth token from headers: {e}") # Create async task - task_id = task_manager.create_task("podcast_video_generation") + task_id = task_manager.create_task( + "podcast_video_generation", + metadata={"owner_user_id": user_id}, + ) background_tasks.add_task( _execute_podcast_video_task, task_id=task_id, @@ -488,7 +491,10 @@ async def combine_podcast_videos( raise HTTPException(status_code=400, detail="No scene videos provided") # Create async task - task_id = task_manager.create_task("podcast_combine_videos") + task_id = task_manager.create_task( + "podcast_combine_videos", + metadata={"owner_user_id": user_id}, + ) # Extract token for authenticated URL building auth_token = None diff --git a/backend/api/podcast/router.py b/backend/api/podcast/router.py index 8fd3cf65..c3b66234 100644 --- a/backend/api/podcast/router.py +++ b/backend/api/podcast/router.py @@ -4,7 +4,7 @@ Podcast Maker API Router Main router that imports and registers all handler modules. """ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from typing import Dict, Any from middleware.auth_middleware import get_current_user @@ -32,5 +32,8 @@ router.include_router(dubbing.router) @router.get("/task/{task_id}/status") async def podcast_task_status(task_id: str, current_user: Dict[str, Any] = Depends(get_current_user)): """Expose task status under podcast namespace (reuses shared task manager).""" - require_authenticated_user(current_user) - return task_manager.get_task_status(task_id) + user_id = require_authenticated_user(current_user) + task_status = task_manager.get_task_status(task_id, requester_user_id=user_id) + if not task_status: + raise HTTPException(status_code=404, detail="Task not found") + return task_status diff --git a/backend/api/story_writer/task_manager.py b/backend/api/story_writer/task_manager.py index 42415269..0886d95b 100644 --- a/backend/api/story_writer/task_manager.py +++ b/backend/api/story_writer/task_manager.py @@ -34,9 +34,14 @@ class TaskManager: del self.task_storage[task_id] logger.debug(f"[StoryWriter] Cleaned up old task: {task_id}") - def create_task(self, task_type: str = "story_generation") -> str: + def create_task( + self, + task_type: str = "story_generation", + metadata: Optional[Dict[str, Any]] = None, + ) -> str: """Create a new task and return its ID.""" task_id = str(uuid.uuid4()) + task_metadata = metadata or {} self.task_storage[task_id] = { "status": "pending", @@ -45,13 +50,14 @@ class TaskManager: "error": None, "progress_messages": [], "task_type": task_type, - "progress": 0.0 + "progress": 0.0, + "metadata": task_metadata, } logger.info(f"[StoryWriter] Created task: {task_id} (type: {task_type})") return task_id - def get_task_status(self, task_id: str) -> Optional[Dict[str, Any]]: + def get_task_status(self, task_id: str, requester_user_id: Optional[str] = None) -> Optional[Dict[str, Any]]: """Get the status of a task.""" self.cleanup_old_tasks() @@ -62,6 +68,15 @@ class TaskManager: return None task = self.task_storage[task_id] + metadata = task.get("metadata", {}) or {} + owner_user_id = metadata.get("owner_user_id") + + if requester_user_id is not None and owner_user_id is not None and requester_user_id != owner_user_id: + logger.warning( + f"[StoryWriter] Task access denied for task {task_id}: requester does not match owner" + ) + return None + response = { "task_id": task_id, "status": task["status"],