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 auth import decode_token as _decode_token
# Pfade die Login erfordern (Eigentümer-Check)
_OWNER_PROTECTED = ("diary/", "health/")
# Pfade die nur Login erfordern (kein Eigentümer-Check nötig)
_AUTH_ONLY = ("walks/",)
# Pfade die Login erfordern (kein DB-Lookup — UUID in Dateiname schützt ausreichend)
_AUTH_REQUIRED = ("diary/", "health/", "walks/")
def _uid_from_request(request: _Request):
def _is_logged_in(request: _Request) -> bool:
token = request.cookies.get("by_token")
if not token:
return None
return False
try:
return int(_decode_token(token)["sub"])
_decode_token(token)
return True
except Exception:
return None
return False
def _media_response(filepath: str):
@ -382,7 +381,6 @@ def _media_response(filepath: str):
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)
if os.path.isfile(primary):
return primary
@ -398,56 +396,17 @@ async def serve_media(path: str, request: _Request):
from fastapi import HTTPException as _HE
prefix = path.split("/")[0] + "/"
filename = path.split("/", 1)[1] if "/" in path else path
# Auth-Pflicht für geschützte Pfade
if prefix in _OWNER_PROTECTED or prefix in _AUTH_ONLY:
uid = _uid_from_request(request)
if not uid:
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.")
# Sensible Pfade: Login erforderlich — UUID-basierte Dateinamen verhindern Raten
if prefix in _AUTH_REQUIRED and not _is_logged_in(request):
raise _HE(401, "Anmeldung erforderlich.")
filepath = _resolve_media_path(path)
if not filepath:
raise _HE(404, "Nicht gefunden.")
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")
async def assetlinks():