using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SchotzIadKmTracker.Api.Models.DTOs; using SchotzIadKmTracker.Api.Services; using SchotzIadKmTracker.Api.Services.Exceptions; namespace SchotzIadKmTracker.Api.Controllers; /// /// Endpoints publics d'authentification de l'application mono-utilisateur : /// inscription, connexion, demande et application de la réinitialisation du /// mot de passe, ainsi que la vérification de l'existence du compte. /// Toutes les routes sont anonymes () : elles /// restent accessibles même une fois l'autorisation globale activée (Exigence 9.1). /// Les erreurs sont renvoyées au format ProblemDetails, comme les autres controllers. /// [ApiController] [Route("api/auth")] [AllowAnonymous] public class AuthController : ControllerBase { private readonly IAuthService _authService; public AuthController(IAuthService authService) { _authService = authService; } /// /// Inscrit le compte unique de l'application. Les erreurs de validation /// (email invalide, mot de passe trop court) sont renvoyées automatiquement /// en 400 par FluentValidation (Exigences 1.3, 1.4). Un compte déjà existant /// est rejeté en 409 (Exigence 1.2). /// [HttpPost("register")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task Register( [FromBody] RegisterDto dto, CancellationToken cancellationToken) { try { await _authService.RegisterAsync(dto, cancellationToken); return Ok(); } catch (CompteDejaExistantException ex) { return Problem(detail: ex.Message, statusCode: StatusCodes.Status409Conflict); } } /// /// Authentifie le compte et retourne le jeton JWT accompagné de sa date /// d'expiration. En cas d'identifiants invalides, renvoie un 401 avec un /// message générique afin de ne pas distinguer email inconnu et mot de passe /// erroné (Exigences 2.1, 2.2). /// [HttpPost("login")] [ProducesResponseType(typeof(LoginResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> Login( [FromBody] LoginDto dto, CancellationToken cancellationToken) { try { var resultat = await _authService.LoginAsync(dto, cancellationToken); return Ok(resultat); } catch (IdentifiantsInvalidesException) { return Problem(detail: "Identifiants invalides.", statusCode: StatusCodes.Status401Unauthorized); } } /// /// Traite une demande de réinitialisation de mot de passe. Par principe de /// non-divulgation, renvoie toujours 200 avec la même réponse, que l'email /// corresponde ou non à un compte (Exigence 6.6). La logique de non-divulgation /// est portée par le service ; les erreurs techniques réelles remontent. /// [HttpPost("forgot-password")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task ForgotPassword( [FromBody] ForgotPasswordDto dto, CancellationToken cancellationToken) { await _authService.ForgotPasswordAsync(dto, cancellationToken); return Ok(); } /// /// Réinitialise le mot de passe à partir d'un jeton de réinitialisation. /// Un jeton expiré (Exigence 7.3) ou invalide/déjà utilisé (Exigence 7.4) /// est rejeté en 400. Les erreurs de validation du nouveau mot de passe sont /// renvoyées automatiquement en 400 par FluentValidation (Exigence 7.5). /// [HttpPost("reset-password")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task ResetPassword( [FromBody] ResetPasswordDto dto, CancellationToken cancellationToken) { try { await _authService.ResetPasswordAsync(dto, cancellationToken); return Ok(); } catch (LienResetExpireException) { return Problem(detail: "Le lien de réinitialisation a expiré.", statusCode: StatusCodes.Status400BadRequest); } catch (LienResetInvalideException) { return Problem(detail: "Le lien de réinitialisation est invalide.", statusCode: StatusCodes.Status400BadRequest); } } /// /// Indique si un compte existe déjà, afin d'orienter le frontend entre /// inscription et connexion (Exigences 1.2, 9.1). /// [HttpGet("compte-existe")] [ProducesResponseType(typeof(CompteExisteDto), StatusCodes.Status200OK)] public async Task> CompteExiste(CancellationToken cancellationToken) { return Ok(new CompteExisteDto(await _authService.CompteExisteAsync(cancellationToken))); } }