Files
SchotzIAD/frontend/src/app/features/auth/login/login.component.ts
T
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

101 lines
3.5 KiB
TypeScript

import { CommonModule } from '@angular/common';
import { Component, OnInit, inject, signal } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http';
import { Router, RouterLink } from '@angular/router';
import { AuthService } from '../../../core/services/auth.service';
import { NotificationService } from '../../../core/services/notification.service';
import { LoginDto } from '../../../core/models';
/**
* Écran de connexion (`/login`) — Etape de session de l'authentification.
*
* Formulaire réactif (email + mot de passe). Sur succès, `AuthService.login`
* stocke le JWT puis le composant navigue vers `/consultation` ; le routing et
* la `profilConfigureGuard` redirigeront vers l'onboarding si le profil n'est
* pas encore configuré. Sur 401, un message générique « Identifiants invalides. »
* est affiché (Exigence 2.2).
*
* Contrainte zoneless : tout état async est exposé via des signals
* (`soumissionEnCours`, `erreur`, `compteExiste`).
*
* _Requirements: 2.1, 2.3_
*/
@Component({
selector: 'app-login',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, RouterLink],
templateUrl: './login.component.html',
styleUrl: './login.component.scss'
})
export class LoginComponent implements OnInit {
private readonly fb = inject(FormBuilder);
private readonly authService = inject(AuthService);
private readonly router = inject(Router);
private readonly notification = inject(NotificationService);
form!: FormGroup;
/** Vrai pendant l'appel de connexion (désactive le bouton). */
readonly soumissionEnCours = signal(false);
/** Message d'erreur courant, ou null si aucune erreur. */
readonly erreur = signal<string | null>(null);
/**
* Indique si un compte existe déjà. Le lien d'inscription n'est affiché que
* lorsqu'aucun compte n'existe (application mono-utilisateur).
*/
readonly compteExiste = signal(true);
ngOnInit(): void {
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
motDePasse: ['', [Validators.required, Validators.minLength(8)]]
});
this.authService.compteExiste().subscribe({
next: (resultat) => this.compteExiste.set(resultat.existe),
// En cas d'échec, on conserve la valeur par défaut (masque l'inscription).
error: () => this.compteExiste.set(true)
});
}
/** Tente la connexion avec les identifiants saisis. */
seConnecter(): void {
if (this.form.invalid || this.soumissionEnCours()) {
this.form.markAllAsTouched();
return;
}
const valeur = this.form.getRawValue();
const dto: LoginDto = {
email: (valeur.email ?? '').trim(),
motDePasse: valeur.motDePasse ?? ''
};
this.erreur.set(null);
this.soumissionEnCours.set(true);
this.authService.login(dto).subscribe({
next: () => {
this.soumissionEnCours.set(false);
// La cible protégée oriente ensuite vers l'onboarding si nécessaire.
this.router.navigate(['/consultation']);
},
error: (err: HttpErrorResponse) => {
this.soumissionEnCours.set(false);
if (err.status === 401) {
const message = 'Identifiants invalides.';
this.erreur.set(message);
this.notification.error(message);
} else {
const message = 'La connexion a échoué. Veuillez réessayer.';
this.erreur.set(message);
this.notification.error(message);
}
}
});
}
}