"""Gemeinsame Passwort-Link-Erzeugung (Sessions) + Mailtext — wie /auth/forgot-password.""" from __future__ import annotations import os import secrets from datetime import datetime, timedelta from typing import Any RESET_TOKEN_PREFIX = "reset_" def public_reset_link(token: str) -> str: base = (os.getenv("APP_URL") or "https://shinkan.jinkendo.de").rstrip("/") return f"{base}/reset-password?token={token}" def revoke_pending_password_resets_for_profile(cur: Any, profile_id: int) -> None: """Entfernt alte Reset-Sessions eines Profils, damit nur der neueste Link aktiv ist.""" cur.execute( """ DELETE FROM sessions WHERE profile_id = %s AND token LIKE %s """, (profile_id, f"{RESET_TOKEN_PREFIX}%"), ) def insert_password_reset_session(cur: Any, profile_id: int, *, hours_valid: int = 1) -> str: """ Legt reset_-Session an. Gibt den Klartext-Token zurück (wie bei forgot-password). """ raw = secrets.token_urlsafe(32) expires = datetime.now() + timedelta(hours=hours_valid) cur.execute( """ INSERT INTO sessions (token, profile_id, expires_at, created_at) VALUES (%s, %s, %s, CURRENT_TIMESTAMP) """, (f"{RESET_TOKEN_PREFIX}{raw}", profile_id, expires.isoformat()), ) return raw def password_reset_email_body(*, recipient_name: str | None, token: str, intro: str) -> str: name = (recipient_name or "").strip() or "Kollege/Kollegin" link = public_reset_link(token) return f"""Hallo {name}, {intro} Neues Passwort setzen: {link} Der Link ist 1 Stunde gültig. Erst wenn du ihn nutzt und ein neues Passwort wählst, wird dein bestehendes Passwort ersetzt — bis dahin kannst du dich wie gewohnt anmelden. Falls du diese Anfrage nicht erwartest, ignoriere diese E-Mail; dein Zugang bleibt unverändert. Dein Shinkan Jinkendo Team """ def issue_password_reset_via_email(cur: Any, send_email_fn, *, profile_id: int, email: str, name: str | None, intro: str) -> bool: """Session anlegen und Mail schicken (send_email_fn wie routers.auth.send_email).""" revoke_pending_password_resets_for_profile(cur, profile_id) raw_token = insert_password_reset_session(cur, profile_id) body = password_reset_email_body(recipient_name=name, token=raw_token, intro=intro) return send_email_fn(email, "Passwort-Link – Shinkan Jinkendo", body)