Auth: Verwaltung auf Magic-Link umstellen (Passwort-Login entfernt)
Backend: - src/routers/auth.py NEU: POST /api/auth/magic-link + POST /api/auth/verify - src/auth.py: verify_password/hash_password raus, generate_magic_token rein - src/main.py: alter Login-Endpoint + Brute-Force-Logik raus, neuer auth-Router eingebunden - src/config.py: ALLOWED_EMAIL + PORTAL_MAGIC_LINK_* hinzu - src/models.py: LoginRequest raus, MagicLinkRequest etc. rein - src/email_utils/templates.py: portal_magic_link_email Template Frontend: - src/static/index.html: Email-Eingabe statt Passwort, Token-Verify-Logik fuer ?token= aus URL Datenbank-Migration (migrations/2026-05-09_portal_magic_link.py): - portal_magic_links + portal_magic_link_attempts neu - portal_login_attempts gedroppt - portal_admins.email Spalte hinzu, password_hash geleert Whitelist info@aegis-sight.de, Rate-Limit 5/15 Min, Anti-Enumeration generische Antwort.
Dieser Commit ist enthalten in:
28
src/auth.py
28
src/auth.py
@@ -1,7 +1,11 @@
|
||||
"""Passwort-basierte Authentifizierung fuer das Verwaltungsportal."""
|
||||
"""Magic-Link-Authentifizierung für das Verwaltungsportal.
|
||||
|
||||
JWT für Session, Magic-Link an info@aegis-sight.de zur Anmeldung.
|
||||
Passwort-Login wurde mit Migration 2026-05-09 entfernt.
|
||||
"""
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from jose import jwt, JWTError
|
||||
import bcrypt as _bcrypt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from config import JWT_SECRET, JWT_ALGORITHM, JWT_EXPIRE_HOURS
|
||||
@@ -12,20 +16,19 @@ JWT_ISSUER = "aegissight-portal"
|
||||
JWT_AUDIENCE = "aegissight-portal"
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return _bcrypt.hashpw(password.encode("utf-8"), _bcrypt.gensalt()).decode("utf-8")
|
||||
def generate_magic_token() -> str:
|
||||
"""Erzeugt einen URL-sicheren Token (43 Zeichen) für den Magic-Link."""
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
def verify_password(password: str, password_hash: str) -> bool:
|
||||
return _bcrypt.checkpw(password.encode("utf-8"), password_hash.encode("utf-8"))
|
||||
|
||||
|
||||
def create_token(admin_id: int, username: str) -> str:
|
||||
def create_token(admin_id: int, email: str, username: str = "") -> str:
|
||||
"""JWT-Session-Token nach erfolgreichem Magic-Link-Verify."""
|
||||
now = datetime.now(timezone.utc)
|
||||
expire = now + timedelta(hours=JWT_EXPIRE_HOURS)
|
||||
payload = {
|
||||
"sub": str(admin_id),
|
||||
"username": username,
|
||||
"email": email,
|
||||
"username": username or email.split("@")[0],
|
||||
"role": "portal_admin",
|
||||
"iss": JWT_ISSUER,
|
||||
"aud": JWT_AUDIENCE,
|
||||
@@ -47,7 +50,7 @@ def decode_token(token: str) -> dict:
|
||||
except JWTError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token ungueltig oder abgelaufen",
|
||||
detail="Token ungültig oder abgelaufen",
|
||||
)
|
||||
|
||||
|
||||
@@ -57,5 +60,6 @@ async def get_current_admin(
|
||||
payload = decode_token(credentials.credentials)
|
||||
return {
|
||||
"id": int(payload["sub"]),
|
||||
"username": payload["username"],
|
||||
"email": payload.get("email", ""),
|
||||
"username": payload.get("username", ""),
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren