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:
@@ -20,6 +20,7 @@ from slowapi.util import get_remote_address
|
||||
|
||||
from ..core.dependencies import get_current_user, get_app_state
|
||||
from ..core.exceptions import handle_exceptions, NotFoundError, ValidationError
|
||||
from ..core.path_tokens import encode_path
|
||||
from ..core.responses import message_response, id_response, count_response, offset_paginated
|
||||
from modules.discovery_system import get_discovery_system
|
||||
from modules.universal_logger import get_logger
|
||||
@@ -381,8 +382,10 @@ async def get_smart_folders_stats(
|
||||
|
||||
previews = []
|
||||
for row in cursor.fetchall():
|
||||
fp = row['file_path']
|
||||
previews.append({
|
||||
'file_path': row['file_path'],
|
||||
'file_path': fp,
|
||||
'file_token': encode_path(fp) if fp else None,
|
||||
'content_type': row['content_type']
|
||||
})
|
||||
|
||||
@@ -758,9 +761,11 @@ async def get_recent_activity(
|
||||
''', (limit,))
|
||||
|
||||
for row in cursor.fetchall():
|
||||
fp = row['file_path']
|
||||
activity['recent_downloads'].append({
|
||||
'id': row['id'],
|
||||
'file_path': row['file_path'],
|
||||
'file_path': fp,
|
||||
'file_token': encode_path(fp) if fp else None,
|
||||
'filename': row['filename'],
|
||||
'platform': row['platform'],
|
||||
'source': row['source'],
|
||||
@@ -788,9 +793,11 @@ async def get_recent_activity(
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
fp = row['recycle_path']
|
||||
activity['recent_deleted'].append({
|
||||
'id': row['id'],
|
||||
'file_path': row['recycle_path'],
|
||||
'file_path': fp,
|
||||
'file_token': encode_path(fp) if fp else None,
|
||||
'original_path': row['original_path'],
|
||||
'filename': row['original_filename'],
|
||||
'platform': metadata.get('platform', 'unknown'),
|
||||
@@ -814,9 +821,11 @@ async def get_recent_activity(
|
||||
''', (limit,))
|
||||
|
||||
for row in cursor.fetchall():
|
||||
fp = row['file_path']
|
||||
activity['recent_moved_to_review'].append({
|
||||
'id': row['id'],
|
||||
'file_path': row['file_path'],
|
||||
'file_path': fp,
|
||||
'file_token': encode_path(fp) if fp else None,
|
||||
'filename': row['filename'],
|
||||
'platform': row['platform'],
|
||||
'source': row['source'],
|
||||
|
||||
Reference in New Issue
Block a user