Remplace la regle de longueur seule (12 caracteres) par une exigence de composition (8 caracteres minimum, majuscule, minuscule, chiffre, caractere special), non documentee dans les exigences officielles du projet, par une regle explicite partagee entre le backend (validateur Pydantic) et le frontend. Ajoute un flux "mot de passe oublie" en libre-service, absent jusqu'ici : jeton a usage unique hache en base (meme principe que les refresh tokens), expirant a 15 minutes, envoye par email via un service SMTP (aiosmtplib, Mailpit en dev), avec limitation de debit dediee et reponse generique pour eviter l'enumeration des comptes. Closes #87
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
# Piège : l'URL de réinitialisation porte le jeton en clair. Ne jamais la journaliser :
|
|
# `send_password_reset_email()` ne logue que le destinataire, jamais `reset_url`.
|
|
|
|
from dataclasses import dataclass
|
|
from email.message import EmailMessage
|
|
|
|
import aiosmtplib
|
|
|
|
from app.core.logging import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SmtpConfig:
|
|
host: str
|
|
port: int
|
|
username: str | None
|
|
password: str | None
|
|
use_tls: bool
|
|
from_address: str
|
|
|
|
|
|
class Mailer:
|
|
def __init__(self, config: SmtpConfig) -> None:
|
|
self._config = config
|
|
|
|
async def send_password_reset_email(self, *, to: str, reset_url: str) -> None:
|
|
message = EmailMessage()
|
|
message["From"] = self._config.from_address
|
|
message["To"] = to
|
|
message["Subject"] = "Réinitialisation de votre mot de passe EnerVision"
|
|
message.set_content(
|
|
"Une réinitialisation de mot de passe a été demandée pour ce compte.\n\n"
|
|
f"Ouvrez ce lien dans les 15 minutes pour choisir un nouveau mot de passe : "
|
|
f"{reset_url}\n\n"
|
|
"Si vous n'êtes pas à l'origine de cette demande, ignorez cet email."
|
|
)
|
|
|
|
_, message_recu = await aiosmtplib.send(
|
|
message,
|
|
hostname=self._config.host,
|
|
port=self._config.port,
|
|
username=self._config.username,
|
|
password=self._config.password,
|
|
use_tls=self._config.use_tls,
|
|
)
|
|
logger.info("mailer.password_reset_sent to=%s smtp_response=%s", to, message_recu)
|