feat(hosted-file): implement hash-based download action and flexible auth
- Adds GET /v3/action/hosted_file/hash/{sha256}/download for direct content-addressable storage access.
- Updates V3 authentication dependencies to support 'api_key' in the query parameter (alias 'api_key').
- Implements auth_method: 'api_key' for machine-to-machine requests that provide a valid key but no user/account context.
- Updates GUIDE__V3_FRONTEND_API.md with the new endpoint and auth options.
This commit is contained in:
@@ -276,6 +276,50 @@ async def download_file_action(
|
||||
return FileResponse(full_file_path, filename=target_filename, media_type=media_type)
|
||||
|
||||
|
||||
@router.get('/hash/{sha256}/download')
|
||||
async def download_file_by_hash_action(
|
||||
response: Response,
|
||||
sha256: str = Path(min_length=64, max_length=64, regex='^[a-f0-9]{64}$'),
|
||||
filename: Optional[str] = Query(None, min_length=4, max_length=255),
|
||||
account: AccountContext = Depends(get_account_context_optional),
|
||||
delay: DelayParams = Depends(),
|
||||
):
|
||||
"""
|
||||
Direct hash-based download (Content-Addressable).
|
||||
- Skips DB lookup for path resolution.
|
||||
- Requires a valid API Key (via header or ?api_key=).
|
||||
- Ideal for local caching systems like Events Launcher.
|
||||
"""
|
||||
if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s)
|
||||
|
||||
# 1. Mandatory Auth Check
|
||||
# For now, we strictly require a valid machine API key (auth_method will not be 'guest')
|
||||
if account.auth_method == 'guest':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Valid API Key required for hash-based downloads."
|
||||
)
|
||||
|
||||
# 2. Path Resolution (Deterministic)
|
||||
hosted_files_path = settings.FILES_PATH['hosted_files_root']
|
||||
subdir = sha256[0:2]
|
||||
hash_filename = f"{sha256}.file"
|
||||
full_file_path = os.path.join(hosted_files_path, subdir, hash_filename)
|
||||
|
||||
if not os.path.exists(full_file_path):
|
||||
# Fallback to root (legacy structure)
|
||||
full_file_path = os.path.join(hosted_files_path, hash_filename)
|
||||
if not os.path.exists(full_file_path):
|
||||
log.error(f"Hash-based file not found: {sha256}")
|
||||
raise HTTPException(status_code=404, detail="File not found on server.")
|
||||
|
||||
# 3. Serve File
|
||||
target_filename = filename or f"file_{sha256[:8]}.bin"
|
||||
media_type = mimetypes.guess_type(target_filename)[0] or 'application/octet-stream'
|
||||
|
||||
return FileResponse(full_file_path, filename=target_filename, media_type=media_type)
|
||||
|
||||
|
||||
@router.delete('/{hosted_file_id}', response_model=Resp_Body_Base)
|
||||
async def delete_file_action(
|
||||
hosted_file_id: str = Path(min_length=11, max_length=22),
|
||||
|
||||
Reference in New Issue
Block a user