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>
31 lines
1.2 KiB
HTML
31 lines
1.2 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Connexion — Forge Engine{% endblock %}
|
|
{% block content %}
|
|
<section class="section">
|
|
<div class="container" style="max-width:420px;">
|
|
<h1 class="title">Connexion</h1>
|
|
|
|
{% if error %}
|
|
<div class="notification is-danger">{{ error }}</div>
|
|
{% endif %}
|
|
|
|
<form method="post" action="{{ url_for('login') }}" autocomplete="off">
|
|
<input type="hidden" name="next" value="{{ next_url }}">
|
|
<div class="field">
|
|
<label class="label">Adresse email</label>
|
|
<div class="control"><input class="input" type="email" name="email" value="{{ email }}" required autofocus></div>
|
|
</div>
|
|
<div class="field">
|
|
<label class="label">Mot de passe</label>
|
|
<div class="control"><input class="input" type="password" name="password" required autocomplete="current-password"></div>
|
|
</div>
|
|
<div class="field mt-4">
|
|
<button type="submit" class="button primary is-fullwidth">Continuer</button>
|
|
</div>
|
|
</form>
|
|
<p class="hint mt-3"><a href="{{ url_for('forgot_password') }}">Mot de passe oublié ?</a></p>
|
|
<p class="hint mt-1">Pas encore de compte ? <a href="{{ url_for('register') }}">S'inscrire</a></p>
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|