Jetons MCP chiffrés au repos + bouton « Copier le jeton » sur /compte/mcp

Les jetons MCP sont désormais chiffrés (Fernet dérivée de SECRET_KEY) en plus
du hash conservé pour l'auth, permettant leur ré-affichage/copie à tout moment.
Getter scopé au propriétaire, dégradation propre si clé absente/changée.
Migration 0013 + colonne token_enc. UI alignée sur « Copier le lien » des vues.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-07-14 23:55:01 +02:00
parent 43d8ba5098
commit f571627a6b
6 changed files with 172 additions and 11 deletions
+31
View File
@@ -101,3 +101,34 @@ def test_revoke_user_token_already_revoked_returns_false(temp_db):
_, token_id = tokens_db.create_token(temp_db, "x", user_id=1, kind="mcp")
assert tokens_db.revoke_user_token(temp_db, token_id, 1) is True
assert tokens_db.revoke_user_token(temp_db, token_id, 1) is False
def test_create_token_stores_recoverable_encrypted_token(temp_db, monkeypatch):
monkeypatch.setenv("SECRET_KEY", "s3cr3t-test-key")
token, token_id = tokens_db.create_token(temp_db, "x", user_id=7, kind="mcp")
with sqlite3.connect(str(temp_db)) as conn:
enc = conn.execute(
"SELECT token_enc FROM api_tokens WHERE id = ?", (token_id,)
).fetchone()[0]
assert enc is not None
assert token not in enc # chiffré, jamais en clair dans la base
assert tokens_db.get_token_plaintext_for_user(temp_db, token_id, 7) == token
def test_get_token_plaintext_scoped_to_owner(temp_db, monkeypatch):
monkeypatch.setenv("SECRET_KEY", "s3cr3t-test-key")
_, token_id = tokens_db.create_token(temp_db, "x", user_id=1, kind="mcp")
assert tokens_db.get_token_plaintext_for_user(temp_db, token_id, 999) is None
def test_get_token_plaintext_none_when_no_key(temp_db, monkeypatch):
monkeypatch.delenv("SECRET_KEY", raising=False)
_, token_id = tokens_db.create_token(temp_db, "x", user_id=1, kind="mcp")
assert tokens_db.get_token_plaintext_for_user(temp_db, token_id, 1) is None
def test_get_token_plaintext_none_after_key_rotation(temp_db, monkeypatch):
monkeypatch.setenv("SECRET_KEY", "key-A")
_, token_id = tokens_db.create_token(temp_db, "x", user_id=1, kind="mcp")
monkeypatch.setenv("SECRET_KEY", "key-B") # rotation → indéchiffrable
assert tokens_db.get_token_plaintext_for_user(temp_db, token_id, 1) is None