Files
office_translator/_bmad-output/implementation-artifacts/5-1-admin-login.md
Sepehr Ramezani 26bd096a06 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>
2026-04-25 15:01:47 +02:00

13 KiB

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

  • Task 1: Créer la structure de dossiers (AC: #11)

    • 1.1 Créer frontend/src/app/admin/login/page.tsx (fichier spécial: minuscules!)
    • 1.2 Créer frontend/src/app/admin/login/types.ts
    • 1.3 Créer frontend/src/app/admin/login/useAdminLogin.ts
  • Task 2: Définir les types TypeScript (AC: #2)

    • 2.1 AdminLoginRequest: { password: string }
    • 2.2 AdminLoginResponse: { status: string, access_token: string, token_type: string, expires_in: number, message: string }
  • Task 3: Créer useAdminLogin.ts hook (AC: #2, #3, #4, #5, #6)

    • 3.1 login(password): POST /api/v1/admin/login
    • 3.2 Stocker access_token dans localStorage sous clé admin_token
    • 3.3 Gestion erreurs: 401 → "Mot de passe incorrect", timeout → message spécifique
    • 3.4 Loading state: isLoading
    • 3.5 Redirection vers /admin après succès
  • Task 4: Créer la page de login (AC: #1, #5, #6, #7)

    • 4.1 Layout centré avec gradient background (réutiliser style existant)
    • 4.2 Formulaire avec input password et bouton show/hide
    • 4.3 Icône Shield pour branding admin
    • 4.4 Bouton submit avec loading spinner
    • 4.5 Affichage erreur en rouge avec icône AlertCircle
  • Task 5: Créer middleware de protection admin (AC: #8, #10)

    • 5.1 Vérifier présence de admin_token dans localStorage
    • 5.2 Si absent → rediriger vers /admin/login?redirect=/admin/...
    • 5.3 Optionnel: Vérifier validité du token via GET /api/v1/admin/verify
  • Task 6: Intégration avec layout admin existant (AC: #4, #10)

    • 6.1 Modifier frontend/src/app/admin/layout.tsx pour ajouter protection
    • 6.2 Vérifier que le layout admin existant fonctionne avec auth
  • Task 7: Tests et validation (AC: Tous)

    • 7.1 npm run build → 0 erreurs TypeScript
    • 7.2 Tester connexion avec bon mot de passe → redirection OK
    • 7.3 Tester connexion avec mauvais mot de passe → erreur affichée
    • 7.4 Tester accès direct à /admin sans token → redirection login
    • 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):

{
  "status": "success",
  "access_token": "abc123...xyz789",
  "token_type": "bearer",
  "expires_in": 86400,
  "message": "Login successful"
}

POST /api/v1/admin/login (401):

{
  "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

  • Backend API /api/v1/admin/login est fonctionnel
  • Variable d'environnement ADMIN_PASSWORD configurée dans .env
  • Zustand store useTranslationStore a setAdminToken() disponible
  • Le fichier frontend/src/app/(app)/admin/login/page.tsx existe (à migrer)
  • 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.