- Implement tests for database generator to ensure proper session handling. - Create tests for EXIF extraction and conversion functions. - Add tests for image-related endpoints, ensuring proper data retrieval and isolation between clients. - Develop tests for OCR functionality, including language detection and text extraction. - Introduce tests for the image processing pipeline, covering success and failure scenarios. - Validate rate limiting functionality and ensure independent counters for different clients. - Implement scraper tests to verify HTML content fetching and error handling. - Add unit tests for various services, including storage and filename generation. - Establish worker entry point for ARQ to handle background image processing tasks.
13 KiB
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.
<project_context>
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. </project_context>
## 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é :
-
app/models/client.py— Nouveau modèle SQLAlchemyAPIClient:id: UUID, primary keyname: 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éesplan: Enum (free,standard,premium)is_active: Boolean, default Truecreated_at/updated_at: DateTime
-
app/dependencies/__init__.py+app/dependencies/auth.py— Dépendances FastAPI :verify_api_key(authorization: str = Header(...))→ retourne l'APIClientauthentifié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 401si clé invalide ou client inactif - Lever
HTTP 403si scope manquant
-
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, scopeadmin)GET /auth/clients/{id}— Détail d'un clientPATCH /auth/clients/{id}— Modifier un client (scopes, plan, is_active)POST /auth/clients/{id}/rotate-key— Régénérer la clé APIDELETE /auth/clients/{id}— Désactiver (soft delete)
-
Scopes à définir :
images:read— lire les images et métadonnéesimages:write— uploader et modifier des imagesimages:delete— supprimer des imagesai:use— utiliser les endpoints AI (résumé URL, génération tâches)admin— gestion des clients (réservé à un super-client)
-
app/config.py— Ajouter :ADMIN_API_KEY: clé du super-client admin (depuis .env)JWT_SECRET_KEYetJWT_ALGORITHMpour 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-joseetpasslib(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é :
-
app/models/image.py— Modifier le modèleImageexistant :- Ajouter
client_id = Column(UUID, ForeignKey("api_clients.id"), nullable=False, index=True) - Ajouter la relation
client = relationship("APIClient", back_populates="images")
- Ajouter
-
app/models/client.py— Ajouter la relation inverse :images = relationship("Image", back_populates="client", cascade="all, delete-orphan")
-
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_uploaddoit accepterclient_iden paramètre
- Les fichiers doivent être stockés dans
-
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
- Injecter
-
app/routers/ai.py— Même injection + scopeai:use -
alembic/versions/— Créer une migration Alembic pour :- Créer la table
api_clients - Ajouter la colonne
client_idà la tableimages - Créer un client "default" avec toutes les permissions pour la migration des données existantes
- Créer la table
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é :
-
Ajouter
slowapiau requirements.txt -
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/heurestandard: 100 uploads/heure, 200 requêtes AI/heurepremium: 500 uploads/heure, 1000 requêtes AI/heure
- Retourner
HTTP 429avec headerRetry-Aftersi limite atteinte
- Identifier les requêtes par
-
app/routers/images.py— Appliquer les décorateurs rate limit sur :POST /images/upload→ limiter par planPOST /images/{id}/reprocess→ limiter par plan
-
app/routers/ai.py— Appliquer sur :POST /ai/summarize→ limiter par planPOST /ai/draft-task→ limiter par plan
-
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éé :
-
tests/conftest.py— Fixtures pytest :test_db: base SQLite in-memory pour les testsclient_aetclient_b: deux clients API de test avec des clés distinctesauth_headers_aetauth_headers_b: headers d'authentification correspondantsasync_client:httpx.AsyncClientconfiguré pour les tests
-
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
-
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
-
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
- ✅ Dépassement de quota → HTTP 429 avec header
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
<execution_rules>
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
- Lire les fichiers existants concernés avant de les modifier
- Écrire le code complet (pas de
# TODOni de# ...) - Mettre à jour
requirements.txtsi de nouvelles dépendances sont ajoutées - Créer la migration Alembic si le modèle de données change
- Lancer
pytest tests/ -vet 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_hmacoupasslib) - 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()etdowngrade()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 :
pytest tests/ -v --cov=app --cov-report=term-missing
Et le serveur doit démarrer sans erreur avec :
python run.py
</execution_rules>
<deliverable_summary>
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. </deliverable_summary>