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>
This commit is contained in:
Sepehr Ramezani
2026-04-25 15:01:47 +02:00
parent 2ba4fedfc8
commit 26bd096a06
1178 changed files with 136435 additions and 3047 deletions

View File

@@ -0,0 +1,175 @@
# Story 1.5: Refresh Token
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a **logged-in user**,
I want **to refresh my access token using my refresh token**,
so that **I can stay logged in without re-entering credentials**.
## Acceptance Criteria
1. **AC1: Endpoint refresh**`POST /api/v1/auth/refresh` existe et accepte un corps JSON `{"refresh_token": "<string>"}`.
2. **AC2: Nouvel access token** — Pour un refresh token valide, la réponse est 200 avec `{"data": {"access_token": "...", "refresh_token": "...", "token_type": "bearer"}, "meta": {}}`. Le nouvel access_token a une durée de vie de 15 minutes (NFR6).
3. **AC3: Refresh token invalide/expiré** — Si le refresh token est invalide, expiré ou révoqué, la réponse est 401 avec `{"error": "TOKEN_EXPIRED", "message": "Token invalide ou expiré"}` (ou message équivalent en français).
4. **AC4: Corps manquant ou invalide** — Requête sans corps ou sans champ `refresh_token` valide retourne 400 avec `{"error": "INVALID_REQUEST", "message": "..."}`.
## Tasks / Subtasks
- [x] **Task 1: Endpoint POST /api/v1/auth/refresh** (AC: 1, 2, 3, 4)
- [x] 1.1 Dans `routes/auth_routes.py`, ajouter une route `@router_v1.post("/refresh")` dans le bloc `router_v1`.
- [x] 1.2 Parser le corps JSON pour extraire `refresh_token` (obligatoire). Si absent ou invalide → 400 INVALID_REQUEST.
- [x] 1.3 Appeler `verify_token(refresh_token)`. Si `None` (expiré, invalide, révoqué) → 401 TOKEN_EXPIRED.
- [x] 1.4 Vérifier `payload.get("type") == "refresh"`. Sinon → 401 TOKEN_EXPIRED.
- [x] 1.5 Récupérer l'utilisateur via `get_user_by_id(payload["sub"])`. Si absent → 401 TOKEN_EXPIRED.
- [x] 1.6 Générer nouvel access_token (15 min) et nouvel refresh_token (7 jours) via `create_access_token` et `create_refresh_token`.
- [x] 1.7 Retourner 200 avec format `{"data": {"access_token", "refresh_token", "token_type": "bearer"}, "meta": {}}`.
- [x] **Task 2: Tests** (AC: 14)
- [x] 2.1 Créer ou étendre `tests/test_auth_refresh.py` (ou fichier dédié refresh v1).
- [x] 2.2 Test : refresh avec token valide → 200 + nouvel access_token et refresh_token.
- [x] 2.3 Test : refresh avec token expiré → 401 TOKEN_EXPIRED.
- [x] 2.4 Test : refresh avec token révoqué (après logout) → 401.
- [x] 2.5 Test : refresh sans corps ou sans refresh_token → 400 INVALID_REQUEST.
- [x] 2.6 Test : nouvel access_token a bien 15 min dexpiry (optionnel, vérification payload JWT).
## Dev Notes
### Contexte brownfield
Le projet a déjà :
- **API v1 auth** : `router_v1` dans `routes/auth_routes.py` avec prefix `/api/v1/auth` ; routes existantes : `/register`, `/login`, `/logout`. Il **ny a pas** dendpoint `/refresh` sous v1 (le refresh existant est sous lancien routeur `/api/auth/refresh`). La story demande dajouter **uniquement** `POST /api/v1/auth/refresh` pour alignement avec register/login/logout.
- **Auth service** : `create_access_token`, `create_refresh_token`, `verify_token`, `get_user_by_id`, blocklist JTI (révocation) dans `services/auth_service.py`. Expiry access 15 min, refresh 7 jours déjà en place pour login v1.
- **Story 1.4** : Logout révoque le JTI du refresh token ; `verify_token()` retourne déjà `None` pour un token révoqué. Aucun changement de schéma ou de blocklist nécessaire pour la story 1.5.
### Architecture Compliance
- **Format succès (200)** — [Source: architecture.md]
```json
{
"data": {
"access_token": "<jwt>",
"refresh_token": "<jwt>",
"token_type": "bearer"
},
"meta": {}
}
```
- **Format erreur (401)** — Pas de champ `data` ; code `TOKEN_EXPIRED` pour token invalide/expiré/révoqué.
```json
{
"error": "TOKEN_EXPIRED",
"message": "Token invalide ou expiré"
}
```
- **Format erreur (400)** — Corps manquant ou invalide.
```json
{
"error": "INVALID_REQUEST",
"message": "Refresh token requis"
}
```
### Patterns à réutiliser (Story 1.3 / 1.4)
- Même style dendpoint que `login_v1` : `request.json()` pour le corps, `JSONResponse` avec `data`/`meta`, messages derreur en français.
- Utiliser `create_access_token(user.id, tier=user.plan.value, expires_delta=timedelta(minutes=15))` et `create_refresh_token(user.id, expires_delta=timedelta(days=7))` comme dans `login_v1`.
- Vérifier `verify_token(refresh_token)` puis `payload.get("type") == "refresh"` et `get_user_by_id(payload["sub"])`.
### Fichiers à modifier / créer
| Fichier | Action |
|---------|--------|
| `routes/auth_routes.py` | Ajouter `@router_v1.post("/refresh")` et logique (parser body, verify_token, type refresh, get_user_by_id, create tokens, retour 200). |
| `tests/test_auth_refresh.py` (ou équivalent) | Créer/étendre avec tests v1 pour POST /api/v1/auth/refresh (valide, expiré, révoqué, corps manquant). |
### Fichiers à ne pas modifier
- `services/auth_service.py` — Pas de changement nécessaire (verify_token, create_access_token, create_refresh_token et blocklist déjà en place).
- `database/` — Aucune migration.
- Endpoints legacy `/api/auth/*` — Hors scope ; on nexpose que `/api/v1/auth/refresh`.
### Références
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.5]
- [Source: _bmad-output/planning-artifacts/architecture.md#Authentication & Security]
- [Source: _bmad-output/planning-artifacts/architecture.md#API Response Formats]
- [Source: _bmad-output/implementation-artifacts/1-4-logout-utilisateur.md — Patterns JTI / verify_token]
- [Source: _bmad-output/implementation-artifacts/1-3-login-utilisateur-jwt.md — Format login v1]
---
## Developer Context (Guardrails)
### Technical requirements
- **Backend** : FastAPI, Python 3.11+. Endpoint sous `router_v1` (prefix `/api/v1/auth`).
- **JWT** : PyJWT ; access 15 min, refresh 7 jours ; `verify_token()` gère déjà la blocklist JTI (Story 1.4).
- **Réponses** : Succès avec `data` + `meta` ; erreurs sans `data`, avec `error` et `message` (snake_case, français).
### Architecture compliance
- Conventions API : JSON snake_case, format succès/erreur comme ci-dessus.
- Auth : Pas de stockage de tokens en clair ; refresh token utilisé une seule fois par échange (rotations optionnelles ultérieures).
### Library / framework
- **FastAPI** : `Request`, `JSONResponse` ; parser body avec `await request.json()` et gestion dexception pour 400.
- **PyJWT** : Déjà utilisé dans `auth_service` ; pas de nouvelle dépendance.
### File structure
- Route : `routes/auth_routes.py` — ajout dune seule fonction `refresh_v1` (ou nom cohérent) et `@router_v1.post("/refresh")`.
- Tests : `tests/test_auth_refresh.py` (nouveau fichier recommandé pour v1) ou section dédiée dans un fichier de tests auth existant.
### Testing requirements
- Tests dintégration (client FastAPI) pour POST /api/v1/auth/refresh : cas valide (200 + structure data), token expiré (401), token révoqué après logout (401), corps manquant (400). Réutiliser fixtures utilisateur/tokens des tests login/logout si possible.
---
## Previous Story Intelligence (1-4 Logout)
- **Fichiers modifiés** : `services/auth_service.py` (JTI, blocklist, `revoke_token_jti`, `is_token_revoked`, vérification dans `verify_token`), `routes/auth_routes.py` (endpoint logout v1), `tests/test_auth_logout.py`.
- **Patterns** : Réponse 200 avec `{"data": {"message": "..."}, "meta": {}}` ; 401 avec `TOKEN_MISSING` / `TOKEN_INVALID` ; extraction Bearer et vérification via `verify_token`. Pour refresh, réutiliser `verify_token` (qui retourne déjà `None` pour token révoqué) et ne pas dupliquer la logique de blocklist.
- **Tests** : Fixture avec réinitialisation de la blocklist (`_revoked_jtis`) pour éviter fuites entre tests ; test AC2 dans 1-4 appelle déjà `POST /api/auth/refresh` avec refresh token révoqué et attend 401. Pour 1.5, ajouter des tests explicites pour **POST /api/v1/auth/refresh**.
---
## Project Context Reference
- **Structure** : Backend à la racine (ou sous backend/) : `main.py`, `routes/`, `services/`, `database/`, `tests/`. Pas de changement de structure requis.
- **Montage des routes** : `router_v1` est monté dans `main.py` ; sassurer que le nouvel endpoint est bien exposé sous `/api/v1/auth/refresh`.
---
## Story Completion Status
- **Status** : done
- **Note** : Code review (adversarial) : correctifs appliqués (guard body dict + test body non-objet, statut aligné). Fichiers à committer : routes/auth_routes.py, tests/test_auth_refresh.py.
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Debug Log References
### Completion Notes List
- Implémentation de `POST /api/v1/auth/refresh` dans `routes/auth_routes.py` (fonction `refresh_v1`) : parsing du corps JSON, validation du `refresh_token`, vérification via `verify_token` et `type == "refresh"`, récupération utilisateur, génération de nouveaux tokens (access 15 min, refresh 7 jours), réponse 200 avec format data/meta. Erreurs 400 INVALID_REQUEST (corps manquant ou refresh_token absent/invalide), 401 TOKEN_EXPIRED (token expiré, invalide, révoqué ou utilisateur absent).
- Tests dans `tests/test_auth_refresh.py` : 13 tests couvrant AC1AC4 (succès 200 + structure, tokens différents, expiry 15 min ; token expiré 401 ; token révoqué après logout 401 ; corps manquant / champ absent / chaîne vide / type invalide → 400). Suite complète : 81 tests passent, aucune régression.
- [Code review 2026-02-20] Correctifs : (1) `refresh_v1` — guard `isinstance(body, dict)` pour éviter 500 sur corps JSON non-objet ; (2) test `test_non_object_json_body_returns_400` ajouté ; (3) statut story aligné (review → done).
### File List
- routes/auth_routes.py (modifié — ajout endpoint refresh_v1)
- tests/test_auth_refresh.py (créé)
## Change Log
- 2026-02-20 : Implémentation story 1.5 — endpoint POST /api/v1/auth/refresh et tests (AC1AC4).