# Prompt Claude Code — Phase 1 : Fondations sécurité
# Modèle : claude-opus-4-6
# Usage : claude --model claude-opus-4-6 -p "$(cat PROMPT_PHASE1.md)"
# ou coller directement dans une session Claude Code interactive
---
Tu es un ingénieur backend senior Python spécialisé en FastAPI, SQLAlchemy async et sécurité des APIs REST. Tu travailles sur le projet Imago, un backend centralisé de gestion d'images conçu pour servir plusieurs applications clientes simultanément.
## Projet : Imago
Backend FastAPI existant servant de hub centralisé pour :
- Stocker et gérer des images avec génération automatique de thumbnails
- Extraire les métadonnées EXIF (appareil photo, GPS, paramètres de prise de vue)
- Effectuer l'OCR sur les images via Tesseract
- Analyser les images avec Claude Vision AI (description + classification par tags)
### Stack technique en place
- FastAPI + Uvicorn (serveur ASGI)
- SQLAlchemy async + Alembic (ORM + migrations)
- Pydantic v2 + pydantic-settings (validation + config)
- Pillow + piexif (traitement images + EXIF)
- pytesseract (OCR)
- Anthropic Claude via httpx (Vision AI)
- aiofiles (I/O async)
- SQLite (développement) / PostgreSQL (production)
### Structure du projet
```
imago/
├── app/
│ ├── main.py # Application FastAPI
│ ├── config.py # Settings depuis .env
│ ├── database.py # Engine SQLAlchemy async + session
│ ├── models/
│ │ └── image.py # Modèle Image (EXIF, OCR, AI, statut)
│ ├── schemas/
│ │ └── __init__.py # Schémas Pydantic
│ ├── routers/
│ │ ├── images.py # Endpoints images (CRUD + pipeline)
│ │ └── ai.py # Endpoints AI (résumé URL, tâches)
│ └── services/
│ ├── storage.py # Sauvegarde fichiers + thumbnails
│ ├── exif_service.py # Extraction EXIF
│ ├── ocr_service.py # OCR Tesseract
│ ├── ai_vision.py # Vision AI Claude
│ ├── scraper.py # Scraping web
│ └── pipeline.py # Orchestration pipeline AI
├── tests/
│ └── test_services.py
├── requirements.txt
└── .env
```
### Problème actuel
Le backend est **entièrement public** : aucune authentification, aucune isolation entre clients. N'importe quelle application peut accéder, modifier ou supprimer toutes les données. C'est incompatible avec un hub multi-clients en production.
## Mission : implémenter la Phase 1 — Fondations sécurité
Tu dois réaliser les 4 livrables suivants dans l'ordre indiqué. Chaque livrable doit être **complet, testé et fonctionnel** avant de passer au suivant.
### Livrable 1.1 — Authentification API Keys + JWT avec scopes (priorité CRITIQUE)
**Objectif** : sécuriser tous les endpoints avec deux mécanismes d'authentification complémentaires.
**Ce qui doit être créé ou modifié** :
1. `app/models/client.py` — Nouveau modèle SQLAlchemy `APIClient` :
- `id` : UUID, primary key
- `name` : String, nom de l'application cliente (ex: "Shaarli", "App Mobile")
- `api_key_hash` : String, hash SHA-256 de la clé API (jamais stocker en clair)
- `scopes` : JSON, liste des permissions accordées
- `plan` : Enum (`free`, `standard`, `premium`)
- `is_active` : Boolean, default True
- `created_at` / `updated_at` : DateTime
2. `app/dependencies/__init__.py` + `app/dependencies/auth.py` — Dépendances FastAPI :
- `verify_api_key(authorization: str = Header(...))` → retourne l'`APIClient` authentifié
- `require_scope(scope: str)` → factory qui vérifie qu'un scope est accordé
- `get_current_client` → alias réutilisable dans tous les routers
- Lever `HTTP 401` si clé invalide ou client inactif
- Lever `HTTP 403` si scope manquant
3. `app/routers/auth.py` — Endpoints de gestion des clients :
- `POST /auth/clients` — Créer un nouveau client (retourne la clé en clair une seule fois)
- `GET /auth/clients` — Lister les clients (admin only, scope `admin`)
- `GET /auth/clients/{id}` — Détail d'un client
- `PATCH /auth/clients/{id}` — Modifier un client (scopes, plan, is_active)
- `POST /auth/clients/{id}/rotate-key` — Régénérer la clé API
- `DELETE /auth/clients/{id}` — Désactiver (soft delete)
4. Scopes à définir :
- `images:read` — lire les images et métadonnées
- `images:write` — uploader et modifier des images
- `images:delete` — supprimer des images
- `ai:use` — utiliser les endpoints AI (résumé URL, génération tâches)
- `admin` — gestion des clients (réservé à un super-client)
5. `app/config.py` — Ajouter :
- `ADMIN_API_KEY` : clé du super-client admin (depuis .env)
- `JWT_SECRET_KEY` et `JWT_ALGORITHM` pour les tokens JWT futurs
**Contraintes** :
- Les clés API doivent être générées avec `secrets.token_urlsafe(32)`
- Le hash doit être SHA-256 via `hashlib`
- La clé en clair ne doit **jamais** être stockée en base ni apparaître dans les logs
- Utiliser `python-jose` et `passlib` (ajouter au requirements.txt)
---
### Livrable 1.2 — Modèle clients + isolation complète des données (priorité CRITIQUE)
**Objectif** : rendre toutes les données strictement isolées par client.
**Ce qui doit être créé ou modifié** :
1. `app/models/image.py` — Modifier le modèle `Image` existant :
- Ajouter `client_id = Column(UUID, ForeignKey("api_clients.id"), nullable=False, index=True)`
- Ajouter la relation `client = relationship("APIClient", back_populates="images")`
2. `app/models/client.py` — Ajouter la relation inverse :
- `images = relationship("Image", back_populates="client", cascade="all, delete-orphan")`
3. `app/services/storage.py` — Modifier le service de stockage :
- Les fichiers doivent être stockés dans `uploads/{client_id}/{filename}`
- Les thumbnails dans `thumbnails/{client_id}/{filename}`
- La fonction `save_upload` doit accepter `client_id` en paramètre
4. `app/routers/images.py` — Modifier TOUS les endpoints :
- Injecter `client: APIClient = Depends(get_current_client)` dans chaque endpoint
- Appliquer `require_scope("images:read")` sur les GET
- Appliquer `require_scope("images:write")` sur les POST
- Appliquer `require_scope("images:delete")` sur les DELETE
- Filtrer systématiquement toutes les requêtes avec `WHERE image.client_id = client.id`
- **Un client ne doit jamais pouvoir accéder aux images d'un autre client**
5. `app/routers/ai.py` — Même injection + scope `ai:use`
6. `alembic/versions/` — Créer une migration Alembic pour :
- Créer la table `api_clients`
- Ajouter la colonne `client_id` à la table `images`
- Créer un client "default" avec toutes les permissions pour la migration des données existantes
**Contraintes** :
- Toute requête DB touchant des images DOIT inclure le filtre `client_id`
- Écrire une fonction utilitaire `get_image_or_404(image_id, client_id, db)` qui centralise ce pattern
- Le filtre doit être appliqué au niveau service, pas seulement dans les routers
---
### Livrable 1.3 — Rate limiting par client et par endpoint (priorité HAUTE)
**Objectif** : protéger le hub contre les abus et contrôler la consommation par client.
**Ce qui doit être créé ou modifié** :
1. Ajouter `slowapi` au requirements.txt
2. `app/middleware/rate_limit.py` — Configuration du rate limiting :
- Identifier les requêtes par `client_id` (extrait du token) plutôt que par IP
- Limites différentes selon le plan :
- `free` : 20 uploads/heure, 50 requêtes AI/heure
- `standard` : 100 uploads/heure, 200 requêtes AI/heure
- `premium` : 500 uploads/heure, 1000 requêtes AI/heure
- Retourner `HTTP 429` avec header `Retry-After` si limite atteinte
3. `app/routers/images.py` — Appliquer les décorateurs rate limit sur :
- `POST /images/upload` → limiter par plan
- `POST /images/{id}/reprocess` → limiter par plan
4. `app/routers/ai.py` — Appliquer sur :
- `POST /ai/summarize` → limiter par plan
- `POST /ai/draft-task` → limiter par plan
5. `app/config.py` — Ajouter les variables de configuration des limites par plan
**Contraintes** :
- Les compteurs de rate limit doivent être par client (pas global)
- Inclure les headers standard : `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
---
### Livrable 1.4 — Tests d'intégration auth + multi-tenants (priorité HAUTE)
**Objectif** : valider que la sécurité fonctionne correctement avec une suite de tests exhaustive.
**Ce qui doit être créé** :
1. `tests/conftest.py` — Fixtures pytest :
- `test_db` : base SQLite in-memory pour les tests
- `client_a` et `client_b` : deux clients API de test avec des clés distinctes
- `auth_headers_a` et `auth_headers_b` : headers d'authentification correspondants
- `async_client` : `httpx.AsyncClient` configuré pour les tests
2. `tests/test_auth.py` — Tests d'authentification :
- ✅ Requête sans clé → HTTP 401
- ✅ Requête avec clé invalide → HTTP 401
- ✅ Requête avec clé valide → HTTP 200
- ✅ Client désactivé → HTTP 401
- ✅ Scope manquant → HTTP 403
- ✅ Rotation de clé → ancienne clé invalide, nouvelle clé valide
- ✅ Création de client → clé retournée une seule fois
3. `tests/test_isolation.py` — Tests d'isolation multi-tenants :
- ✅ Client A upload une image → invisible pour Client B
- ✅ Client B ne peut pas lire l'image du Client A (même avec bonne clé)
- ✅ Client B ne peut pas supprimer l'image du Client A → HTTP 404
- ✅ Listing des images de A ne retourne que les images de A
- ✅ Les fichiers sont stockés dans des répertoires séparés
- ✅ Reprocess d'une image appartenant à un autre client → HTTP 404
4. `tests/test_rate_limit.py` — Tests de rate limiting :
- ✅ Dépassement de quota → HTTP 429 avec header `Retry-After`
- ✅ Compteur distinct par client
- ✅ Headers `X-RateLimit-*` présents sur chaque réponse
**Contraintes** :
- Tous les tests doivent être async (`pytest-asyncio`)
- Les appels à l'API Anthropic doivent être mockés dans les tests
- Couverture minimum : 85% sur les modules `auth.py`, `dependencies/auth.py`, `models/client.py`
## Règles d'exécution
### Ordre de réalisation
Implémenter dans l'ordre strict : 1.1 → 1.2 → 1.3 → 1.4. Ne pas passer au livrable suivant sans avoir validé le précédent avec `pytest`.
### À chaque livrable
1. Lire les fichiers existants concernés avant de les modifier
2. Écrire le code complet (pas de `# TODO` ni de `# ...`)
3. Mettre à jour `requirements.txt` si de nouvelles dépendances sont ajoutées
4. Créer la migration Alembic si le modèle de données change
5. Lancer `pytest tests/ -v` et corriger les erreurs avant de continuer
### Qualité du code
- Typage complet sur toutes les fonctions (mypy compatible)
- Docstrings sur tous les services et dépendances
- Pas de secrets hardcodés — tout passe par `app/config.py` + `.env`
- Logging structuré avec le niveau approprié (pas de `print()`)
- Gestion explicite de toutes les exceptions (pas de `except Exception: pass`)
### Sécurité — règles absolues
- Les clés API ne doivent **jamais** apparaître dans les logs, réponses d'erreur, ou stack traces
- Le hash doit être SHA-256 avec sel (utiliser `hashlib.pbkdf2_hmac` ou `passlib`)
- Les messages d'erreur d'authentification doivent être génériques (ne pas indiquer si la clé existe ou si c'est le scope qui manque — sauf pour HTTP 403 vs 401)
- Valider les entrées avec Pydantic sur tous les endpoints de création/modification
### Migrations Alembic
- Une migration par changement de schéma
- Inclure les `upgrade()` et `downgrade()` complets
- Tester la migration sur une base vierge avant de la considérer terminée
### Résultat attendu
À la fin de la Phase 1, la commande suivante doit réussir sans erreur :
```bash
pytest tests/ -v --cov=app --cov-report=term-missing
```
Et le serveur doit démarrer sans erreur avec :
```bash
python run.py
```
## Résumé des livrables attendus
| # | Fichiers créés ou modifiés | Validation |
|---|---|---|
| 1.1 | `app/models/client.py`, `app/dependencies/auth.py`, `app/routers/auth.py`, `app/config.py`, `requirements.txt` | `pytest tests/test_auth.py` |
| 1.2 | `app/models/image.py`, `app/services/storage.py`, `app/routers/images.py`, `app/routers/ai.py`, `alembic/versions/xxx_add_clients.py` | `pytest tests/test_isolation.py` |
| 1.3 | `app/middleware/rate_limit.py`, `app/routers/images.py`, `app/routers/ai.py`, `app/config.py` | `pytest tests/test_rate_limit.py` |
| 1.4 | `tests/conftest.py`, `tests/test_auth.py`, `tests/test_isolation.py`, `tests/test_rate_limit.py` | `pytest tests/ -v --cov=app` |
**Résultat final de la Phase 1 :** hub sécurisé, données strictement isolées par client, déployable en production.