Ajoute une page de profil (modifier ses infos, mot de passe, supprimer son compte)

Le nom affiché dans la barre de navigation (base.html) devient un lien
vers /profile : informations du compte (email en lecture seule, nom/
prénom modifiables), changement de mot de passe (mot de passe actuel
requis + mêmes règles de force qu'à l'inscription/la réinitialisation),
et une section "danger" pour demander la suppression du compte.

La suppression exige le mot de passe actuel ET la saisie exacte de
"SUPPRIMER" (deux confirmations distinctes pour une action irréversible)
— routes/auth/profile.py. Pour un compte "user" (limité à un seul projet,
créé automatiquement et impossible à supprimer autrement, voir
core/auth_guard.py), supprimer le compte supprime aussi son unique projet
sur le disque, faute de quoi il resterait orphelin sans plus aucun
propriétaire. Un compte "admin" peut posséder plusieurs jeux qui ne lui
sont pas dédiés de la même façon : ses projets ne sont jamais touchés.
L'unique compte administrateur ne peut pas être supprimé (auth/
count_admins.py) — le supprimer bloquerait la création d'un nouveau
compte admin (réservée au tout premier compte jamais créé, base vide).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-28 21:03:47 +02:00
co-authored by Claude Sonnet 5
parent d5a84413d1
commit 0d46abe193
9 changed files with 379 additions and 2 deletions
+1 -1
View File
@@ -1 +1 @@
from . import register, register_2fa, login, login_2fa, logout, forgot_password, reset_password
from . import register, register_2fa, login, login_2fa, logout, forgot_password, reset_password, profile
+77
View File
@@ -0,0 +1,77 @@
import os
import shutil
from flask import g, redirect, render_template, request, session, url_for
import auth
import db
from core.flask_app import app
def _render(error=None, success=None):
user = auth.get_user_by_id(g.current_user["id"])
return render_template(
"auth/profile.html", user=user, error=error, success=success,
min_score_required=auth.MIN_SCORE_REQUIRED,
)
@app.route("/profile", methods=["GET"])
def profile():
return _render()
@app.route("/profile/update-name", methods=["POST"])
def profile_update_name():
nom = request.form.get("nom", "").strip()
prenom = request.form.get("prenom", "").strip()
if not nom or not prenom:
return _render(error="Nom et prénom sont requis.")
auth.update_profile(g.current_user["id"], nom, prenom)
return _render(success="Informations mises à jour.")
@app.route("/profile/change-password", methods=["POST"])
def profile_change_password():
user = g.current_user
current_password = request.form.get("current_password", "")
new_password = request.form.get("new_password", "")
new_password_confirm = request.form.get("new_password_confirm", "")
if not auth.verify_password(user, current_password):
return _render(error="Mot de passe actuel incorrect.")
if new_password != new_password_confirm:
return _render(error="Les nouveaux mots de passe ne correspondent pas.")
if not auth.password_strength(new_password)["valid"]:
return _render(error="Nouveau mot de passe trop faible — respecte les critères affichés.")
auth.set_password(user["id"], new_password)
return _render(success="Mot de passe modifié.")
@app.route("/profile/delete", methods=["POST"])
def profile_delete():
user = g.current_user
current_password = request.form.get("current_password", "")
confirm_text = request.form.get("confirm_text", "").strip().upper()
if not auth.verify_password(user, current_password):
return _render(error="Mot de passe incorrect — suppression annulée.")
if confirm_text != "SUPPRIMER":
return _render(error='Tape "SUPPRIMER" pour confirmer — suppression annulée.')
if user["role"] == "admin" and auth.count_admins() <= 1:
return _render(error="Impossible de supprimer l'unique compte administrateur.")
# Un compte "user" ne possède qu'UN SEUL projet, créé automatiquement
# pour lui et impossible à supprimer autrement (voir core/auth_guard.py,
# game_delete bloqué pour ce rôle) — supprimer le compte est donc la
# seule façon de s'en débarrasser aussi, sans quoi il resterait orphelin
# sur le disque sans plus aucun propriétaire. Un "admin" peut posséder
# plusieurs jeux qui ne lui sont pas dédiés de la même façon : ses
# projets restent intacts.
if user["role"] != "admin" and user.get("project_slug"):
game_dir = db.game_dir(user["project_slug"])
if os.path.isdir(game_dir):
shutil.rmtree(game_dir)
auth.delete_user(user["id"])
session.clear()
return redirect(url_for("login"))