diff --git a/VERSION b/VERSION index e9fa9e7..03a524d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1114 \ No newline at end of file +1115 \ No newline at end of file diff --git a/backend/auth.py b/backend/auth.py index 9cb25c6..1b5f126 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -212,6 +212,49 @@ def require_admin(user=Depends(get_current_user)): return user +def require_moderator(user=Depends(get_current_user)): + """Dependency: Admin oder Moderator. Konsequente Nutzung statt + Inline-`if user['rolle'] not in (...):` in den Routen.""" + if user["rolle"] not in ("admin", "moderator") and not user.get("is_moderator"): + raise HTTPException(status.HTTP_403_FORBIDDEN, "Moderator-Zugriff erforderlich.") + return user + + +def require_breeder(user=Depends(get_current_user)): + """Dependency: Admin oder Züchter (breeder/breeder_test).""" + if user["rolle"] == "admin": + return user + if user.get("subscription_tier") in ("breeder", "breeder_test"): + return user + raise HTTPException(status.HTTP_403_FORBIDDEN, "Züchter-Zugriff erforderlich.") + + +# ------------------------------------------------------------------ +# Owner-Checks — zentral, statt 54x inline `if row['user_id'] != user['id']: 403` +# ------------------------------------------------------------------ +def require_owner(row, user: dict, owner_field: str = "user_id", + not_found_msg: str = "Nicht gefunden", + forbidden_msg: str = "Kein Zugriff"): + """Wirft 404 wenn row None/falsy ist, 403 wenn User nicht Besitzer. + Returns row für chainability: + dog = require_owner(conn.execute(...).fetchone(), user, 'user_id', 'Hund nicht gefunden') + """ + if not row: + raise HTTPException(status.HTTP_404_NOT_FOUND, not_found_msg) + if row[owner_field] != user["id"]: + raise HTTPException(status.HTTP_403_FORBIDDEN, forbidden_msg) + return row + + +def is_owner_or_admin(row, user: dict, owner_field: str = "user_id") -> bool: + """True wenn User Owner ist oder Admin/Moderator.""" + if not row: + return False + if user["rolle"] in ("admin", "moderator") or user.get("is_moderator"): + return True + return row[owner_field] == user["id"] + + def has_pro_access(user: dict) -> bool: """True wenn User Pro-Features nutzen darf.""" if not user: diff --git a/backend/routes/places.py b/backend/routes/places.py index bb1f86b..3dfc0a8 100644 --- a/backend/routes/places.py +++ b/backend/routes/places.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional from database import db -from auth import get_current_user +from auth import get_current_user, require_owner from math_utils import haversine_m router = APIRouter() @@ -121,11 +121,10 @@ async def get_place(place_id: int): @router.patch("/{place_id}") async def update_place(place_id: int, data: PlaceUpdate, user=Depends(get_current_user)): with db() as conn: - row = conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone() - if not row: - raise HTTPException(404, "Ort nicht gefunden.") - if row['user_id'] != user['id']: - raise HTTPException(403, "Nicht berechtigt.") + row = require_owner( + conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone(), + user, not_found_msg="Ort nicht gefunden.", forbidden_msg="Nicht berechtigt." + ) updates = data.model_dump(exclude_none=True) if not updates: @@ -150,9 +149,8 @@ async def update_place(place_id: int, data: PlaceUpdate, user=Depends(get_curren @router.delete("/{place_id}", status_code=204) async def delete_place(place_id: int, user=Depends(get_current_user)): with db() as conn: - row = conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone() - if not row: - raise HTTPException(404, "Ort nicht gefunden.") - if row['user_id'] != user['id']: - raise HTTPException(403, "Nicht berechtigt.") + require_owner( + conn.execute("SELECT * FROM places WHERE id = ?", (place_id,)).fetchone(), + user, not_found_msg="Ort nicht gefunden.", forbidden_msg="Nicht berechtigt." + ) conn.execute("DELETE FROM places WHERE id = ?", (place_id,)) diff --git a/backend/static/index.html b/backend/static/index.html index eee2b4e..09855ab 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@