feat(api): Core-API mit Authentifizierung, CRUD und Monatsreport
- Anmeldung über Argon2id und JWT in httpOnly-Cookies, Refresh mit echter Rotation über die neue Tabelle refresh_token - AuthProvider-Protokoll als Vorbereitung für OIDC, Administrator-Anlage beim Erststart mit erzwungenem Passwortwechsel - CRUD für Konten, Kategorien (zweistufiger Baum), Firmen, Recurrences, Preisversionen, Buchungen, Budgets, Vorlagen und Sparziele - Fälligkeiten mit Overlay-Logik: abrufen, bestätigen, auslassen, zurücksetzen - Kontosalden zum Stichtag, Monatsübersicht mit Plan-Ist-Vergleich - SECRET_KEY jetzt mindestens 32 Zeichen; Platzhalter in Produktion abgelehnt - 61 neue Integrationstests, insgesamt 148 grün Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""Setzen und Löschen der Authentifizierungs-Cookies."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import Response
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
ACCESS_COOKIE = "moneyfy_access"
|
||||
REFRESH_COOKIE = "moneyfy_refresh"
|
||||
|
||||
# Der Refresh-Cookie wird nur an die Endpunkte geschickt, die ihn wirklich brauchen.
|
||||
REFRESH_COOKIE_PATH = "/api/auth"
|
||||
|
||||
|
||||
def set_auth_cookies(
|
||||
response: Response,
|
||||
access_token: str,
|
||||
access_expires_at: datetime,
|
||||
refresh_token: str,
|
||||
refresh_expires_at: datetime,
|
||||
) -> None:
|
||||
"""Legt beide Cookies als httpOnly/SameSite=Lax ab."""
|
||||
now = datetime.now(UTC)
|
||||
response.set_cookie(
|
||||
ACCESS_COOKIE,
|
||||
access_token,
|
||||
max_age=max(int((access_expires_at - now).total_seconds()), 0),
|
||||
httponly=True,
|
||||
secure=settings.cookie_secure,
|
||||
samesite="lax",
|
||||
path="/",
|
||||
domain=settings.cookie_domain,
|
||||
)
|
||||
response.set_cookie(
|
||||
REFRESH_COOKIE,
|
||||
refresh_token,
|
||||
max_age=max(int((refresh_expires_at - now).total_seconds()), 0),
|
||||
httponly=True,
|
||||
secure=settings.cookie_secure,
|
||||
samesite="lax",
|
||||
path=REFRESH_COOKIE_PATH,
|
||||
domain=settings.cookie_domain,
|
||||
)
|
||||
|
||||
|
||||
def clear_auth_cookies(response: Response) -> None:
|
||||
"""Entfernt beide Cookies – muss dieselben Attribute wie beim Setzen verwenden."""
|
||||
response.delete_cookie(
|
||||
ACCESS_COOKIE,
|
||||
path="/",
|
||||
domain=settings.cookie_domain,
|
||||
httponly=True,
|
||||
secure=settings.cookie_secure,
|
||||
samesite="lax",
|
||||
)
|
||||
response.delete_cookie(
|
||||
REFRESH_COOKIE,
|
||||
path=REFRESH_COOKIE_PATH,
|
||||
domain=settings.cookie_domain,
|
||||
httponly=True,
|
||||
secure=settings.cookie_secure,
|
||||
samesite="lax",
|
||||
)
|
||||
Reference in New Issue
Block a user