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:
@@ -11,6 +11,7 @@ from app.services.policy_service import (
|
|||||||
resolve_api_actor,
|
resolve_api_actor,
|
||||||
resolve_web_actor,
|
resolve_web_actor,
|
||||||
)
|
)
|
||||||
|
from app.utils.auth import remember_task_access
|
||||||
|
|
||||||
tasks_bp = Blueprint("tasks", __name__)
|
tasks_bp = Blueprint("tasks", __name__)
|
||||||
|
|
||||||
@@ -52,6 +53,17 @@ def get_task_status(task_id: str):
|
|||||||
task_result = result.result or {}
|
task_result = result.result or {}
|
||||||
response["result"] = task_result
|
response["result"] = task_result
|
||||||
|
|
||||||
|
# Remember the file UUID in the session so the download route can verify access.
|
||||||
|
# The download URL contains a different UUID than the Celery task ID.
|
||||||
|
download_url = task_result.get("download_url", "")
|
||||||
|
if download_url:
|
||||||
|
parts = download_url.split("/")
|
||||||
|
# URL format: /api/download/<file_uuid>/<filename>
|
||||||
|
if len(parts) >= 4:
|
||||||
|
file_uuid = parts[3]
|
||||||
|
if file_uuid != task_id:
|
||||||
|
remember_task_access(file_uuid)
|
||||||
|
|
||||||
elif result.state == "FAILURE":
|
elif result.state == "FAILURE":
|
||||||
response["error"] = str(result.info) if result.info else "Task failed."
|
response["error"] = str(result.info) if result.info else "Task failed."
|
||||||
|
|
||||||
|
|||||||
@@ -678,6 +678,23 @@ def has_task_access(user_id: int, source: str, task_id: str) -> bool:
|
|||||||
return row is not None
|
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
|
# Password reset tokens
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from app.services.account_service import (
|
|||||||
get_api_key_actor,
|
get_api_key_actor,
|
||||||
get_user_by_id,
|
get_user_by_id,
|
||||||
get_current_period_month,
|
get_current_period_month,
|
||||||
|
has_download_access,
|
||||||
has_task_access,
|
has_task_access,
|
||||||
normalize_plan,
|
normalize_plan,
|
||||||
record_usage_event,
|
record_usage_event,
|
||||||
@@ -227,7 +228,12 @@ def build_task_tracking_kwargs(actor: ActorContext) -> dict:
|
|||||||
|
|
||||||
def assert_api_task_access(actor: ActorContext, task_id: str):
|
def assert_api_task_access(actor: ActorContext, task_id: str):
|
||||||
"""Ensure one API actor can poll one task id."""
|
"""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)
|
raise PolicyError("Task not found.", 404)
|
||||||
|
|
||||||
|
|
||||||
@@ -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):
|
if actor.user_id is not None and has_task_access(actor.user_id, "web", task_id):
|
||||||
return
|
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):
|
if has_session_task_access(task_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user