Passwort vergessen per Adresse (ADR-0041, 24.10.2026)
Je eingerichtetem Konto mit der Adresse ein Link, auch auf mehreren Geräten; Begrenzung je Konto mit dem Kiosk-Weg geteilt, Konten an der Grenze still übergangen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bcfe411290
commit
da44fd1c6a
1 file changed
+67
-17
@@ -6334,30 +6334,36 @@ def vergessen_anfordern(device_id: str, token: str):
|
||||
Mail, die kundenplattform verschickt.
|
||||
"""
|
||||
|
||||
jetzt = datetime.now(timezone.utc)
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
row = _vergessen_konto(cur, device_id, token)
|
||||
if row is None:
|
||||
return None, "Auf diesem Gerät ist kein persönliches Konto eingerichtet."
|
||||
pid, anmeldename, email, mails, hostname, org_name = row
|
||||
cur.execute("SELECT 1 FROM personalisierungen WHERE id = %s FOR UPDATE", (pid,))
|
||||
return _vergessen_link(cur, row)
|
||||
|
||||
letzte = [m for m in (mails or []) if m > jetzt - timedelta(days=1)]
|
||||
if letzte and max(letzte) > jetzt - VERGESSEN_ABSTAND:
|
||||
return None, "Es wurde gerade erst ein Link verschickt. Bitte im Postfach (auch im Spam-Ordner) nachsehen oder in 10 Minuten erneut versuchen."
|
||||
if len(letzte) >= VERGESSEN_JE_TAG:
|
||||
return None, "Heute wurden schon mehrere Links verschickt. Bitte morgen erneut versuchen oder an die Verwaltung wenden."
|
||||
|
||||
link_token = secrets.token_urlsafe(32)
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE personalisierungen
|
||||
SET vergessen_token_hash = %s, vergessen_bis = %s, vergessen_mails = %s
|
||||
WHERE id = %s
|
||||
""",
|
||||
(personalisierung_token_hash(link_token), jetzt + VERGESSEN_LINK_GUELTIG, letzte + [jetzt], pid),
|
||||
)
|
||||
def _vergessen_link(cur, row):
|
||||
"""Neuer Link für ein Konto, sofern die Begrenzung es zulässt. Gibt (Ergebnis, Fehler) zurück."""
|
||||
|
||||
jetzt = datetime.now(timezone.utc)
|
||||
pid, anmeldename, email, mails, hostname, org_name = row
|
||||
cur.execute("SELECT 1 FROM personalisierungen WHERE id = %s FOR UPDATE", (pid,))
|
||||
|
||||
letzte = [m for m in (mails or []) if m > jetzt - timedelta(days=1)]
|
||||
if letzte and max(letzte) > jetzt - VERGESSEN_ABSTAND:
|
||||
return None, "Es wurde gerade erst ein Link verschickt. Bitte im Postfach (auch im Spam-Ordner) nachsehen oder in 10 Minuten erneut versuchen."
|
||||
if len(letzte) >= VERGESSEN_JE_TAG:
|
||||
return None, "Heute wurden schon mehrere Links verschickt. Bitte morgen erneut versuchen oder an die Verwaltung wenden."
|
||||
|
||||
link_token = secrets.token_urlsafe(32)
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE personalisierungen
|
||||
SET vergessen_token_hash = %s, vergessen_bis = %s, vergessen_mails = %s
|
||||
WHERE id = %s
|
||||
""",
|
||||
(personalisierung_token_hash(link_token), jetzt + VERGESSEN_LINK_GUELTIG, letzte + [jetzt], pid),
|
||||
)
|
||||
|
||||
return {
|
||||
"personalisierung_id": str(pid),
|
||||
@@ -6370,6 +6376,38 @@ def vergessen_anfordern(device_id: str, token: str):
|
||||
}, None
|
||||
|
||||
|
||||
def vergessen_per_adresse(email: str) -> list[dict]:
|
||||
"""
|
||||
"Passwort vergessen?" auf der öffentlichen Seite (ADR-0041, Ergänzung
|
||||
24.10.2026): je eingerichtetem Konto mit dieser Adresse ein Link, auch auf
|
||||
mehreren Geräten. Konten an der Begrenzung werden still übergangen; die
|
||||
Seite antwortet immer gleich und verrät nicht, ob es die Adresse gibt.
|
||||
"""
|
||||
|
||||
email = (email or "").strip()
|
||||
if not EMAIL_MUSTER.match(email):
|
||||
return []
|
||||
links = []
|
||||
with get_database_connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT p.id, p.anmeldename, p.email, p.vergessen_mails, d.hostname, o.name
|
||||
FROM personalisierungen p
|
||||
JOIN devices d ON d.id = p.device_id
|
||||
JOIN organizations o ON o.id = p.organization_id
|
||||
WHERE lower(p.email) = lower(%s) AND p.status = 'angelegt' AND NOT p.entfernen AND NOT p.gesperrt
|
||||
ORDER BY p.angelegt_am
|
||||
""",
|
||||
(email,),
|
||||
)
|
||||
for row in cur.fetchall():
|
||||
ergebnis, _fehler = _vergessen_link(cur, row)
|
||||
if ergebnis:
|
||||
links.append(ergebnis)
|
||||
return links
|
||||
|
||||
|
||||
def _neues_passwort_konto(cur, token: str):
|
||||
cur.execute(
|
||||
"""
|
||||
@@ -7071,6 +7109,18 @@ def post_vergessen_anfordern(payload: VergessenRequest, authorization: str | Non
|
||||
return {"success": True, **ergebnis}
|
||||
|
||||
|
||||
class VergessenPerAdresseRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
@app.post("/api/v1/personalisierung/vergessen/per-adresse")
|
||||
def post_vergessen_per_adresse(payload: VergessenPerAdresseRequest, authorization: str | None = Header(default=None)):
|
||||
if not require_service_token(authorization):
|
||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||
|
||||
return {"success": True, "links": vergessen_per_adresse(payload.email)}
|
||||
|
||||
|
||||
@app.post("/api/v1/personalisierung/neues-passwort")
|
||||
def post_neues_passwort(payload: NeuesPasswortRequest, authorization: str | None = Header(default=None)):
|
||||
"""Ohne passwort_hash nur prüfen (Seite hinter dem Link), mit Hash setzen."""
|
||||
|
||||
Reference in new issue
Block a user