96 lines
3.7 KiB
Markdown
96 lines
3.7 KiB
Markdown
# #107 — Clés API & MCP (panneau de configuration)
|
|
|
|
**Version : 2.15.0 — statut : complété (septembre 2026)**
|
|
|
|
## Problème
|
|
|
|
Pour brancher un client MCP externe (Claude Desktop, Cursor…) ou scripter
|
|
l'API REST, il fallait soit se connecter et voler le JWT de session de 1 h
|
|
dans le navigateur, soit générer un token à la main via `docker exec`
|
|
(`generer_access_token.sh`) — sans expiration maîtrisable ni révocation.
|
|
|
|
## Décision : une seule clé pour l'API ET le MCP
|
|
|
|
Le serveur MCP (`/mcp`, `backend/mcp/server.py::_authenticate`) résout
|
|
l'appelant via la même dépendance `get_current_user()` que l'API REST.
|
|
Un jeton HS256 `type=access` authentifie donc **les deux surfaces** — il
|
|
n'y a pas de famille de clés séparée à exposer dans l'UI. C'est dit
|
|
explicitement dans la section (« la même clé fonctionne pour les deux »)
|
|
et verrouillé par tests (REST 200 + MCP initialize 200 avec la même clé ;
|
|
révocation → 401 des deux côtés).
|
|
|
|
## Conception
|
|
|
|
### Store — `data/api_tokens.json`
|
|
|
|
```json
|
|
{"version": 1, "tokens": {"<jti>": {
|
|
"name": "Claude Desktop", "username": "admin",
|
|
"created_at": 1790000000, "expires_at": 1792592000,
|
|
"expiry_key": "30d", "last_used_at": null
|
|
}}}
|
|
```
|
|
|
|
Le JWT brut n'est **jamais persisté** : affiché une seule fois à la
|
|
création, sinon perdu (pattern GitHub). La révocation fonctionne par
|
|
`jti` : le JWT présenté est rejeté par `is_token_revoked` même s'il est
|
|
encore valide dans sa signature.
|
|
|
|
### Expirations (choix UI)
|
|
|
|
| Clé | Durée | `exp` dans le JWT |
|
|
|---|---|---|
|
|
| `1d` | 1 jour | iat + 86 400 |
|
|
| `30d` | 1 mois | iat + 2 592 000 |
|
|
| `180d` | 6 mois | iat + 15 552 000 |
|
|
| `365d` | 1 an | iat + 31 536 000 |
|
|
| `never` | sans fin | **aucun claim exp** |
|
|
|
|
Plafond : 50 tokens actifs par utilisateur (`API_TOKEN_MAX_PER_USER`).
|
|
|
|
### Correction induite — révocation longue durée
|
|
|
|
L'ancien `revoked_tokens.json` bornait toute entrée à 7 jours ; une clé
|
|
1 an révoquée aurait « repris vie » au nettoyage suivant. Le store devient
|
|
un dict `{jti: valid_until}` calé sur l'expiration réelle du jeton
|
|
(« sans fin » → 100 ans). Session tokens inchangés (7 j).
|
|
|
|
### « Dernière utilisation »
|
|
|
|
`get_current_user` appelle `maybe_touch_api_token(jti)` pour les jetons
|
|
`api: true` — écriture disque throttlée à 1 h par jti, silencieuse si le
|
|
dossier est en lecture seule ; ne fait jamais échouer une requête.
|
|
|
|
## Endpoints (`/api/auth`, auth requise, périmètre = l'appelant)
|
|
|
|
- `GET /api/auth/tokens` → `{tokens: [...], expiry_choices: [...]}`
|
|
- `POST /api/auth/tokens` `{name, expiry}` → `{token, ...record}` (secret unique)
|
|
- `DELETE /api/auth/tokens/{jti}` → révocation immédiate API + MCP
|
|
- Audit : `config_change` / `api_token_create|revoke`.
|
|
|
|
## UI — panneau Configuration
|
|
|
|
Nouvelle section `#cfg-tokens` « 🔑 Clés API & MCP » (après Sécurité) :
|
|
liste (badge Active/Expirée, créée/expire/dernière utilisation), champ
|
|
nom + sélecteur d'expiration + Créer, zone secrète en tirets avec Copier,
|
|
bloc « Utilisation » : header `Authorization: Bearer <clé>` + exemple de
|
|
config MCP avec headers sur `<base>/mcp`. 28 clés i18n FR/EN, SW v24.
|
|
|
|
## Tests — `tests/test_api_tokens.py` (13)
|
|
|
|
Création/liste (secret jamais restitué), les 5 durées dont l'absence
|
|
d'`exp` pour `never`, expiration invalide → 400, clé identique acceptée
|
|
par REST **et** `/mcp`, refus MCP anonyme, isolation par utilisateur,
|
|
révocation → 401 immédiat des deux côtés, survie de la révocation
|
|
longue durée après reload disque, drapeau `expired`, migration de format
|
|
`revoked_tokens.json`. Suite auth + MCP complète verte (77).
|
|
|
|
## Config MCP externe (exemple)
|
|
|
|
```json
|
|
{"mcpServers": {"obsigate": {
|
|
"url": "http://localhost:2020/mcp",
|
|
"headers": {"Authorization": "***"}
|
|
}}}
|
|
```
|