diff --git a/src/pages/mot_de_passe_oublie.py b/src/pages/mot_de_passe_oublie.py new file mode 100644 index 0000000..c139820 --- /dev/null +++ b/src/pages/mot_de_passe_oublie.py @@ -0,0 +1,69 @@ +import dash_bootstrap_components as dbc +from dash import Input, Output, callback, dcc, html, register_page +from flask_wtf.csrf import generate_csrf + +NAME = "Mot de passe oublié" + +register_page( + __name__, + path="/mot-de-passe-oublie", + title="Mot de passe oublié | decp.info", + name=NAME, + description="Réinitialiser votre mot de passe decp.info.", +) + +ERROR_MESSAGES = { + "email_send_failed": "Erreur technique lors de l'envoi de l'email. Réessayez plus tard.", +} + + +def layout( + error: str | None = None, pending: str | None = None, email: str | None = None, **_ +): + alerts = [] + if error and error in ERROR_MESSAGES: + alerts.append(dbc.Alert(ERROR_MESSAGES[error], color="danger")) + if pending == "1": + alerts.append( + dbc.Alert( + "Si un compte existe avec cet email, un lien de réinitialisation vient d'être envoyé. " + "Vérifiez votre boîte de réception.", + color="info", + ) + ) + + return dbc.Container( + className="py-4", + style={"maxWidth": "500px"}, + children=[ + html.H2("Mot de passe oublié"), + *alerts, + html.P( + "Saisissez votre adresse email. Si un compte existe, " + "vous recevrez un lien pour choisir un nouveau mot de passe." + ), + html.Form( + method="POST", + action="/auth/request-password-reset", + children=[ + dcc.Input(type="hidden", id="csrf-forgot", name="csrf_token"), + dbc.Label("Adresse email"), + dbc.Input( + type="email", + name="email", + required=True, + value=email or "", + className="mb-3", + ), + dbc.Button("Envoyer le lien", type="submit", color="primary"), + ], + ), + html.Hr(), + dcc.Link("Retour à la connexion", href="/connexion"), + ], + ) + + +@callback(Output("csrf-forgot", "value"), Input("csrf-forgot", "id")) +def _fill_csrf(_): + return generate_csrf() diff --git a/src/pages/reinitialiser_mot_de_passe.py b/src/pages/reinitialiser_mot_de_passe.py new file mode 100644 index 0000000..d85b026 --- /dev/null +++ b/src/pages/reinitialiser_mot_de_passe.py @@ -0,0 +1,77 @@ +import dash_bootstrap_components as dbc +from dash import Input, Output, callback, dcc, html, register_page +from flask_wtf.csrf import generate_csrf + +from src.auth.tokens import validate_password_reset_token + +NAME = "Réinitialiser le mot de passe" + +register_page( + __name__, + path="/reinitialiser-mot-de-passe", + title="Nouveau mot de passe | decp.info", + name=NAME, + description="Choisir un nouveau mot de passe.", +) + +ERROR_MESSAGES = { + "invalid_token": "Lien invalide ou expiré. Demandez un nouveau lien de réinitialisation.", + "password_too_short": "Le mot de passe doit faire au moins 8 caractères.", + "password_mismatch": "Les mots de passe ne correspondent pas.", +} + + +def layout(token: str | None = None, error: str | None = None, **_): + if not token or validate_password_reset_token(token) is None: + return dbc.Container( + className="py-4", + style={"maxWidth": "500px"}, + children=[ + html.H2("Lien invalide"), + dbc.Alert(ERROR_MESSAGES["invalid_token"], color="danger"), + dcc.Link("Demander un nouveau lien", href="/mot-de-passe-oublie"), + ], + ) + + alerts = [] + if error and error in ERROR_MESSAGES: + alerts.append(dbc.Alert(ERROR_MESSAGES[error], color="danger")) + + return dbc.Container( + className="py-4", + style={"maxWidth": "500px"}, + children=[ + html.H2("Choisir un nouveau mot de passe"), + *alerts, + html.Form( + method="POST", + action="/auth/reset-password", + children=[ + dcc.Input(type="hidden", id="csrf-reset", name="csrf_token"), + dcc.Input(type="hidden", name="token", value=token), + dbc.Label("Nouveau mot de passe (8 caractères minimum)"), + dbc.Input( + type="password", + name="password", + required=True, + minLength=8, + className="mb-3", + ), + dbc.Label("Confirmer le nouveau mot de passe"), + dbc.Input( + type="password", + name="password_confirm", + required=True, + minLength=8, + className="mb-3", + ), + dbc.Button("Valider", type="submit", color="primary"), + ], + ), + ], + ) + + +@callback(Output("csrf-reset", "value"), Input("csrf-reset", "id")) +def _fill_csrf(_): + return generate_csrf()