diff --git a/src/auth.py b/src/auth.py index 233f08b..d79148e 100644 --- a/src/auth.py +++ b/src/auth.py @@ -1,6 +1,5 @@ """JWT-Authentifizierung mit Magic-Link-Support und Multi-Tenancy.""" import secrets -import string from datetime import datetime, timedelta from jose import jwt, JWTError from fastapi import Depends, HTTPException, status @@ -77,7 +76,3 @@ def generate_magic_token() -> str: """Generiert einen 64-Zeichen URL-safe Token.""" return secrets.token_urlsafe(48) - -def generate_magic_code() -> str: - """Generiert einen 6-stelligen numerischen Code.""" - return ''.join(secrets.choice(string.digits) for _ in range(6)) diff --git a/src/email_utils/rate_limiter.py b/src/email_utils/rate_limiter.py index 57c596c..6381bf3 100644 --- a/src/email_utils/rate_limiter.py +++ b/src/email_utils/rate_limiter.py @@ -1,4 +1,4 @@ -"""In-Memory Rate-Limiting fuer Magic-Link-Anfragen und Code-Verifizierung.""" +"""In-Memory Rate-Limiting fuer Magic-Link-Anfragen.""" import time from collections import defaultdict @@ -51,52 +51,5 @@ class RateLimiter: self._ip_requests[ip].append(now) -class VerifyCodeLimiter: - """Rate-Limiter fuer Code-Verifizierung (Brute-Force-Schutz). - - Zaehlt Fehlversuche pro E-Mail und pro IP. - Nach max_attempts wird gesperrt bis das Zeitfenster ablaeuft. - """ - - def __init__( - self, - max_attempts_per_email: int = 5, - max_attempts_per_ip: int = 15, - window_seconds: int = 600, # 10 Minuten (= Magic-Link-Ablaufzeit) - ): - self.max_per_email = max_attempts_per_email - self.max_per_ip = max_attempts_per_ip - self.window = window_seconds - self._email_failures: dict[str, list[float]] = defaultdict(list) - self._ip_failures: dict[str, list[float]] = defaultdict(list) - - def _clean(self, entries: list[float]) -> list[float]: - cutoff = time.time() - self.window - return [t for t in entries if t > cutoff] - - def check(self, email: str, ip: str) -> tuple[bool, str]: - """Prueft ob ein Verifizierungsversuch erlaubt ist.""" - self._email_failures[email] = self._clean(self._email_failures[email]) - if len(self._email_failures[email]) >= self.max_per_email: - return False, "Zu viele Fehlversuche. Bitte neuen Code anfordern." - - self._ip_failures[ip] = self._clean(self._ip_failures[ip]) - if len(self._ip_failures[ip]) >= self.max_per_ip: - return False, "Zu viele Fehlversuche von dieser IP-Adresse." - - return True, "" - - def record_failure(self, email: str, ip: str): - """Zeichnet einen fehlgeschlagenen Versuch auf.""" - now = time.time() - self._email_failures[email].append(now) - self._ip_failures[ip].append(now) - - def clear(self, email: str): - """Loescht Zaehler nach erfolgreichem Login.""" - self._email_failures.pop(email, None) - - -# Singleton-Instanzen +# Singleton-Instanz magic_link_limiter = RateLimiter() -verify_code_limiter = VerifyCodeLimiter() diff --git a/src/email_utils/templates.py b/src/email_utils/templates.py index 83f2b7d..adcd0e1 100644 --- a/src/email_utils/templates.py +++ b/src/email_utils/templates.py @@ -1,8 +1,8 @@ """HTML-E-Mail-Vorlagen fuer Magic Links, Einladungen und Benachrichtigungen.""" -def magic_link_login_email(username: str, code: str, link: str) -> tuple[str, str]: - """Erzeugt Login-E-Mail mit Magic Link und Code. +def magic_link_login_email(username: str, link: str) -> tuple[str, str]: + """Erzeugt Login-E-Mail mit Magic Link. Returns: (subject, html_body) @@ -17,16 +17,15 @@ def magic_link_login_email(username: str, code: str, link: str) -> tuple[str, st
Hallo {username},
-Klicken Sie auf den Link oder geben Sie den Code ein, um sich anzumelden:
- -Klicken Sie auf den Button, um sich anzumelden:
+Oder kopieren Sie diesen Link in Ihren Browser:
+{link}
+Dieser Link ist 10 Minuten gueltig. Falls Sie diese Anmeldung nicht angefordert haben, ignorieren Sie diese E-Mail.