Files
MadJax dd36bbc425 Ajout de l'authentification (compte unique, JWT, reset par email)
Application mono-utilisateur : inscription unique, connexion par JWT (12 h),
protection de toutes les routes de données, réinitialisation de mot de passe
par email avec repli de journalisation, onboarding réorganisé en deux étapes
(inscription → connexion → profil + véhicule).

Backend (SchotzIadKmTracker.Api) :
- Entités Compte (singleton) et ResetPasswordToken + migration AjoutAuthentification.
- Hachage PasswordHasher, JwtService (HS256, durée configurable), IEmailService
  (MailKit + repli LoggingEmailService selon Smtp:Host).
- AuthService (register 409 si compte existant, login 401 générique, forgot-password
  non-divulgation, reset à usage unique + expiration), AuthController (5 endpoints
  publics), DTOs + validateurs FluentValidation.
- Sécurité pipeline : JwtBearer + filtre d'autorisation global, UseAuthentication
  avant UseAuthorization ; /health et /api/auth/* publics.

Frontend (schotziad-km-tracker, zoneless + signals) :
- AuthService (localStorage), authInterceptor (Bearer + purge/redirection 401),
  authGuard, écrans Login/Register/ForgotPassword/ResetPassword, routes publiques
  et protection [authGuard, profilConfigureGuard].

Configuration : .env.example et docker-compose.yml (JWT__SecretKey, durées, Smtp__*).

Tests : 54 tests backend (propriétés FsCheck P1–P14, exemples, intégration
WebApplicationFactory) + tests frontend (AuthService, authGuard, authInterceptor,
ResetPasswordComponent). Suite complète au vert.

Spec : .kiro/specs/authentification (requirements, design, tasks).
2026-09-14 09:07:08 +02:00

16 KiB
Raw Permalink Blame History

Implementation Plan : Authentification

Overview

Ce plan découpe l'ajout de l'authentification en tâches de codage incrémentales, chacune s'appuyant sur les précédentes et se terminant par un câblage complet (pas de code orphelin). Le backend (SchotzIadKmTracker.Api, ASP.NET Core .NET 8, EF Core + SQLite) est construit couche par couche — modèle → options → hachage → JWT → email → service métier → controller → sécurité pipeline — puis le frontend Angular zoneless (signals) — service → interceptor/garde → écrans → routes — et enfin la configuration/déploiement. Les tests de propriétés (FsCheck.Xunit) et d'exemples/intégration (xUnit + WebApplicationFactory) sont des sous-tâches placées au plus près du code qu'elles valident.

Tasks

  • 1. Backend — Modèle de données et persistance

    • 1.1 Créer les entités Compte et ResetPasswordToken

      • Créer Models/Entities/Compte.cs (singleton Id = 1 non généré, Email, MotDePasseHash, CreatedAt, UpdatedAt)
      • Créer Models/Entities/ResetPasswordToken.cs (Id, TokenHash, Expiration, UsedAt?, CreatedAt)
      • Requirements: 1.5, 6.1, 7.2, 8.1
    • 1.2 Déclarer les DbSet et la configuration OnModelCreating

      • Ajouter DbSet<Compte> et DbSet<ResetPasswordToken> dans Data/AppDbContext.cs
      • Configurer Compte : HasKey, Id ValueGeneratedNever(), index unique UX_Comptes_Email sur Email
      • Configurer ResetPasswordToken : index unique UX_ResetTokens_TokenHash, index IX_ResetTokens_Expiration
      • Requirements: 1.1, 1.2, 6.1
    • 1.3 Générer la migration EF AjoutAuthentification

      • Exécuter dotnet ef migrations add AjoutAuthentification pour créer les tables Comptes et ResetPasswordTokens avec leurs index
      • Vérifier que la migration est appliquée par le MigrateAsync() existant de Program.cs (aucune modification du démarrage requise)
      • Requirements: 1.1, 4.4
  • 2. Backend — Options de configuration

    • 2.1 Créer les classes d'options et les lier à la configuration

      • Créer Configuration/JwtOptions.cs (SecretKey, Issuer, Audience, ExpirationMinutes = 720, SectionName = "Jwt")
      • Créer Configuration/AuthOptions.cs (ResetTokenLifetimeMinutes = 60, FrontendBaseUrl, SectionName = "Auth")
      • Créer Configuration/SmtpOptions.cs (Host, Port = 587, User, Password, From, SectionName = "Smtp")
      • Enregistrer les trois via builder.Services.Configure<...>(...GetSection(...)) dans Program.cs
      • Requirements: 5.5, 6.3, 6.7
    • * 2.2 Écrire un test de liaison des options

      • Vérifier que JwtOptions, AuthOptions, SmtpOptions se lient correctement depuis la configuration (démarrage)
      • Requirements: 5.5, 6.3, 6.7
  • 3. Backend — Hachage des mots de passe

    • 3.1 Intégrer PasswordHasher<Compte> en DI

      • Enregistrer IPasswordHasher<Compte> -> PasswordHasher<Compte> en singleton dans Program.cs
      • Requirements: 1.5, 8.1, 8.3
    • * 3.2 Écrire le test de propriété du hachage

      • Property 1 : Round-trip du hachage de mot de passe
      • Validates: Requirements 1.5, 8.1, 8.3
  • 4. Backend — Service JWT

    • 4.1 Implémenter IJwtService / JwtService

      • Créer Services/IJwtService.cs et Services/JwtService.cs
      • GenererJeton(Compte) : émission HS256 avec claims sub, email, jti, expiration = maintenant + JwtOptions.ExpirationMinutes ; aucune donnée sensible (jamais le hash)
      • Enregistrer IJwtService -> JwtService (scoped) dans Program.cs
      • Requirements: 2.1, 2.4, 5.1, 5.4, 5.5
    • * 4.2 Écrire le test de propriété émission/validation et claims

      • Property 2 : Émission/validation JWT et préservation des claims
      • Validates: Requirements 2.1, 2.4, 5.1, 5.4, 5.5
    • * 4.3 Écrire le test de propriété jeton expiré

      • Property 3 : Un jeton JWT expiré est rejeté
      • Validates: Requirements 4.3, 5.2
    • * 4.4 Écrire le test de propriété signature invalide

      • Property 4 : Une signature JWT invalide est rejetée
      • Validates: Requirements 5.3, 4.2
  • 5. Backend — Service email

    • 5.1 Implémenter IEmailService et ses deux implémentations

      • Créer Services/IEmailService.cs (EnvoyerLienResetAsync(destinataire, lienReset, ct))
      • Créer Services/SmtpEmailService.cs (MailKit, configuré par SmtpOptions, email HTML/texte contenant le lien)
      • Créer Services/LoggingEmailService.cs (repli : journalise le lien via ILogger niveau Information)
      • Ajouter la dépendance NuGet MailKit au projet API
      • Requirements: 6.4, 6.5, 6.7
    • 5.2 Câbler la sélection DI selon Smtp__Host

      • Dans Program.cs, enregistrer LoggingEmailService si Smtp:Host est vide, sinon SmtpEmailService
      • Requirements: 6.4, 6.5
    • * 5.3 Écrire le test de sélection DI de IEmailService

      • Smtp__Host vide ⇒ LoggingEmailService ; renseigné ⇒ SmtpEmailService
      • Requirements: 6.4, 6.5
  • 6. Backend — DTOs, validateurs et service d'authentification

    • 6.1 Créer les DTOs d'authentification et les validateurs FluentValidation

      • Créer Models/DTOs/AuthDtos.cs (records RegisterDto, LoginDto, LoginResultDto, ForgotPasswordDto, ResetPasswordDto, CompteExisteDto) — aucun n'expose MotDePasseHash
      • Créer les validateurs FluentValidation : email syntaxiquement valide, MotDePasse/NouveauMotDePasse >= 8 caractères
      • Requirements: 1.3, 1.4, 7.5, 8.2
    • 6.2 Implémenter IAuthService / AuthService

      • Créer Services/IAuthService.cs et Services/AuthService.cs + exceptions métier (compte existant, identifiants invalides, lien invalide/expiré)
      • CompteExisteAsync ; RegisterAsync (compte unique, sinon exception -> 409, hachage du mdp) ; LoginAsync (vérifie via VerifyHashedPassword, retourne JWT, sinon exception générique -> 401)
      • ForgotPasswordAsync (réponse générique ; si email connu : crée un ResetPasswordToken haché avec expiration = now + ResetTokenLifetimeMinutes, construit le lien {FrontendBaseUrl}/reset-password?token=..., appelle IEmailService) ; ResetPasswordAsync (jeton valide/non expiré/non utilisé, remplace le mdp haché, marque UsedAt)
      • Enregistrer IAuthService -> AuthService (scoped) dans Program.cs
      • Requirements: 1.1, 1.2, 2.1, 2.2, 6.1, 6.2, 6.4, 6.5, 6.6, 7.1, 7.2, 7.3, 7.4, 8.3, 9.1
    • * 6.3 Écrire le test de propriété inscription crée le compte

      • Property 5 : L'inscription crée le compte lorsqu'aucun compte n'existe
      • Validates: Requirements 1.1
    • * 6.4 Écrire le test de propriété inscription rejetée si compte existant

      • Property 6 : L'inscription est rejetée lorsqu'un compte existe déjà
      • Validates: Requirements 1.2
    • * 6.5 Écrire le test de propriété mot de passe trop court à l'inscription

      • Property 7 : Un mot de passe trop court est refusé à l'inscription
      • Validates: Requirements 1.3
    • * 6.6 Écrire le test de propriété connexion (émission/rejet)

      • Property 8 : La connexion émet un jeton sur identifiants corrects et rejette les identifiants invalides
      • Validates: Requirements 2.1, 2.2
    • * 6.7 Écrire le test de propriété non-divulgation forgot-password

      • Property 9 : Non-divulgation lors de la demande de réinitialisation
      • Validates: Requirements 6.6
    • * 6.8 Écrire le test de propriété expiration du jeton de reset

      • Property 10 : Expiration du jeton de reset paramétrée
      • Validates: Requirements 6.1, 6.2, 6.3
    • * 6.9 Écrire le test de propriété réinitialisation valide remplace le mot de passe

      • Property 11 : Une réinitialisation valide remplace le mot de passe
      • Validates: Requirements 7.1
    • * 6.10 Écrire le test de propriété jeton de reset à usage unique

      • Property 12 : Un jeton de reset est à usage unique
      • Validates: Requirements 7.2, 7.4
    • * 6.11 Écrire le test de propriété jeton de reset expiré ou inconnu

      • Property 13 : Un jeton de reset expiré ou inconnu est rejeté
      • Validates: Requirements 7.3, 7.4
    • * 6.12 Écrire le test de propriété nouveau mot de passe trop court à la réinitialisation

      • Property 14 : Un nouveau mot de passe trop court est refusé à la réinitialisation
      • Validates: Requirements 7.5
    • * 6.13 Écrire les tests d'exemples de validation et d'exclusion du hash

      • Emails invalides ("a@", "abc", "@x", chaîne vide) ⇒ 400 ; email valide accepté
      • Les corps de réponse d'auth ne contiennent aucun champ de hash
      • Requirements: 1.4, 8.2
  • 7. Backend — Controller d'authentification

    • 7.1 Implémenter AuthController avec les 5 endpoints
      • Créer Controllers/AuthController.cs ([ApiController], [Route("api/auth")], [AllowAnonymous])
      • POST /register, POST /login, POST /forgot-password, POST /reset-password, GET /compte-existe
      • Mapper les exceptions métier en codes HTTP via ProblemDetails : 409 (compte existant), 401 (identifiants génériques), 400 (mdp court / email invalide / lien invalide ou expiré), 200 générique (forgot-password)
      • Requirements: 1.2, 1.3, 1.4, 2.1, 2.2, 6.6, 7.3, 7.4, 7.5, 9.1
  • 8. Backend — Sécurité du pipeline

    • 8.1 Configurer l'authentification JWT et l'autorisation globale

      • Ajouter AddAuthentication(JwtBearer).AddJwtBearer(...) avec TokenValidationParameters (issuer, audience, lifetime, signing key depuis JwtOptions, ClockSkew 30s)
      • Ajouter le filtre global AuthorizeFilter(RequireAuthenticatedUser()) sur AddControllers
      • Insérer app.UseAuthentication() avant app.UseAuthorization()
      • Conserver [AllowAnonymous] sur AuthController et laisser /health public
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 5.1, 5.2, 5.3
    • * 8.2 Écrire le test d'intégration de protection des routes

      • Via WebApplicationFactory<Program> : routes de données (/api/trajets, /api/profil, /api/vehicules, /api/bareme, /api/resumes, exports PDF, recalcul) ⇒ 401 sans jeton, 2xx avec jeton valide
      • /api/auth/* et /health accessibles sans jeton
      • Requirements: 4.1, 4.2, 4.4, 4.5
    • * 8.3 Écrire le test d'intégration bout en bout

      • register → login → appel protégé avec le token ; puis forgot → reset → login avec le nouveau mot de passe
      • Requirements: 1.1, 2.1, 7.1
  • 9. Checkpoint backend

    • Ensure all tests pass, ask the user if questions arise.
  • 10. Frontend — Service, modèles, interceptor et garde

    • 10.1 Créer les modèles TS et AuthService (signals + localStorage)

      • Créer les types LoginDto, RegisterDto, LoginResult, ResetPasswordDto
      • Créer core/services/auth.service.ts : signal _token initialisé depuis localStorage, estAuthentifie (computed), login/register/forgotPassword/resetPassword/compteExiste/logout/getToken, base ${environment.apiUrl}/auth
      • Requirements: 2.3, 3.1, 3.2, 9.1
    • 10.2 Implémenter authInterceptor et l'enregistrer avant timeoutInterceptor

      • Créer core/interceptors/auth.interceptor.ts : ajoute Authorization: Bearer aux requêtes vers environment.apiUrl (hors endpoints publics d'auth et appels externes) ; sur 401, purge le token et redirige vers /login
      • Dans app.config.ts : withInterceptors([authInterceptor, timeoutInterceptor])
      • Requirements: 4.7
    • 10.3 Implémenter authGuard

      • Créer core/guards/auth.guard.ts (CanActivateFn) : si non authentifié -> UrlTree vers /login, sinon true
      • Requirements: 4.6, 9.3
    • * 10.4 Écrire les tests de service, interceptor et garde

      • AuthService stocke/supprime le token dans localStorage ; authGuard redirige un non-authentifié vers /login ; authInterceptor purge le token et redirige sur 401
      • Requirements: 2.3, 3.1, 4.6, 4.7, 9.3
  • 11. Frontend — Écrans standalone (signals)

    • 11.1 Implémenter LoginComponent (/login)

      • Formulaire email + mot de passe ; sur succès, stocke le token puis navigue vers la cible protégée ; état async via signals
      • Requirements: 2.1, 2.3
    • 11.2 Implémenter RegisterComponent (/register, Etape 1)

      • Formulaire email + mot de passe ; sur succès redirige vers /login ; état async via signals
      • Requirements: 1.6, 9.1, 9.2
    • 11.3 Implémenter ForgotPasswordComponent (/forgot-password)

      • Saisie email ; affiche un message générique quel que soit le résultat ; état async via signals
      • Requirements: 6.6
    • 11.4 Implémenter ResetPasswordComponent (/reset-password)

      • Lit le token depuis les query params de l'URL, saisit le nouveau mot de passe ; sur succès redirige vers /login ; état async via signals
      • Requirements: 7.6
    • * 11.5 Écrire le test de lecture du token par ResetPasswordComponent

      • Vérifie la lecture du token depuis l'URL
      • Requirements: 7.6
  • 12. Frontend — Routes et onboarding en deux étapes

    • 12.1 Mettre à jour app.routes.ts
      • Ajouter les routes publiques /login, /register, /forgot-password, /reset-password (sans garde)
      • Protéger les routes de données par [authGuard, profilConfigureGuard] et /onboarding (Etape 2) + /profil + /vehicules par [authGuard]
      • Conserver l'OnboardingComponent existant comme écran de l'Etape 2 (comportement inchangé)
      • Requirements: 4.6, 9.3, 9.4, 9.5, 9.6
  • 13. Configuration et déploiement

    • 13.1 Mettre à jour .env.example et docker-compose.yml

      • .env.example : documenter JWT_SECRET_KEY, JWT_EXPIRATION_MINUTES=720, AUTH_RESET_TOKEN_LIFETIME_MINUTES=60, AUTH_FRONTEND_BASE_URL, SMTP_HOST/PORT/USER/PASSWORD/FROM (sans valeurs secrètes réelles)
      • docker-compose.yml (service backend) : mapper JWT__SecretKey (:? requis), JWT__ExpirationMinutes, Auth__ResetTokenLifetimeMinutes, Auth__FrontendBaseUrl, Smtp__*
      • Requirements: 5.5, 6.3, 6.7
    • 13.2 Vérifier les builds

      • Exécuter dotnet build sur le backend et corriger les erreurs de compilation
      • Exécuter ng build sur le frontend schotziad-km-tracker et corriger les erreurs de compilation
      • Requirements: 4.4, 9.6
  • 14. Checkpoint final

    • Ensure all tests pass, ask the user if questions arise.

Notes

  • Les tâches marquées * sont des tests optionnels et peuvent être ignorées pour un MVP plus rapide ; elles ne sont pas implémentées automatiquement.
  • Chaque tâche référence des exigences précises pour la traçabilité, et chaque test de propriété référence sa propriété de conception (P1–P14).
  • Les tests de propriétés (FsCheck.Xunit, ≥ 100 itérations) valident les propriétés universelles ; les tests d'exemples et d'intégration (xUnit + WebApplicationFactory) couvrent la validation d'entrée, la sélection DI et la protection des routes.
  • Les checkpoints assurent une validation incrémentale (backend d'abord, puis l'ensemble).
  • Le séquençage respecte le contexte projet : entités → migration EF appliquée au démarrage → options → services → controller → sécurité pipeline (ordre UseAuthentication avant UseAuthorization), puis frontend zoneless (signals) et déploiement Docker.

Task Dependency Graph

{
  "waves": [
    { "id": 0, "tasks": ["1.1", "2.1", "10.1"] },
    { "id": 1, "tasks": ["1.2", "2.2", "3.1", "5.1", "10.2", "10.3", "11.1", "11.2", "11.3", "11.4"] },
    { "id": 2, "tasks": ["1.3", "3.2", "4.1", "5.2", "6.1", "10.4", "11.5", "12.1"] },
    { "id": 3, "tasks": ["4.2", "4.3", "4.4", "5.3", "6.2", "13.1"] },
    { "id": 4, "tasks": ["6.3", "6.4", "6.5", "6.6", "6.7", "6.8", "6.9", "6.10", "6.11", "6.12", "6.13", "7.1"] },
    { "id": 5, "tasks": ["8.1"] },
    { "id": 6, "tasks": ["8.2", "8.3", "13.2"] }
  ]
}