Un lien "Mot de passe oublié ?" (login.html) mène à /forgot-password : si l'adresse saisie correspond à un compte, un jeton à haute entropie (secrets.token_urlsafe, valable 1h) est généré et envoyé par email via smtplib (auth/send_email.py, aucune dépendance ajoutée) — configuré uniquement par variables d'environnement (SMTP_HOST/PORT/USER/PASSWORD/ FROM, voir .env.example et docker-compose.prod.yml), n'importe quel serveur SMTP existant convient (Mailcow compris). Seul le hash SHA-256 du jeton est stocké (auth/password_reset.py, table _password_reset_tokens) : un jeton envoyé par email reste inutilisable même en cas de fuite de la base. Le même message générique s'affiche que l'adresse corresponde à un compte ou non, pour ne jamais permettre à ce formulaire de servir à deviner quelles adresses sont déjà inscrites. Un échec d'envoi (SMTP non configuré) est journalisé côté serveur seulement, jamais révélé à l'utilisateur. /reset-password/<token> vérifie le jeton (non expiré, non déjà utilisé), applique les mêmes règles de mot de passe fort qu'à l'inscription (même schéma visuel), puis consomme le jeton et remet à zéro le compteur anti-bruteforce du compte (auth/set_password.py) — une identité prouvée par email est une voie de récupération légitime même pour un compte verrouillé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
30 lines
1.3 KiB
Python
30 lines
1.3 KiB
Python
from flask import current_app, redirect, render_template, request, session, url_for
|
|
|
|
import auth
|
|
|
|
from core.flask_app import app
|
|
|
|
|
|
@app.route("/forgot-password", methods=["GET", "POST"])
|
|
def forgot_password():
|
|
if session.get("user_id"):
|
|
return redirect(url_for("index"))
|
|
sent = False
|
|
if request.method == "POST":
|
|
email = request.form.get("email", "").strip().lower()
|
|
user = auth.get_user_by_email(email)
|
|
if user:
|
|
token = auth.create_password_reset_token(user["id"])
|
|
reset_url = url_for("reset_password", token=token, _external=True)
|
|
try:
|
|
auth.send_password_reset_email(user["email"], reset_url)
|
|
except auth.EmailNotConfiguredError:
|
|
# Jamais révélé à l'utilisateur (voir le message générique
|
|
# ci-dessous) : seul l'admin qui lit les logs serveur doit
|
|
# savoir que le SMTP n'est pas encore configuré.
|
|
current_app.logger.exception("Email de réinitialisation non envoyé : SMTP non configuré.")
|
|
# Même message que le compte existe ou non, pour ne jamais
|
|
# permettre de deviner quelles adresses sont déjà inscrites.
|
|
sent = True
|
|
return render_template("auth/forgot_password.html", sent=sent)
|