Compare commits

..

1 Commits

Author SHA1 Message Date
ي
b54c2978c3 Restrict podcast task status access by owner 2026-03-30 08:05:44 +05:30
6 changed files with 76 additions and 72 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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"],

View File

@@ -48,9 +48,6 @@ load_dotenv(backend_dir / '.env') # backend/.env
load_dotenv(project_root / '.env') # root .env (fallback)
load_dotenv() # CWD .env (fallback)
# Feature flags (read early so app wiring can rely on a single source of truth)
PODCAST_ONLY_DEMO_MODE = os.getenv("PODCAST_ONLY_DEMO_MODE", "false").lower() == "true"
# Set up clean logging for end users
from logging_config import setup_clean_logging
setup_clean_logging()
@@ -408,62 +405,48 @@ async def analyze_urls_ai_endpoint(request: AnalyzeURLsRequest, current_user: di
"""Run AI-powered SEO analysis on selected URLs."""
return await analyze_urls_ai(request, current_user)
# Centralized mode helpers for router wiring.
# Keep all mode decisions in this section to avoid scattered env checks.
def is_podcast_only_demo_mode() -> bool:
return PODCAST_ONLY_DEMO_MODE
# Include platform analytics router
from routers.platform_analytics import router as platform_analytics_router
app.include_router(platform_analytics_router)
# Include Bing Analytics Storage router to expose storage-backed endpoints
from routers.bing_analytics_storage import router as bing_analytics_storage_router
app.include_router(bing_analytics_storage_router)
app.include_router(images_router)
app.include_router(image_studio_router)
app.include_router(product_marketing_router)
app.include_router(campaign_creator_router)
# Include content assets router
from api.content_assets.router import router as content_assets_router
app.include_router(content_assets_router)
def should_include_non_podcast_routers() -> bool:
return not is_podcast_only_demo_mode()
# Include Podcast Maker router (available in all modes)
# Include Podcast Maker router
from api.podcast.router import router as podcast_router
app.include_router(podcast_router)
if should_include_non_podcast_routers():
# Include platform analytics router
from routers.platform_analytics import router as platform_analytics_router
app.include_router(platform_analytics_router)
# Include YouTube Creator Studio router
from api.youtube.router import router as youtube_router
app.include_router(youtube_router, prefix="/api")
# Include Bing Analytics Storage router to expose storage-backed endpoints
from routers.bing_analytics_storage import router as bing_analytics_storage_router
app.include_router(bing_analytics_storage_router)
app.include_router(images_router)
app.include_router(image_studio_router)
app.include_router(product_marketing_router)
app.include_router(campaign_creator_router)
# Include research configuration router
app.include_router(research_config_router, prefix="/api/research", tags=["research"])
# Include content assets router
from api.content_assets.router import router as content_assets_router
app.include_router(content_assets_router)
# Include Research Engine router (standalone AI research module)
from api.research.router import router as research_engine_router
app.include_router(research_engine_router, tags=["Research Engine"])
# Include YouTube Creator Studio router
from api.youtube.router import router as youtube_router
app.include_router(youtube_router, prefix="/api")
# Scheduler dashboard routes
from api.scheduler_dashboard import router as scheduler_dashboard_router
app.include_router(scheduler_dashboard_router)
app.include_router(oauth_token_monitoring_router)
# Include research configuration router
app.include_router(research_config_router, prefix="/api/research", tags=["research"])
# Autonomous Agents API routes (Phase 3A)
from api.agents_api import router as agents_router
app.include_router(agents_router)
# Include Research Engine router (standalone AI research module)
from api.research.router import router as research_engine_router
app.include_router(research_engine_router, tags=["Research Engine"])
# Scheduler dashboard routes
from api.scheduler_dashboard import router as scheduler_dashboard_router
app.include_router(scheduler_dashboard_router)
app.include_router(oauth_token_monitoring_router)
# Autonomous Agents API routes (Phase 3A)
from api.agents_api import router as agents_router
app.include_router(agents_router)
# Today workflow routes
from api.today_workflow import router as today_workflow_router
app.include_router(today_workflow_router)
else:
logger.info("PODCAST_ONLY_DEMO_MODE is enabled: non-podcast routers are not registered.")
# Today workflow routes
from api.today_workflow import router as today_workflow_router
app.include_router(today_workflow_router)
# Setup frontend serving using modular utilities
frontend_serving.setup_frontend_serving()

View File

@@ -298,11 +298,6 @@ def main():
parser.add_argument("--dev", action="store_true", help="Enable development mode (auto-reload)")
parser.add_argument("--production", action="store_true", help="Enable production mode (optimized for deployment)")
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging for debugging")
parser.add_argument(
"--podcast-only-demo",
action="store_true",
help="Enable podcast-only demo mode (sets PODCAST_ONLY_DEMO_MODE=true before app startup)",
)
args = parser.parse_args()
# Determine mode
@@ -312,8 +307,6 @@ def main():
# Set global verbose flag for utilities
os.environ["ALWRITY_VERBOSE"] = "true" if verbose_mode else "false"
if args.podcast_only_demo:
os.environ["PODCAST_ONLY_DEMO_MODE"] = "true"
print("[*] ALwrity Backend Server")
print("=" * 40)
@@ -321,8 +314,6 @@ def main():
print(f"Auto-reload: {'ENABLED' if enable_reload else 'DISABLED'}")
if verbose_mode:
print("Verbose logging: ENABLED")
if args.podcast_only_demo:
print("Podcast-only demo mode: ENABLED")
print("=" * 40)
# Check if we're in the right directory
@@ -410,4 +401,4 @@ def main():
if __name__ == "__main__":
success = main()
if not success:
sys.exit(1)
sys.exit(1)