# 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 ` | 200: `{status: "valid", authenticated: true}` | | `/api/v1/admin/logout` | POST | Header: `Authorization: Bearer ` | 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 ││ │ │ ├─ 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 ``. ### 📋 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.