fix: resolve download 404 caused by file UUID / Celery task ID mismatch\n\nThe download route checked access using the file UUID from the URL,\nbut the session and usage_events only stored the Celery task ID.\nThese are different UUIDs, causing all downloads to return 404.\n\nFixes:\n- Add has_download_access() to check file_history table as fallback\n- Update assert_web/api_task_access to use file_history lookup\n- Remember file UUID in session when task status returns SUCCESS"

This commit is contained in:
Your Name
2026-03-20 10:07:48 +02:00
parent 94b23e511e
commit 0174f935c3
3 changed files with 39 additions and 1 deletions

View File

@@ -678,6 +678,23 @@ def has_task_access(user_id: int, source: str, task_id: str) -> bool:
return row is not None
def has_download_access(user_id: int, file_task_id: str) -> bool:
"""Return whether one user owns a file_history entry whose download_url contains the given file task id."""
pattern = f"/api/download/{file_task_id}/"
with _connect() as conn:
row = conn.execute(
"""
SELECT 1
FROM file_history
WHERE user_id = ? AND download_url LIKE ?
LIMIT 1
""",
(user_id, f"%{pattern}%"),
).fetchone()
return row is not None
# ---------------------------------------------------------------------------
# Password reset tokens
# ---------------------------------------------------------------------------

View File

@@ -8,6 +8,7 @@ from app.services.account_service import (
get_api_key_actor,
get_user_by_id,
get_current_period_month,
has_download_access,
has_task_access,
normalize_plan,
record_usage_event,
@@ -227,8 +228,13 @@ def build_task_tracking_kwargs(actor: ActorContext) -> dict:
def assert_api_task_access(actor: ActorContext, task_id: str):
"""Ensure one API actor can poll one task id."""
if actor.user_id is None or not has_task_access(actor.user_id, "api", task_id):
if actor.user_id is None:
raise PolicyError("Task not found.", 404)
if has_task_access(actor.user_id, "api", task_id):
return
if has_download_access(actor.user_id, task_id):
return
raise PolicyError("Task not found.", 404)
def assert_web_task_access(actor: ActorContext, task_id: str):
@@ -236,6 +242,9 @@ def assert_web_task_access(actor: ActorContext, task_id: str):
if actor.user_id is not None and has_task_access(actor.user_id, "web", task_id):
return
if actor.user_id is not None and has_download_access(actor.user_id, task_id):
return
if has_session_task_access(task_id):
return