fix(download): strict ID typing — remove cross-resolution from hosted_file download
event_file download now resolves event_file_id → hosted_file_id explicitly before
delegating, rather than relying on a cross-resolution fallback inside the hosted_file
endpoint. The hosted_file download endpoint now only accepts hosted_file IDs.
Cross-resolution was added reactively (ea117bf) to patch incorrect frontend ID usage
and was never a deliberate design decision. With no per-record account ownership check
on the download path, the implicit ID aliasing was an unauditable gap.
- download_event_file_action: resolves event_file → hosted_file via Redis + SQL before
delegating; 404s explicitly if chain is broken
- download_file_action: strict hosted_file ID only; cross-resolution fallback removed
- Also fixes ?key= not being forwarded (was missing from event_file endpoint signature)
- TODO: per-record account ownership check (P1), archive_content download endpoint (P2)
- Docs: breaking change note added to frontend guide (remove ~2026-06-24)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -281,20 +281,34 @@ async def download_event_file_action(
|
||||
response: Response,
|
||||
event_file_id: str = Path(min_length=11, max_length=22),
|
||||
filename: Optional[str] = Query(None, min_length=4, max_length=255),
|
||||
key: Optional[str] = Query(None),
|
||||
site_key: Optional[str] = Query(None),
|
||||
range: Optional[str] = Header(None),
|
||||
account: AccountContext = Depends(get_account_context_optional),
|
||||
delay: DelayParams = Depends(),
|
||||
):
|
||||
"""
|
||||
Semantic alias for hosted_file download with Event-specific context.
|
||||
Download the underlying file for an event_file record.
|
||||
Resolves event_file_id → hosted_file_id explicitly before delegating.
|
||||
"""
|
||||
# Simply delegate to the universal hosted_file download logic
|
||||
ef_int_id = redis_lookup_id_random(record_id_random=event_file_id, table_name='event_file')
|
||||
if not ef_int_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Event file record not found.")
|
||||
|
||||
ef_rec = sql_select(sql="SELECT hosted_file_id FROM event_file WHERE id = :id", data={'id': ef_int_id})
|
||||
if not ef_rec or not ef_rec.get('hosted_file_id'):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Event file has no associated hosted file.")
|
||||
|
||||
hosted_file_id_random = get_id_random(record_id=ef_rec['hosted_file_id'], table_name='hosted_file')
|
||||
if not hosted_file_id_random:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Hosted file record not found.")
|
||||
|
||||
from app.routers.api_v3_actions_hosted_file import download_file_action
|
||||
return await download_file_action(
|
||||
response=response,
|
||||
hosted_file_id=event_file_id, # The universal downloader now resolves this!
|
||||
hosted_file_id=hosted_file_id_random,
|
||||
filename=filename,
|
||||
key=key,
|
||||
site_key=site_key,
|
||||
range=range,
|
||||
account=account,
|
||||
|
||||
@@ -227,28 +227,11 @@ async def download_file_action(
|
||||
if not is_authorized:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Authentication required or invalid access key.")
|
||||
|
||||
# 2. Resolve File Record
|
||||
# ID Vision: Attempt to resolve the ID.
|
||||
# 🛑 REMINDER: If adding a new specialized 'container' object (like event_person_profile),
|
||||
# ensure the lookup logic is mirrored here to allow direct downloads via container ID.
|
||||
# If not found in hosted_file, check if it's an event_file or archive_content ID that we can resolve.
|
||||
# 2. Resolve File Record — strict: only accepts hosted_file IDs.
|
||||
# Container objects (event_file, archive_content) must resolve to a hosted_file_id
|
||||
# in their own dedicated download endpoints before calling this function.
|
||||
resolved_id = redis_lookup_id_random(record_id_random=hosted_file_id, table_name='hosted_file')
|
||||
|
||||
if not resolved_id:
|
||||
log.info(f"ID {hosted_file_id} not found in hosted_file. Checking container tables...")
|
||||
# A. Check event_file
|
||||
if ef_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='event_file'):
|
||||
if ef_rec := sql_select(sql="SELECT hosted_file_id FROM event_file WHERE id = :id", data={'id': ef_id}):
|
||||
resolved_id = ef_rec.get('hosted_file_id')
|
||||
log.info(f"Resolved event_file {hosted_file_id} to hosted_file {resolved_id}")
|
||||
|
||||
# B. Check archive_content
|
||||
if not resolved_id:
|
||||
if ac_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='archive_content'):
|
||||
if ac_rec := sql_select(sql="SELECT hosted_file_id FROM archive_content WHERE id = :id", data={'id': ac_id}):
|
||||
resolved_id = ac_rec.get('hosted_file_id')
|
||||
log.info(f"Resolved archive_content {hosted_file_id} to hosted_file {resolved_id}")
|
||||
|
||||
if not resolved_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Hosted file record not found.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user