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>
277 lines
13 KiB
Markdown
277 lines
13 KiB
Markdown
# Story 5.1: Admin Login
|
|
|
|
Status: done
|
|
|
|
## Story
|
|
|
|
En tant qu'**Admin**,
|
|
Je veux **me connecter à un dashboard admin sécurisé**,
|
|
de sorte que **je puisse gérer le système et les utilisateurs**.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Admin Login Page**: Page `/admin/login` avec formulaire de connexion par mot de passe unique
|
|
2. **Secure Authentication**: Authentification via POST `/api/v1/admin/login` avec mot de passe admin
|
|
3. **Token Storage**: Stockage du token admin dans localStorage (clé: `admin_token`)
|
|
4. **Redirect After Login**: Redirection vers `/admin` après connexion réussie
|
|
5. **Error Handling**: Affichage d'erreur claire si mot de passe incorrect (401)
|
|
6. **Loading States**: Spinner pendant la requête de connexion
|
|
7. **Password Toggle**: Bouton pour afficher/masquer le mot de passe
|
|
8. **Non-Admin Rejection**: Si user connecté via JWT standard mais non admin → 403 Forbidden
|
|
9. **Session Persistence**: Token persiste entre les navigations (localStorage)
|
|
10. **Admin Route Protection**: Middleware côté client pour protéger les routes `/admin/*`
|
|
11. **Colocation**: Tous les fichiers dans `frontend/src/app/admin/login/`
|
|
|
|
## Tasks / Subtasks
|
|
|
|
- [x] **Task 1: Créer la structure de dossiers** (AC: #11)
|
|
- [x] 1.1 Créer `frontend/src/app/admin/login/page.tsx` (fichier spécial: minuscules!)
|
|
- [x] 1.2 Créer `frontend/src/app/admin/login/types.ts`
|
|
- [x] 1.3 Créer `frontend/src/app/admin/login/useAdminLogin.ts`
|
|
|
|
- [x] **Task 2: Définir les types TypeScript** (AC: #2)
|
|
- [x] 2.1 `AdminLoginRequest`: `{ password: string }`
|
|
- [x] 2.2 `AdminLoginResponse`: `{ status: string, access_token: string, token_type: string, expires_in: number, message: string }`
|
|
|
|
- [x] **Task 3: Créer useAdminLogin.ts hook** (AC: #2, #3, #4, #5, #6)
|
|
- [x] 3.1 `login(password)`: POST `/api/v1/admin/login`
|
|
- [x] 3.2 Stocker `access_token` dans localStorage sous clé `admin_token`
|
|
- [x] 3.3 Gestion erreurs: 401 → "Mot de passe incorrect", timeout → message spécifique
|
|
- [x] 3.4 Loading state: `isLoading`
|
|
- [x] 3.5 Redirection vers `/admin` après succès
|
|
|
|
- [x] **Task 4: Créer la page de login** (AC: #1, #5, #6, #7)
|
|
- [x] 4.1 Layout centré avec gradient background (réutiliser style existant)
|
|
- [x] 4.2 Formulaire avec input password et bouton show/hide
|
|
- [x] 4.3 Icône Shield pour branding admin
|
|
- [x] 4.4 Bouton submit avec loading spinner
|
|
- [x] 4.5 Affichage erreur en rouge avec icône AlertCircle
|
|
|
|
- [x] **Task 5: Créer middleware de protection admin** (AC: #8, #10)
|
|
- [x] 5.1 Vérifier présence de `admin_token` dans localStorage
|
|
- [x] 5.2 Si absent → rediriger vers `/admin/login?redirect=/admin/...`
|
|
- [x] 5.3 Optionnel: Vérifier validité du token via GET `/api/v1/admin/verify`
|
|
|
|
- [x] **Task 6: Intégration avec layout admin existant** (AC: #4, #10)
|
|
- [x] 6.1 Modifier `frontend/src/app/admin/layout.tsx` pour ajouter protection
|
|
- [x] 6.2 Vérifier que le layout admin existant fonctionne avec auth
|
|
|
|
- [x] **Task 7: Tests et validation** (AC: Tous)
|
|
- [x] 7.1 `npm run build` → 0 erreurs TypeScript
|
|
- [x] 7.2 Tester connexion avec bon mot de passe → redirection OK
|
|
- [x] 7.3 Tester connexion avec mauvais mot de passe → erreur affichée
|
|
- [x] 7.4 Tester accès direct à `/admin` sans token → redirection login
|
|
- [x] 7.5 Tester persistance du token après refresh page
|
|
|
|
## Dev Notes
|
|
|
|
### 🏗️ Stack Technique
|
|
|
|
| Technologie | Version |
|
|
|-------------|---------|
|
|
| Next.js | 16.0.6 (App Router) |
|
|
| React | 19.2.0 |
|
|
| Tailwind CSS | configuré |
|
|
| shadcn/ui | Card, Button, Input (optionnel) |
|
|
| Lucide React | Shield, Lock, Eye, EyeOff, AlertCircle |
|
|
|
|
### 📁 Structure Cible (Colocation Pattern)
|
|
|
|
```
|
|
frontend/src/app/admin/
|
|
├── layout.tsx # Layout admin avec protection (existe déjà)
|
|
├── page.tsx # Dashboard admin principal (existe déjà)
|
|
└── login/
|
|
├── page.tsx # Page de connexion admin
|
|
├── types.ts # TypeScript interfaces
|
|
└── useAdminLogin.ts # Hook pour login
|
|
```
|
|
|
|
**⚠️ Règle absolue (architecture.md):**
|
|
```
|
|
🚨 FICHIERS SPÉCIAUX: page.tsx, layout.tsx → TOUJOURS minuscules
|
|
🚨 COLOCATION: Components/hooks/types dans le dossier de leur page
|
|
```
|
|
|
|
### 🔗 API Endpoint Backend (Déjà implémenté)
|
|
|
|
| Endpoint | Méthode | Request | Response |
|
|
|----------|---------|---------|----------|
|
|
| `/api/v1/admin/login` | POST | `{password: string}` | 200: `{status, access_token, token_type, expires_in, message}` |
|
|
| `/api/v1/admin/verify` | GET | Header: `Authorization: Bearer <token>` | 200: `{status: "valid", authenticated: true}` |
|
|
| `/api/v1/admin/logout` | POST | Header: `Authorization: Bearer <token>` | 200: `{status: "success", message: "Logged out"}` |
|
|
|
|
**Codes erreur:**
|
|
- 401: `Invalid credentials` → mot de passe incorrect
|
|
- 503: `Admin authentication not configured` → ADMIN_PASSWORD non configuré
|
|
|
|
### 📊 API Response Example
|
|
|
|
**POST /api/v1/admin/login (200):**
|
|
```json
|
|
{
|
|
"status": "success",
|
|
"access_token": "abc123...xyz789",
|
|
"token_type": "bearer",
|
|
"expires_in": 86400,
|
|
"message": "Login successful"
|
|
}
|
|
```
|
|
|
|
**POST /api/v1/admin/login (401):**
|
|
```json
|
|
{
|
|
"detail": "Invalid credentials"
|
|
}
|
|
```
|
|
|
|
### 🎨 Patterns UI à Réutiliser
|
|
|
|
**Depuis `frontend/src/app/(app)/admin/login/page.tsx` (existant mais dans mauvais dossier):**
|
|
- Layout gradient: `bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900`
|
|
- Card avec `bg-black/30 backdrop-blur-xl rounded-2xl border border-white/10`
|
|
- Input avec icône Lock à gauche, Eye/EyeOff à droite
|
|
- Button loading avec spinner
|
|
- Error display avec AlertCircle icon
|
|
|
|
**⚠️ IMPORTANT: Ce fichier existe déjà dans `frontend/src/app/(app)/admin/login/page.tsx`. Il faut le DÉPLACER vers `frontend/src/app/admin/login/page.tsx` pour respecter la structure colocation.**
|
|
|
|
### 🔄 State Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ /admin/login │
|
|
│ ┌─────────────────────────────────────────────────────┐│
|
|
│ │ useAdminLogin() hook ││
|
|
│ │ ├─ login(password): Promise<void> ││
|
|
│ │ ├─ isLoading: boolean ││
|
|
│ │ └─ error: string | null ││
|
|
│ └─────────────────────────────────────────────────────┘│
|
|
│ │
|
|
│ Form submit → login(password) │
|
|
│ ├─ Success → localStorage.setItem('admin_token', token)│
|
|
│ │ → router.push('/admin') ││
|
|
│ └─ Error → set error message │
|
|
└─────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ /admin/* (protected) │
|
|
│ ┌─────────────────────────────────────────────────────┐│
|
|
│ │ AdminLayout ││
|
|
│ │ ├─ Check localStorage.getItem('admin_token') ││
|
|
│ │ ├─ If null → redirect to /admin/login ││
|
|
│ │ └─ If present → render children ││
|
|
│ └─────────────────────────────────────────────────────┘│
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### ⚠️ Points d'Attention Critiques
|
|
|
|
1. **Dossier existant**: Un fichier `frontend/src/app/(app)/admin/login/page.tsx` existe déjà. Il faut le MIGRER vers `frontend/src/app/admin/login/page.tsx` pour respecter la structure sans route group `(app)`.
|
|
|
|
2. **Token Storage**: Utiliser la clé `admin_token` (pas `token` qui est pour les users normaux). Le store Zustand `useTranslationStore` a déjà une méthode `setAdminToken()`.
|
|
|
|
3. **Double Auth System**:
|
|
- Users normaux: JWT via `/api/v1/auth/login` → stocké dans `token`
|
|
- Admin: Bearer token via `/api/v1/admin/login` → stocké dans `admin_token`
|
|
- Ces deux systèmes sont INDÉPENDANTS
|
|
|
|
4. **Middleware Protection**: Le layout admin doit vérifier le token avant de rendre les enfants. Si pas de token → redirect vers login.
|
|
|
|
5. **Environment Variables**: Le backend nécessite `ADMIN_PASSWORD` ou `ADMIN_PASSWORD_HASH` pour fonctionner. Si non configuré, le backend retourne 503.
|
|
|
|
6. **Suspense Boundary**: Le composant utilise `useSearchParams` → doit être wrappé dans `<Suspense>`.
|
|
|
|
### 📋 Checklist de Validation Avant Dev
|
|
|
|
- [x] Backend API `/api/v1/admin/login` est fonctionnel
|
|
- [x] Variable d'environnement `ADMIN_PASSWORD` configurée dans `.env`
|
|
- [x] Zustand store `useTranslationStore` a `setAdminToken()` disponible
|
|
- [x] Le fichier `frontend/src/app/(app)/admin/login/page.tsx` existe (à migrer)
|
|
- [x] Layout admin `frontend/src/app/admin/layout.tsx` existe
|
|
|
|
### 🚀 Migration Steps
|
|
|
|
L'implémentation existe partiellement dans `frontend/src/app/(app)/admin/login/page.tsx`. Voici les étapes de migration:
|
|
|
|
1. **Déplacer le fichier** vers `frontend/src/app/admin/login/page.tsx`
|
|
2. **Extraire le hook** dans `useAdminLogin.ts`
|
|
3. **Extraire les types** dans `types.ts`
|
|
4. **Ajouter protection** dans le layout admin pour redirect si pas authentifié
|
|
|
|
### References
|
|
|
|
- [Source: _bmad-output/planning-artifacts/epics.md#Story-5.1] — Story requirements
|
|
- [Source: _bmad-output/planning-artifacts/architecture.md#Frontend-Architecture] — Colocation pattern
|
|
- [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] — Format réponse API
|
|
- [Source: routes/admin_routes.py#L140-154] — Backend login implementation
|
|
- [Source: frontend/src/app/(app)/admin/login/page.tsx] — Implémentation existante à migrer
|
|
- [Source: frontend/src/lib/store.ts] — Zustand store avec setAdminToken()
|
|
- [Source: office-translator-landing-page/app/admin/layout.tsx] — Layout admin à adapter
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
zai-anthropic/glm-5
|
|
|
|
### Debug Log References
|
|
|
|
None
|
|
|
|
### Completion Notes List
|
|
|
|
- 2026-02-24: Completed implementation of Admin Login feature
|
|
- Created types.ts with AdminLoginRequest, AdminLoginResponse, AdminLoginState interfaces
|
|
- Created useAdminLogin.ts hook with login function, error handling, loading states
|
|
- Created login page with gradient background, password toggle, error display, loading spinner
|
|
- Created admin layout.tsx with route protection (redirects to login if no token)
|
|
- Created admin page.tsx that redirects to login (placeholder for future dashboard)
|
|
- Removed old admin files from (app) route group to avoid route conflicts
|
|
- Build passes with 0 TypeScript errors
|
|
|
|
- 2026-02-24: Code Review Fixes Applied
|
|
- **[HIGH] Security**: Replaced SHA256 with bcrypt for admin password hashing in routes/admin_routes.py
|
|
- **[HIGH] Security**: Added server-side token verification via /api/v1/admin/verify in AdminLayout
|
|
- **[MEDIUM]**: Updated admin page.tsx to show dashboard placeholder with logout button
|
|
- **[MEDIUM]**: Added logout function to useAdminLogin hook that invalidates server token
|
|
- **[LOW]**: Removed API_BASE URL exposure in error messages for security
|
|
|
|
### File List
|
|
|
|
**Created files:**
|
|
- frontend/src/app/admin/login/page.tsx
|
|
- frontend/src/app/admin/login/types.ts
|
|
- frontend/src/app/admin/login/useAdminLogin.ts
|
|
- frontend/src/app/admin/layout.tsx
|
|
- frontend/src/app/admin/page.tsx
|
|
|
|
**Deleted files:**
|
|
- frontend/src/app/(app)/admin/login/page.tsx
|
|
- frontend/src/app/(app)/admin/page.tsx
|
|
|
|
## Change Log
|
|
|
|
- 2026-02-24: Story created - ready for development
|
|
- 2026-02-24: Implementation complete - all ACs satisfied, ready for review
|
|
- 2026-02-24: Code review complete - 2 HIGH, 3 MEDIUM, 1 LOW issues fixed
|
|
|
|
## Senior Developer Review (AI)
|
|
|
|
**Reviewer:** AI Code Review (zai-anthropic/glm-5)
|
|
**Date:** 2026-02-24
|
|
|
|
### Issues Found & Fixed
|
|
|
|
| Severity | Issue | File | Status |
|
|
|----------|-------|------|--------|
|
|
| HIGH | SHA256 password hashing (should use bcrypt) | routes/admin_routes.py:53-54 | ✅ Fixed |
|
|
| HIGH | No server-side token verification in AdminLayout | frontend/src/app/admin/layout.tsx | ✅ Fixed |
|
|
| MEDIUM | Admin page redirects instead of showing dashboard | frontend/src/app/admin/page.tsx | ✅ Fixed |
|
|
| MEDIUM | No logout function to invalidate server token | useAdminLogin.ts | ✅ Fixed |
|
|
| LOW | API_BASE URL exposed in error messages | useAdminLogin.ts:40,55 | ✅ Fixed |
|
|
|
|
### Review Outcome
|
|
|
|
**APPROVED** - All issues fixed. Story marked as done.
|