using GestionIntegral.Api.Dtos; using GestionIntegral.Api.Services.Usuarios; using Microsoft.AspNetCore.Authorization; // Para [Authorize] using Microsoft.AspNetCore.Mvc; using System.Security.Claims; // Para leer claims del token namespace GestionIntegral.Api.Controllers.Usuarios { [Route("api/[controller]")] // Ruta base: /api/auth [ApiController] public class AuthController : ControllerBase { private readonly IAuthService _authService; private readonly ILogger _logger; // Para logging public AuthController(IAuthService authService, ILogger logger) { _authService = authService; _logger = logger; } [HttpPost("login")] // Ruta: POST /api/auth/login [ProducesResponseType(typeof(LoginResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task Login([FromBody] LoginRequestDto loginRequest) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { var loginResponse = await _authService.LoginAsync(loginRequest); if (loginResponse == null) { _logger.LogWarning("Login failed for user {Username}", loginRequest.Username); // Devolver Unauthorized genérico para no dar pistas sobre si el usuario existe o no return Unauthorized(new { message = "Usuario o contraseña inválidos." }); } _logger.LogInformation("User {Username} logged in successfully.", loginRequest.Username); return Ok(loginResponse); } catch (Exception ex) { _logger.LogError(ex, "Error during login for user {Username}", loginRequest.Username); // No exponer detalles del error al cliente return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Ocurrió un error interno durante el inicio de sesión." }); } } [HttpPost("change-password")] [Authorize] // <-- Solo usuarios autenticados pueden cambiar su clave [ProducesResponseType(StatusCodes.Status204NoContent)] // Éxito sin contenido [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] // Si el token es inválido [ProducesResponseType(StatusCodes.Status404NotFound)] // Si el usuario del token no existe public async Task ChangePassword([FromBody] ChangePasswordRequestDto changePasswordRequest) { if (!ModelState.IsValid) { // El [ApiController] y el [FromBody] ya validan DTOs (Required, StringLength, Compare) // y devuelven 400 automáticamente si falla. return BadRequest(ModelState); } // Obtener el ID del usuario desde el token JWT var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"); // "sub" es el claim estándar para ID if (!int.TryParse(userIdString, out int userId)) { _logger.LogWarning("ChangePassword failed: Could not parse UserId from token."); // Esto no debería pasar si el token es válido y generado por nosotros return Unauthorized(new { message = "Token de usuario inválido." }); } try { var success = await _authService.ChangePasswordAsync(userId, changePasswordRequest); if (!success) { // AuthService ya loggeó la razón específica (usuario no encontrado, clave actual inválida, etc.) // Devolvemos un BadRequest genérico para no dar pistas return BadRequest(new { message = "No se pudo cambiar la contraseña. Verifique la contraseña actual." }); } // Éxito return NoContent(); // Código 204: Éxito, sin contenido que devolver } catch (Exception ex) { _logger.LogError(ex, "Error during password change for user ID {UserId}", userId); return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Ocurrió un error interno al cambiar la contraseña." }); } } } }