Encrypt file paths in API URLs using Fernet tokens
Raw filesystem paths were exposed in browser URLs, dev tools, and proxy logs. Now all file-serving endpoints accept an opaque encrypted token (t= param) derived from the session secret via HKDF, with a 4-hour TTL. Backend: - Add core/path_tokens.py with Fernet encrypt/decrypt (HKDF from .session_secret) - Add file_token to all list/gallery/feed/search responses across 7 routers - Accept optional t= param on all file-serving endpoints (backward compatible) Frontend: - Update 4 URL helpers in api.ts to prefer token when available - Add 4 new helpers for paid-content/embedded-metadata URLs - Update all 14 page/component files to pass file_token to URL builders - Add file_token to all relevant TypeScript interfaces Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ from ..core.exceptions import (
|
||||
ValidationError
|
||||
)
|
||||
from ..core.responses import now_iso8601
|
||||
from ..core.path_tokens import encode_path, decode_path
|
||||
from modules.universal_logger import get_logger
|
||||
from ..core.utils import (
|
||||
get_media_dimensions,
|
||||
@@ -177,6 +178,7 @@ async def get_media_thumbnail(
|
||||
file_path: str = None,
|
||||
media_type: str = None,
|
||||
token: str = None,
|
||||
t: str = None,
|
||||
current_user: Dict = Depends(get_current_user_media)
|
||||
):
|
||||
"""
|
||||
@@ -192,7 +194,10 @@ async def get_media_thumbnail(
|
||||
Args:
|
||||
file_path: Path to the media file
|
||||
media_type: 'image' or 'video'
|
||||
t: Encrypted file token (alternative to file_path)
|
||||
"""
|
||||
if t:
|
||||
file_path = decode_path(t)
|
||||
resolved_path = validate_file_path(file_path)
|
||||
|
||||
app_state = get_app_state()
|
||||
@@ -261,11 +266,14 @@ async def get_media_thumbnail(
|
||||
@handle_exceptions
|
||||
async def get_media_preview(
|
||||
request: Request,
|
||||
file_path: str,
|
||||
file_path: str = None,
|
||||
token: str = None,
|
||||
t: str = None,
|
||||
current_user: Dict = Depends(get_current_user_media)
|
||||
):
|
||||
"""Serve a media file for preview."""
|
||||
if t:
|
||||
file_path = decode_path(t)
|
||||
resolved_path = validate_file_path(file_path)
|
||||
|
||||
if not resolved_path.exists() or not resolved_path.is_file():
|
||||
@@ -283,12 +291,17 @@ async def get_media_preview(
|
||||
@handle_exceptions
|
||||
async def get_media_metadata(
|
||||
request: Request,
|
||||
file_path: str,
|
||||
file_path: str = None,
|
||||
t: str = None,
|
||||
current_user: Dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get cached metadata for a media file (resolution, duration, etc.).
|
||||
"""
|
||||
if t:
|
||||
file_path = decode_path(t)
|
||||
elif not file_path:
|
||||
raise ValidationError("Either 't' or 'file_path' is required")
|
||||
resolved_path = validate_file_path(file_path)
|
||||
|
||||
if not resolved_path.exists() or not resolved_path.is_file():
|
||||
@@ -381,7 +394,8 @@ async def get_media_metadata(
|
||||
@handle_exceptions
|
||||
async def get_embedded_metadata(
|
||||
request: Request,
|
||||
file_path: str,
|
||||
file_path: str = None,
|
||||
t: str = None,
|
||||
current_user: Dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
@@ -392,6 +406,8 @@ async def get_embedded_metadata(
|
||||
|
||||
This is different from /metadata which returns technical info (resolution, duration).
|
||||
"""
|
||||
if t:
|
||||
file_path = decode_path(t)
|
||||
resolved_path = validate_file_path(file_path)
|
||||
|
||||
if not resolved_path.exists() or not resolved_path.is_file():
|
||||
@@ -1332,12 +1348,14 @@ async def get_media_gallery(
|
||||
'scan_date': row['face_scan_date'] if has_face_data else None
|
||||
}
|
||||
|
||||
fp = row['file_path']
|
||||
item = {
|
||||
"id": row['id'],
|
||||
"platform": row['platform'],
|
||||
"source": row['source'] or 'unknown',
|
||||
"filename": row['filename'],
|
||||
"file_path": row['file_path'],
|
||||
"file_path": fp,
|
||||
"file_token": encode_path(fp) if fp else None,
|
||||
"file_size": row['file_size'] or 0,
|
||||
"media_type": row['media_type'] or 'image',
|
||||
"download_date": row['download_date'],
|
||||
|
||||
Reference in New Issue
Block a user