Files
office_translator/_bmad-output/implementation-artifacts/3-3-admin-revocation-api-key-any-user.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:47 +02:00

294 lines
11 KiB
Markdown

# Story 3.3: Admin - Révocation API Key (Any User)
Status: done
## Story
En tant qu'**Admin**,
Je veux **révoquer la clé API de n'importe quel utilisateur**,
de sorte que **je puisse gérer la sécurité et prévenir les abus**.
## Acceptance Criteria
1. **Endpoint Admin DELETE**: `DELETE /api/v1/admin/api-keys/{key_id}` révoque la clé API spécifiée. (FR31)
2. **Accès Admin Requis**: Seul un administrateur authentifié peut accéder à cet endpoint.
3. **Révocation Universelle**: L'admin peut révoquer N'IMPORTE quelle clé, peu importe le propriétaire.
4. **Révocation Immédiate**: La clé est marquée `is_active=False` et les requêtes suivantes avec cette clé retournent 401 avec le code `API_KEY_REVOKED`.
5. **Réponse Confirmée**: Retourne 200 avec confirmation de révocation dans le format `{data: {...}, meta: {}}`.
6. **Audit Logging**: L'action est journalisée avec `admin_id` et `reason` (paramètre optionnel).
7. **Clé Introuvable**: Si la clé n'existe pas, retourne 404 avec `API_KEY_NOT_FOUND`.
8. **Paramètre Raison**: L'admin peut optionnellement fournir une raison pour la révocation (body parameter).
## Tasks / Subtasks
- [x] **Task 1: Implémenter l'Endpoint Admin DELETE** (AC: #1, #2, #3, #4, #5, #8)
- [x] 1.1 Ajouter la route `DELETE /api/v1/admin/api-keys/{key_id}` dans `main.py`
- [x] 1.2 Utiliser la dépendance `require_admin` existante pour l'authentification
- [x] 1.3 Ajouter paramètre optionnel `reason` dans le body de la requête
- [x] 1.4 Rechercher la clé par `id` uniquement (PAS de filtre user_id)
- [x] 1.5 Si trouvée, définir `is_active=False` et sauvegarder
- [x] 1.6 Retourner 200 avec confirmation `{data: {id, revoked: true, revoked_at, reason?}, meta: {}}`
- [x] **Task 2: Implémenter l'Audit Logging** (AC: #6)
- [x] 2.1 Logger l'action avec structlog ou logging standard
- [x] 2.2 Inclure: timestamp, admin identifier, key_id, key owner user_id, reason
- [x] 2.3 Niveau de log: INFO pour révocation réussie
- [x] **Task 3: Gérer les Cas d'Erreur** (AC: #7)
- [x] 3.1 Retourner 404 si clé non trouvée
- [x] 3.2 Retourner 401 si non authentifié admin (déjà géré par require_admin)
- [x] 3.3 Vérifier que la clé existe avant de tenter la révocation
- [x] **Task 4: Ajouter les Tests** (AC: Tous)
- [x] 4.1 Test révocation réussie par admin
- [x] 4.2 Test révocation avec raison fournie
- [x] 4.3 Test révocation échoue sans authentification admin (401)
- [x] 4.4 Test révocation de clé inexistante (404)
- [x] 4.5 Test clé révoquée ne peut plus authentifier (vérifier via get_user_by_api_key)
- [x] 4.6 Test vérifier que l'audit logging fonctionne
## Dev Notes
### Infrastructure Existante (Ne pas réimplémenter)
**Fonction Admin Auth** (`main.py` lignes 215-236):
```python
async def require_admin(authorization: Optional[str] = Header(None)) -> bool:
"""Dependency to require admin authentication"""
# Vérifie le token admin (stocké en mémoire ou via JWT)
# Retourne True si valide, sinon lève HTTPException
```
**Modèle ApiKey** (`database/models.py`):
```python
class ApiKey(Base):
__tablename__ = "api_keys"
id = Column(String(36), primary_key=True)
user_id = Column(String(36), ForeignKey("users.id")) # Propriétaire de la clé
name = Column(String(100))
key_hash = Column(String(255))
key_prefix = Column(String(10))
is_active = Column(Boolean, default=True) # ⭐ Champ à modifier pour révocation
# ... autres champs
```
**Pattern Admin Routes** (`main.py`):
- Toutes les routes admin sont directement dans `main.py` avec préfixe `/api/v1/admin/`
- Utilisent `is_admin: bool = Depends(require_admin)` comme dépendance
### Différences Critiques avec Story 3.2
| Aspect | Story 3.2 (User) | Story 3.3 (Admin) |
|--------|------------------|-------------------|
| Endpoint | `DELETE /api/v1/api-keys/{key_id}` | `DELETE /api/v1/admin/api-keys/{key_id}` |
| Auth | `_require_pro_user` (Pro tier) | `require_admin` (Admin) |
| Filtre Query | `user_id == user.id` (propriété) | Aucun (accès universel) |
| Logging | Non requis | **Obligatoire** avec admin_id + reason |
| Raison | Non applicable | Paramètre optionnel |
### Patterns à Suivre
**Format de Réponse Succès**:
```json
{
"data": {
"id": "abc123",
"revoked": true,
"revoked_at": "2024-01-15T10:30:00Z",
"owner_user_id": "user-uuid-here",
"reason": "Violation des conditions d'utilisation"
},
"meta": {}
}
```
**Format de Réponse Erreur**:
```json
{
"error": "API_KEY_NOT_FOUND",
"message": "Clé API non trouvée"
}
```
**Pattern de Query Admin** (SANS filtre user_id):
```python
# Admin peut révoquer N'IMPORTE quelle clé
api_key = session.query(ApiKey).filter(
ApiKey.id == key_id
).first()
```
### Structure de Fichiers
```
main.py # MODIFIER - Ajouter DELETE /api/v1/admin/api-keys/{key_id}
routes/api_key_routes.py # Existant - Story 3.2 (ne pas modifier)
services/auth_service.py # Existant - get_user_by_api_key vérifie déjà is_active
database/models.py # Existant - ApiKey model
tests/test_story_3_3_admin_api_key_revocation.py # CRÉER
```
### Project Structure Notes
- Les routes admin sont centralisées dans `main.py` (pas de fichier routes/admin_routes.py)
- Pattern établi par les autres endpoints admin existants
- Le logging utilise `structlog` ou le module `logging` standard de Python
### Références
- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.3]
- [Source: _bmad-output/planning-artifacts/architecture.md#Admin Dashboard]
- [Source: _bmad-output/planning-artifacts/architecture.md#API Response Formats]
- [Source: main.py#require_admin (lignes 215-236)]
- [Source: routes/api_key_routes.py#revoke_api_key (lignes 257-308)]
## Intelligence de la Story Précédente (3.2)
### Ce qui a été implémenté
1. **Endpoint DELETE** pour révocation utilisateur à `DELETE /api/v1/api-keys/{key_id}`
2. **Soft delete** avec `is_active=False`
3. **Fonction `get_user_by_api_key`** dans `services/auth_service.py` vérifie `is_active`
4. **18 tests** complets dans `tests/test_story_3_2_api_key_revocation.py`
### Patterns Établis à Réutiliser
```python
from datetime import datetime, timezone
from fastapi.responses import JSONResponse
# Soft delete pattern
api_key.is_active = False
session.commit()
# Response format
return JSONResponse(
status_code=200,
content={
"data": {
"id": api_key.id,
"revoked": True,
"revoked_at": datetime.now(timezone.utc).isoformat(),
},
"meta": {},
},
)
```
### Points d'Attention Identifiés
1. **NE PAS** utiliser `HTTPException` avec `detail` string - Utiliser `JSONResponse` structuré
2. **TOUJOURS** snake_case dans les réponses JSON
3. **VÉRIFIER** que `get_user_by_api_key` vérifie déjà `is_active` (✅ confirmé)
## Intelligence Git (Commits Récents)
Derniers commits pertinents:
- `dfd45d9`: Admin login endpoint - patterns d'authentification admin
- `80318a8`: Admin dashboard - structure des routes admin
**Patterns identifiés**:
- Routes admin directement dans `main.py`
- Dépendance `require_admin` pour l'auth
- Tests avec `pytest` dans `tests/`
## Contexte Métier
### Epic 3: API & Automation (Pro)
Cette story est la **troisième de l'Epic 3** qui permet aux utilisateurs Pro d'automatiser les traductions:
1. ~~API Keys - Génération~~ (Story 3.1 ✅)
2. ~~API Keys - Révocation User~~ (Story 3.2 ✅)
3. **API Keys - Révocation Admin** (cette story)
4. Authentification X-API-Key (Story 3.4 - backlog)
5. API Versioning (Story 3.5 - backlog)
6. Documentation OpenAPI (Story 3.6 - backlog)
7. Webhooks (Stories 3.7-3.8 - backlog)
8. Glossaires (Stories 3.9-3.10 - backlog)
9. Custom Prompts (Stories 3.11-3.12 - backlog)
### Valeur Business
La révocation admin est critique pour:
- Gérer les abus (surutilisation, comportement suspect)
- Répondre aux demandes de support (compte compromis)
- Appliquer les conditions d'utilisation
- Contrôle de sécurité centralisé
### Dépendances
- **Story 3.1** (prérequis): Génération de clés API ✅
- **Story 3.2** (prérequis): Révocation utilisateur ✅
- **Story 3.4** (impact): L'authentification API vérifie `is_active` ✅ (déjà implémenté)
## Guardrails Développeur
### ❌ À NE PAS FAIRE
1. **NE PAS** supprimer physiquement la clé de la DB (soft delete avec `is_active=False`)
2. **NE PAS** filtrer par `user_id` dans la query (admin a accès universel)
3. **NE PAS** utiliser `HTTPException` avec `detail` string (utiliser JSONResponse structuré)
4. **NE PAS** oublier l'audit logging (obligatoire pour cette story)
5. **NE PAS** utiliser camelCase dans les réponses JSON (toujours snake_case)
6. **NE PAS** créer un fichier `routes/admin_routes.py` - les routes admin restent dans `main.py`
### ✅ À FAIRE
1. **TOUJOURS** utiliser `require_admin` dependency pour l'authentification
2. **TOUJOURS** soft delete (`is_active=False`)
3. **TOUJOURS** logger l'action avec admin_id, key_id, owner_user_id, et reason
4. **TOUJOURS** retourner 404 si clé non trouvée
5. **TOUJOURS** suivre le format de réponse `{data: {...}, meta: {...}}`
6. **TOUJOURS** écrire des tests pour tous les cas
7. **INCLURE** le `owner_user_id` dans la réponse pour traçabilité
## Dev Agent Record
### Agent Model Used
Claude 3.5 Sonnet (claude-3-5-sonnet)
### Debug Log References
Aucun problème rencontré lors de l'implémentation.
### Completion Notes List
- ✅ Analyse exhaustive du contexte terminée - guide complet créé pour le développeur
- ✅ Patterns de la Story 3.2 réutilisables identifiés
- ✅ Code existant analysé (main.py, routes/api_key_routes.py)
- ✅ Différences critiques avec Story 3.2 documentées
- ✅ Endpoint DELETE /api/v1/admin/api-keys/{key_id} implémenté dans main.py
- ✅ AdminRevokeApiKeyRequest model ajouté pour le body parameter optionnel `reason`
- ✅ Soft delete avec is_active=False et revoked_at timestamp
- ✅ Audit logging avec logger.info incluant admin_id, key_id, owner_user_id, reason, timestamp
- ✅ 15 tests créés et tous passent (tests/test_story_3_3_admin_api_key_revocation.py)
- ✅ Migration alembic 003_add_revoked_at_to_api_keys.py appliquée
- ✅ Tests de régression passent (Story 3.2 et admin tier change)
### File List
- `main.py` - MODIFIÉ - Ajout endpoint DELETE /api/v1/admin/api-keys/{key_id}, AdminRevokeApiKeyRequest model, require_admin retourne admin_id
- `tests/test_story_3_3_admin_api_key_revocation.py` - CRÉÉ - 15 tests couvrant tous les AC
## Change Log
- 2026-02-22: Story créée avec contexte complet (patterns Story 3.2, code suggéré, différences critiques)
- 2026-02-22: Implémentation terminée - Endpoint DELETE, audit logging, tests
- 2026-02-22: Code review - Fix dead code (lignes dupliquées supprimées), fix AC6 (admin_id ajouté au log), fix tests (vérification admin_id ajoutée)
## Checklist de Validation
Avant de marquer cette story comme terminée, vérifier:
- [x] `DELETE /api/v1/admin/api-keys/{key_id}` retourne 200 avec confirmation
- [x] La clé est marquée `is_active=False` (soft delete)
- [x] L'admin peut révoquer n'importe quelle clé (pas de filtre user_id)
- [x] L'action est journalisée avec admin_id, key_id, owner_user_id, reason
- [x] Le paramètre `reason` optionnel fonctionne correctement
- [x] Les utilisateurs non-admin reçoivent 401
- [x] Les clés inexistantes retournent 404
- [x] Tous les tests passent (15/15)
- [x] La clé révoquée ne peut plus authentifier (vérifier via test)