diff --git a/backend/main.py b/backend/main.py index 55fc033..45826e0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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(): diff --git a/backend/static/index.html b/backend/static/index.html index 23a9e72..6413439 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -101,9 +101,9 @@ - - - + + +
@@ -583,10 +583,10 @@ - - - - + + + + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 9b927e3..95691e8 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ 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 IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/sw.js b/backend/static/sw.js index 78a8a1f..c6d2171 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v882'; +const CACHE_VERSION = 'by-v883'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache