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
+121
View File
@@ -0,0 +1,121 @@
{% extends "base.html" %}
{% block title %}Mon profil — Forge Engine{% endblock %}
{% block content %}
<section class="section">
<div class="container" style="max-width:560px;">
<h1 class="title">Mon profil</h1>
{% if error %}
<div class="notification is-danger">{{ error }}</div>
{% endif %}
{% if success %}
<div class="notification is-success">{{ success }}</div>
{% endif %}
<div class="box">
<h2 class="title is-5">Informations</h2>
<p class="hint mb-4">Adresse email : <strong>{{ user.email }}</strong> ({{ 'compte administrateur' if user.role == 'admin' else 'compte utilisateur' }})</p>
<form method="post" action="{{ url_for('profile_update_name') }}">
<div class="field">
<label class="label">Prénom</label>
<div class="control"><input class="input" type="text" name="prenom" value="{{ user.prenom }}" required></div>
</div>
<div class="field">
<label class="label">Nom</label>
<div class="control"><input class="input" type="text" name="nom" value="{{ user.nom }}" required></div>
</div>
<div class="field mt-4">
<button type="submit" class="button primary">Enregistrer</button>
</div>
</form>
</div>
<div class="box">
<h2 class="title is-5">Changer de mot de passe</h2>
<form method="post" action="{{ url_for('profile_change_password') }}" autocomplete="off">
<div class="field">
<label class="label">Mot de passe actuel</label>
<div class="control"><input class="input" type="password" name="current_password" required autocomplete="current-password"></div>
</div>
<div class="field">
<label class="label">Nouveau mot de passe</label>
<div class="control"><input class="input" type="password" id="password" name="new_password" required autocomplete="new-password"></div>
</div>
<div class="field">
<div class="passwordStrengthBar"><div class="passwordStrengthFill" id="pwFill"></div></div>
<ul class="passwordChecklist" id="pwChecklist">
<li data-check="longueur">Au moins 8 caractères</li>
<li data-check="majuscule">Une majuscule</li>
<li data-check="minuscule">Une minuscule</li>
<li data-check="chiffre">Un chiffre</li>
<li data-check="special">Un caractère spécial (ex. ! ? # -)</li>
</ul>
</div>
<div class="field">
<label class="label">Confirmer le nouveau mot de passe</label>
<div class="control"><input class="input" type="password" name="new_password_confirm" required autocomplete="new-password"></div>
</div>
<div class="field mt-4">
<button type="submit" class="button primary">Changer le mot de passe</button>
</div>
</form>
</div>
<div class="box" style="border:1px solid #b33;">
<h2 class="title is-5">Supprimer mon compte</h2>
<p class="hint mb-4">
{% if user.role != 'admin' %}
Action définitive et irréversible : ton compte ET ton jeu ({{ user.project_slug }}) seront supprimés, sans possibilité de récupération.
{% else %}
Action définitive et irréversible : ton compte sera supprimé (tes jeux existants ne sont pas touchés).
{% endif %}
</p>
<form method="post" action="{{ url_for('profile_delete') }}" autocomplete="off">
<div class="field">
<label class="label">Mot de passe actuel</label>
<div class="control"><input class="input" type="password" name="current_password" required autocomplete="current-password"></div>
</div>
<div class="field">
<label class="label">Tape "SUPPRIMER" pour confirmer</label>
<div class="control"><input class="input" type="text" name="confirm_text" required></div>
</div>
<div class="field mt-4">
<button type="submit" class="button is-danger">Supprimer définitivement mon compte</button>
</div>
</form>
</div>
</div>
</section>
<script>
var MIN_SCORE_REQUIRED = {{ min_score_required }};
var PW_CHECKS = {
longueur: function (p) { return p.length >= 8; },
majuscule: function (p) { return /[A-Z]/.test(p); },
minuscule: function (p) { return /[a-z]/.test(p); },
chiffre: function (p) { return /\d/.test(p); },
special: function (p) { return /[^A-Za-z0-9]/.test(p); },
};
(function () {
var input = document.getElementById('password');
var fill = document.getElementById('pwFill');
var items = document.querySelectorAll('#pwChecklist li');
if (!input) return;
function update() {
var value = input.value || '';
var score = 0;
items.forEach(function (li) {
var ok = PW_CHECKS[li.dataset.check](value);
li.classList.toggle('ok', ok);
if (ok) score++;
});
var pct = (score / items.length) * 100;
fill.style.width = pct + '%';
fill.className = 'passwordStrengthFill' +
(score >= items.length ? ' strong' : score >= MIN_SCORE_REQUIRED ? ' good' : score >= 2 ? ' medium' : ' weak');
}
input.addEventListener('input', update);
update();
})();
</script>
{% endblock %}
+1 -1
View File
@@ -47,7 +47,7 @@
</div>
{% if g.get('current_user') %}
<div class="navbar-end" style="margin-left:auto; display:flex; align-items:center; gap:10px; padding-right:12px;">
<span class="hint">{{ g.current_user.prenom }} {{ g.current_user.nom }}{{ ' (admin)' if g.current_user.role == 'admin' else '' }}</span>
<a href="{{ url_for('profile') }}" class="hint" style="text-decoration:underline;">{{ g.current_user.prenom }} {{ g.current_user.nom }}{{ ' (admin)' if g.current_user.role == 'admin' else '' }}</a>
<form method="post" action="{{ url_for('logout') }}"><button type="submit" class="button is-small">Déconnexion</button></form>
</div>
{% endif %}