Files
kanbante/app/templates/profile.html
T
Spark 13182f5d53
CI / test (push) Successful in 1m56s
0.25.0 — Jetons d'API personnels (kbt_) pour les surfaces machine
Depuis 0.24.0, /mcp exige session ou Basic : le service d'agents s'y
faisait refuser en 401 (vu en production, panneau Spark du 2026-10-21).

- Table api_tokens (empreinte SHA-256 seule), CRUD dans Mon profil
- Bearer honoré par le middleware sur /mcp et /api/ uniquement —
  jamais l'interface web ; révocation et désactivation immédiates
- 235 tests verts (3 nouveaux : cycle de vie, compte désactivé,
  handshake MCP par jeton quand l'auth est active)
2026-10-21 21:38:16 -04:00

133 lines
7.6 KiB
HTML

<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token }}">
<title>Mon profil — Kanbante</title>
<link rel="stylesheet" href="/static/css/style.css?v={{ version }}">
</head>
<body>
{% include "_header.html" %}
<main class="focus-main">
<div class="page-head">
<h1>👤 Mon profil</h1>
<p class="page-sub">{{ profile_user.username }} · rôle : {{ profile_user.role }}</p>
</div>
{% if msg %}<p class="chip-ok">✅ {{ msg }}</p>{% endif %}
{% if err %}<p class="chip-warn">⚠️ {{ err }}</p>{% endif %}
{% if profile_user.must_change_password %}<p class="chip-warn">⚠️ Ton mot de passe est temporaire : change-le ci-dessous.</p>{% endif %}
<section class="briefing-card">
<h2>Informations</h2>
<form method="post" action="/profile" class="admin-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Nom d'affichage <input type="text" name="display_name" value="{{ profile_user.display_name or '' }}"></label>
<button type="submit" class="btn-save btn-inline">Enregistrer</button>
</form>
<p class="muted">Mes espaces :
{% for m in memberships %}<span class="ws-dot" style="background: {{ m.color }}"></span> {{ m.name }} ({{ m.member_role }}){% if not loop.last %} · {% endif %}
{% else %}aucun{% endfor %}
</p>
</section>
<section class="briefing-card">
<h2>🔑 Mot de passe</h2>
{% if profile_user.id %}
<form method="post" action="/profile/password" class="admin-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Mot de passe actuel <input type="password" name="current_password" required autocomplete="current-password"></label>
<label>Nouveau mot de passe <input type="password" name="new_password" required autocomplete="new-password"></label>
<button type="submit" class="btn-save btn-inline">Changer</button>
</form>
{% else %}
<p class="muted">Compte défini par l'environnement (KANBANTE_AUTH_*) : le mot de passe se change dans la configuration du déploiement.</p>
{% endif %}
</section>
<section class="briefing-card">
<h2>🗝️ Jetons d'API (MCP)</h2>
<p class="muted">Pour les consommateurs machine — le service d'agents, en premier lieu. Un jeton (préfixe <code>kbt_</code>) authentifie uniquement <code>/mcp</code> et <code>/api/</code>, jamais l'interface web. Il se présente en en-tête <code>Authorization: Bearer kbt_…</code>. Le texte clair n'est affiché qu'à la création ; ensuite, seule son empreinte est conservée.</p>
{% if profile_user.id %}
{% if nouveau_jeton %}
<p>✅ Nouveau jeton — <strong>copie-le maintenant</strong> :</p>
<p><code style="font-size:15px">{{ nouveau_jeton }}</code></p>
{% endif %}
{% if api_tokens %}
<ul>
{% for t in api_tokens %}
<li>
<strong>{{ t.name }}</strong> · créé le {{ t.created_at }}
· {% if t.last_used_at %}dernier usage : {{ t.last_used_at }}{% else %}jamais utilisé{% endif %}
{% if t.revoked_at %} · <span class="chip-warn">révoqué le {{ t.revoked_at }}</span>
{% else %}
<form method="post" action="/profile/tokens/{{ t.id }}/revoke" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn-mini">Révoquer</button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Aucun jeton pour l'instant.</p>
{% endif %}
<form method="post" action="/profile/tokens" class="admin-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Nom du jeton <input type="text" name="name" placeholder="service-agents" required></label>
<button type="submit" class="btn-save btn-inline">Créer un jeton</button>
</form>
{% else %}
<p class="muted">Compte défini par l'environnement (KANBANTE_AUTH_*) : les jetons appartiennent aux comptes de la base — connecte-toi avec ton compte personnel pour en créer un.</p>
{% endif %}
</section>
<section class="briefing-card">
<h2>✦ Liaison draco (SSO)</h2>
{% if profile_user.draco_sub %}
<p>Compte lié à l'identité draco <code>{{ profile_user.draco_sub }}</code> — la connexion via ObsiGate ouvre directement cette session.</p>
<form method="post" action="/profile/draco/unlink" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn-mini">Délier</button>
</form>
{% else %}
<p class="muted">Pas de liaison. Elle se crée automatiquement à la première visite où ta session Kanbante et ton cookie draco coexistent (par exemple en arrivant depuis ObsiGate).</p>
{% endif %}
</section>
<section class="briefing-card">
<h2>🔌 Mes connexions personnelles</h2>
<p class="muted">Visibles de toi seul. Pour les utiliser, ajoute-les à un de tes espaces (ou demande à un admin d'espace).</p>
{% for c in my_connections %}
<p>
{% if c.kind == 'github' %}🐙{% else %}🍵{% endif %} <strong>{{ c.name }}</strong>
<span class="muted">({{ c.slug }}) · {{ c.base_url }} · compte : {{ c.account_login or 'à tester' }} · jeton : {{ c.token_mask }}</span>
<form method="post" action="/profile/connections/{{ c.id }}/test" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn-mini">🧪 Tester</button>
</form>
<form method="post" action="/profile/connections/{{ c.id }}/delete" class="inline-form" onsubmit="return confirm('Supprimer cette connexion personnelle ?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn-mini btn-danger">Supprimer</button>
</form>
</p>
{% endfor %}
<form method="post" action="/profile/connections" class="admin-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Type
<select name="kind">
<option value="gitea">Gitea</option>
<option value="github">GitHub</option>
</select>
</label>
<label>Nom <input type="text" name="name" required></label>
<label>URL de base <input type="text" name="base_url" required placeholder="https://api.github.com"></label>
<label>Jeton d'accès <input type="password" name="token" autocomplete="off"></label>
<button type="submit" class="btn-save btn-inline">Ajouter</button>
</form>
</section>
</main>
</body>
</html>