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

11 KiB

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

  • Task 1: Implémenter l'Endpoint Admin DELETE (AC: #1, #2, #3, #4, #5, #8)

    • 1.1 Ajouter la route DELETE /api/v1/admin/api-keys/{key_id} dans main.py
    • 1.2 Utiliser la dépendance require_admin existante pour l'authentification
    • 1.3 Ajouter paramètre optionnel reason dans le body de la requête
    • 1.4 Rechercher la clé par id uniquement (PAS de filtre user_id)
    • 1.5 Si trouvée, définir is_active=False et sauvegarder
    • 1.6 Retourner 200 avec confirmation {data: {id, revoked: true, revoked_at, reason?}, meta: {}}
  • Task 2: Implémenter l'Audit Logging (AC: #6)

    • 2.1 Logger l'action avec structlog ou logging standard
    • 2.2 Inclure: timestamp, admin identifier, key_id, key owner user_id, reason
    • 2.3 Niveau de log: INFO pour révocation réussie
  • Task 3: Gérer les Cas d'Erreur (AC: #7)

    • 3.1 Retourner 404 si clé non trouvée
    • 3.2 Retourner 401 si non authentifié admin (déjà géré par require_admin)
    • 3.3 Vérifier que la clé existe avant de tenter la révocation
  • Task 4: Ajouter les Tests (AC: Tous)

    • 4.1 Test révocation réussie par admin
    • 4.2 Test révocation avec raison fournie
    • 4.3 Test révocation échoue sans authentification admin (401)
    • 4.4 Test révocation de clé inexistante (404)
    • 4.5 Test clé révoquée ne peut plus authentifier (vérifier via get_user_by_api_key)
    • 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):

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

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:

{
  "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:

{
  "error": "API_KEY_NOT_FOUND",
  "message": "Clé API non trouvée"
}

Pattern de Query Admin (SANS filtre user_id):

# 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

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:

  • DELETE /api/v1/admin/api-keys/{key_id} retourne 200 avec confirmation
  • La clé est marquée is_active=False (soft delete)
  • L'admin peut révoquer n'importe quelle clé (pas de filtre user_id)
  • L'action est journalisée avec admin_id, key_id, owner_user_id, reason
  • Le paramètre reason optionnel fonctionne correctement
  • Les utilisateurs non-admin reçoivent 401
  • Les clés inexistantes retournent 404
  • Tous les tests passent (15/15)
  • La clé révoquée ne peut plus authentifier (vérifier via test)