# 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)