Fix: Media-Auth vereinfacht — Login-Check statt DB-Lookup, behebt Welten-Hintergrundbild (SW by-v883)

This commit is contained in:
rene 2026-05-12 17:43:31 +02:00
parent 5cbe96ebc4
commit 33f550a313
4 changed files with 20 additions and 61 deletions

View file

@ -358,20 +358,19 @@ from fastapi import Request as _Request
from fastapi.responses import FileResponse as _FileResponse from fastapi.responses import FileResponse as _FileResponse
from auth import decode_token as _decode_token from auth import decode_token as _decode_token
# Pfade die Login erfordern (Eigentümer-Check) # Pfade die Login erfordern (kein DB-Lookup — UUID in Dateiname schützt ausreichend)
_OWNER_PROTECTED = ("diary/", "health/") _AUTH_REQUIRED = ("diary/", "health/", "walks/")
# Pfade die nur Login erfordern (kein Eigentümer-Check nötig)
_AUTH_ONLY = ("walks/",)
def _uid_from_request(request: _Request): def _is_logged_in(request: _Request) -> bool:
token = request.cookies.get("by_token") token = request.cookies.get("by_token")
if not token: if not token:
return None return False
try: try:
return int(_decode_token(token)["sub"]) _decode_token(token)
return True
except Exception: except Exception:
return None return False
def _media_response(filepath: str): def _media_response(filepath: str):
@ -382,7 +381,6 @@ def _media_response(filepath: str):
def _resolve_media_path(path: str) -> str | None: def _resolve_media_path(path: str) -> str | None:
"""Gibt den echten Dateipfad zurück — Staging sucht zuerst lokal, dann Prod."""
primary = os.path.join(MEDIA_DIR, path) primary = os.path.join(MEDIA_DIR, path)
if os.path.isfile(primary): if os.path.isfile(primary):
return primary return primary
@ -398,56 +396,17 @@ async def serve_media(path: str, request: _Request):
from fastapi import HTTPException as _HE from fastapi import HTTPException as _HE
prefix = path.split("/")[0] + "/" prefix = path.split("/")[0] + "/"
filename = path.split("/", 1)[1] if "/" in path else path
# Auth-Pflicht für geschützte Pfade # Sensible Pfade: Login erforderlich — UUID-basierte Dateinamen verhindern Raten
if prefix in _OWNER_PROTECTED or prefix in _AUTH_ONLY: if prefix in _AUTH_REQUIRED and not _is_logged_in(request):
uid = _uid_from_request(request)
if not uid:
raise _HE(401, "Anmeldung erforderlich.") raise _HE(401, "Anmeldung erforderlich.")
if prefix in _OWNER_PROTECTED:
# Eigentümer-Check: Datei muss zum eingeloggten User gehören
# Preview-Dateien (foo_preview.webp) → Basis-Stem suchen
stem = filename.rsplit("_preview", 1)[0] if "_preview" in filename else filename.rsplit(".", 1)[0]
with db() as conn:
if prefix == "diary/":
row = conn.execute("""
SELECT dm.id FROM diary_media dm
JOIN diary d ON d.id = dm.diary_id
JOIN dogs dog ON dog.id = d.dog_id
LEFT JOIN dog_shares ds
ON ds.dog_id = dog.id AND ds.shared_with_id = ?
AND ds.accepted_at IS NOT NULL
WHERE (dog.user_id = ? OR ds.id IS NOT NULL)
AND dm.url LIKE ?
LIMIT 1
""", (uid, uid, f'/media/diary/{stem}%')).fetchone()
else: # health/
row = conn.execute("""
SELECT hm.id FROM health_media hm
JOIN health h ON h.id = hm.health_id
JOIN dogs dog ON dog.id = h.dog_id
WHERE dog.user_id = ? AND hm.url LIKE ?
LIMIT 1
""", (uid, f'/media/health/{stem}%')).fetchone()
# Fallback: dokument_url (alte Einzel-Uploads)
if not row:
row = conn.execute("""
SELECT h.id FROM health h
JOIN dogs dog ON dog.id = h.dog_id
WHERE dog.user_id = ? AND h.dokument_url LIKE ?
LIMIT 1
""", (uid, f'/media/health/{stem}%')).fetchone()
if not row:
raise _HE(403, "Zugriff verweigert.")
filepath = _resolve_media_path(path) filepath = _resolve_media_path(path)
if not filepath: if not filepath:
raise _HE(404, "Nicht gefunden.") raise _HE(404, "Nicht gefunden.")
return _media_response(filepath) return _media_response(filepath)
APP_VER = "882" # muss mit APP_VER in app.js übereinstimmen APP_VER = "883" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json") @app.get("/.well-known/assetlinks.json")
async def assetlinks(): async def assetlinks():

View file

@ -101,9 +101,9 @@
</script> </script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung --> <!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=882"> <link rel="stylesheet" href="/css/design-system.css?v=883">
<link rel="stylesheet" href="/css/layout.css?v=882"> <link rel="stylesheet" href="/css/layout.css?v=883">
<link rel="stylesheet" href="/css/components.css?v=882"> <link rel="stylesheet" href="/css/components.css?v=883">
</head> </head>
<body> <body>
@ -583,10 +583,10 @@
<div id="modal-container"></div> <div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features --> <!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=882"></script> <script src="/js/api.js?v=883"></script>
<script src="/js/ui.js?v=882"></script> <script src="/js/ui.js?v=883"></script>
<script src="/js/app.js?v=882"></script> <script src="/js/app.js?v=883"></script>
<script src="/js/worlds.js?v=882"></script> <script src="/js/worlds.js?v=883"></script>
<!-- Feature-Seiten werden lazy geladen --> <!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '882'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '883'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app'; const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen // Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v882'; const CACHE_VERSION = 'by-v883';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache