using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using phronCare.API.Models; using Services.Models; using Services.Interfaces; using phronCare.API.Models.Authentication.Login; using phronCare.API.Models.Authentication.SingUp; using Google.Authenticator; using QRCoder; using System.ComponentModel.DataAnnotations; namespace phronCare.API.Controllers { [Route("api/[controller]")] [ApiController] public class AuthenticationController(UserManager userManager, RoleManager roleManager, IEmailService emailService, IConfiguration configuration, SignInManager signInManager, TwoFactorAuthenticator twoFactorAuthenticator) : ControllerBase { private const int JWT_TOKEN_VALIDITY_HOURS = 48; #region Declaration Section private readonly UserManager userManager = userManager; private readonly RoleManager roleManager = roleManager; private readonly SignInManager signInManager = signInManager; private readonly TwoFactorAuthenticator authenticator = twoFactorAuthenticator; private readonly IEmailService emailService = emailService; private readonly IConfiguration configuration = configuration; #endregion [HttpPost] [Route("generate-qr-code")] public async Task GenerateQRCodeAsync() { try { var user = await signInManager.GetTwoFactorAuthenticationUserAsync(); // Obtén el nombre de usuario actual if (user == null) { return BadRequest(new Response { Status = "Error", Message = "Usuario no autenticado." }); } var setupInfo = authenticator.GenerateSetupCode("MiApp", user.Email, user.Id, false, 10); // Genera el código QR como una imagen var qrCodeGenerator = new QRCodeGenerator(); var qrCodeData = qrCodeGenerator.CreateQrCode(setupInfo.ManualEntryKey, QRCodeGenerator.ECCLevel.Q); var qrCode = new PngByteQRCode(qrCodeData); // Convierte la imagen del código QR en bytes var qrCodeImage = qrCode.GetGraphic(10); // Ajusta el tamaño según tus necesidades var qrCodeImageData = Convert.ToBase64String(qrCodeImage); // Devuelve el userId (clave secreta) y la imagen del código QR en la respuesta return Ok(new AuthResponse { Status = "Success", Message = "Código QR generado satisfactoriamente.", ManualSetupKey = setupInfo.ManualEntryKey, // Proporciona esto al usuario para configuración manual en Google Authenticator QRCodeImage = qrCodeImageData // Devuelve la imagen del código QR }); } catch (Exception) { return StatusCode(StatusCodes.Status500InternalServerError, new AuthResponse { Status = "Error", Message = "Error al generar el código QR." }); } } [HttpPost] [Route("register")] public async Task Register([FromBody] RegisterUser registerUser) { //Chequeo Correo existente var userExist = await userManager.FindByEmailAsync(registerUser.EmailAddress); if (userExist != null) { return StatusCode(StatusCodes.Status409Conflict, "El correo ingresado ya esta en uso."); } userExist = await userManager.FindByNameAsync(registerUser.UserName); if (userExist != null) { return StatusCode(StatusCodes.Status409Conflict, "El usuario ingresado ya existe."); } IdentityUser user = new() { Email = registerUser.EmailAddress, SecurityStamp = Guid.NewGuid().ToString(), UserName = registerUser.UserName }; if (await roleManager.RoleExistsAsync(registerUser.Role)) { var result = await userManager.CreateAsync(user, registerUser.Password); if (!result.Succeeded) { return StatusCode(StatusCodes.Status500InternalServerError, $"Creacion de usuario fallida: {result}"); } await userManager.AddToRoleAsync(user, registerUser.Role); //Add Token to Verify the email var token = await userManager.GenerateEmailConfirmationTokenAsync(user); var confirmationLink = Url.Action(nameof(ConfirmEmail), "Authentication", new { token, email = user.Email }, Request.Scheme); var message = new Message([user.Email!], "phronCare - Link de verificacion", confirmationLink!); emailService.SendEmail(message); return StatusCode(StatusCodes.Status201Created, $"Usuario creado satisfactoriamente. Debe autenticar la cuenta generada desde la direccion de correo registrada: {user.Email}"); } else { return StatusCode(StatusCodes.Status500InternalServerError, "El rol no existe. Intentalo nuevamente!"); } } [HttpGet("confirmemail")] public async Task ConfirmEmail(string token, string email) { var user = await userManager.FindByEmailAsync(email); if (user != null) { var result = await userManager.ConfirmEmailAsync(user, token); if (result.Succeeded) { return StatusCode(StatusCodes.Status200OK, new Response { Status = "Success", Message = "Email verificado satisfactoriamente" }); } } return StatusCode(StatusCodes.Status409Conflict, new Response { Status = "Error", Message = "Este usuario no existe" }); } [HttpPost] [Route("login")] public async Task Login([FromBody] LoginModel loginModel) { await signInManager.SignOutAsync(); var user = await userManager.FindByNameAsync(loginModel.Username); if (user is null) { return Unauthorized("El nombre de usuario o contraseña son incorrectos"); }; var canSignIn = await signInManager.CanSignInAsync(user); if (!canSignIn) { string message = string.Empty; if (userManager.Options.SignIn.RequireConfirmedEmail && !(await userManager.IsEmailConfirmedAsync(user))) { message = "El usuario {" + user.UserName + "} no puede iniciar sesion sin tener el email confirmado."; } if (userManager.Options.SignIn.RequireConfirmedPhoneNumber && !(await userManager.IsPhoneNumberConfirmedAsync(user))) { message = "El usuario {" + user.UserName + "} no puede iniciar sesion sin tener confirmado el numero de telefono."; } return StatusCode(StatusCodes.Status401Unauthorized, message); } if (user != null && await userManager.CheckPasswordAsync(user, loginModel.Password)) { if (!user.TwoFactorEnabled) { return await GenerateAccess(user); } else { return StatusCode(StatusCodes.Status202Accepted, new Response { Status = "Success", Message = $"Debe ingresar el codigo desde la app Google Authenticator : {user.UserName}" }); }; } return Unauthorized("El nombre de usuario o contraseña son incorrectos"); } [HttpPost] [Route("login-2FA")] public async Task LoginWithOTP(string code, string username) { var user = await userManager.FindByNameAsync(username); if (user is not null) { string secretKey = user.Id; // Obtén la clave secreta del usuario desde tu base de datos bool isBase32 = false; // Define si la clave secreta está en formato Base32 bool validate = authenticator.ValidateTwoFactorPIN(secretKey, code, isBase32); if (validate) { return await GenerateAccess(user); } } return StatusCode(StatusCodes.Status401Unauthorized, new Response { Status = "Error", Message = "Código de verificación incorrecto." }); } [HttpPost] [AllowAnonymous] [Route("forgot-password")] public async Task ForgotPassword([Required]string email) { var user = await userManager.FindByEmailAsync(email); if (user != null) { var token = await userManager.GeneratePasswordResetTokenAsync(user); var forgotPasswordLink = Url.Action(nameof(ResetPassword), "Authentication", new { token, email = user.Email }, Request.Scheme); var message = new Message([user.Email!], "phronCare - Enlace de recuperacion de contraseña.", forgotPasswordLink!); emailService.SendEmail(message); return StatusCode(StatusCodes.Status200OK, $"La solicitud de cambio de contraseña se envio a: {user.Email} con éxito. Por favor verifique el enlace enviado a la casilla de correo."); } return StatusCode(StatusCodes.Status400BadRequest, "No se pudo enviar el enlace al correo, por favor intente nuevamente."); } [HttpGet("reset-password")] public async Task ResetPassword(string token, string email) { //var model =new ResetPassword {Token=token, Email = email}; var htmlContent = GetResetPasswordHtmlContent(email,token); var bytes = Encoding.UTF8.GetBytes(htmlContent); var stream = new MemoryStream(bytes); return File(stream, "text/html"); } private static string GetResetPasswordHtmlContent(string username, string token) { string htmlContent = @"phronCare - Notificación

phronCare XL: Notificación

" + username + "

Token

Tu solicitud de restablecimiento de contraseña ha sido confirmada

Para restablecer tu contraseña en phronCare, ingrese las credenciales proporcionadas.

"; return htmlContent; } [HttpPost] [AllowAnonymous] [Route("reset-password")] public async Task ResetPassword([FromBody] ResetPassword resetPassword) { var user = await userManager.FindByEmailAsync(resetPassword.Email); if (user != null) { var resetPassResult = await userManager.ResetPasswordAsync(user, resetPassword.Token, resetPassword.Password); if (!resetPassResult.Succeeded) { return StatusCode(StatusCodes.Status500InternalServerError, resetPassResult.Errors.First().Description); } return StatusCode(StatusCodes.Status200OK,$"La password ha sido cambiada correctamente."); } return StatusCode(StatusCodes.Status500InternalServerError,"No se pudo encontrar el usuario, por favor intente nuevamente."); } #region GenerateAccess private async Task GenerateAccess(IdentityUser? user) { var authClaims = new List { new(ClaimTypes.Name, user.UserName), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), }; var userRoles = await userManager.GetRolesAsync(user); foreach (var role in userRoles) { authClaims.Add(new Claim(ClaimTypes.Role, role)); } var jwtToken = GetToken(authClaims); /* Returning the User Session object */ var userSession = new UserSession { UserName = user.UserName, Role = userRoles.First(), Token = new JwtSecurityTokenHandler().WriteToken(jwtToken), ExpiresIn = (int)jwtToken.ValidTo.Subtract(DateTime.Now).TotalSeconds, ExpiryTimeStamp = jwtToken.ValidTo }; return Ok(userSession); /* Returning simple jwt object */ //return Ok(new //{ // token = new JwtSecurityTokenHandler().WriteToken(jwtToken), // expiration = jwtToken.ValidTo //}); } public class UserSession { public string UserName { get; set; } public string Token { get; set; } public string Role { get; set; } public int ExpiresIn { get; set; } public DateTime ExpiryTimeStamp { get; set; } } #endregion #region GenerateToken private JwtSecurityToken GetToken(List authClaims) { var authSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["JWT:Secret"])); var token = new JwtSecurityToken( issuer: configuration["JWT:ValidIssuer"], audience: configuration["JWT:ValidAudience"], expires: DateTime.Now.AddHours(JWT_TOKEN_VALIDITY_HOURS), claims: authClaims, signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256Signature) ); return token; } #endregion } }