2025-01-24 19:17:26 -03:00
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 < IdentityUser > userManager , RoleManager < IdentityRole > roleManager , IEmailService emailService , IConfiguration configuration , SignInManager < IdentityUser > signInManager , TwoFactorAuthenticator twoFactorAuthenticator ) : ControllerBase
{
private const int JWT_TOKEN_VALIDITY_HOURS = 48 ;
#region Declaration Section
private readonly UserManager < IdentityUser > userManager = userManager ;
private readonly RoleManager < IdentityRole > roleManager = roleManager ;
private readonly SignInManager < IdentityUser > signInManager = signInManager ;
private readonly TwoFactorAuthenticator authenticator = twoFactorAuthenticator ;
private readonly IEmailService emailService = emailService ;
private readonly IConfiguration configuration = configuration ;
#endregion
2025-03-22 01:19:48 -03:00
#region Segurity Endpoints
2025-01-24 19:17:26 -03:00
[HttpPost]
[Route("generate-qr-code")]
public async Task < IActionResult > 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 < IActionResult > 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 < IActionResult > 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 < IActionResult > 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 < IActionResult > 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 < IActionResult > 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 < IActionResult > 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 = @"<!DOCTYPE html><html><head><meta name=viewport content=""width=device-width""><meta http-equiv=Content-Type content=""text/html; charset=UTF-8""><title>phronCare - Notificación</title><style>body{background:white;font-family:'Helvetica',sans-serif;color:#fff}.container{background-image:linear-gradient(45deg,rgb(5,39,103) 0%,#3a0647 70%);border-radius:50px;color:#fff;padding:20px;margin:30px auto;width:500px;text-align:center;}h1{font-size:20px}h2{font-size:16px;color:yellow;overflow-wrap:break-word;}p{font-size:16px}.form-control{width: 90%;padding: .375rem .75rem;height: 110px;max-height: 150px;border: 1px solid #ced4da;border-radius: .25rem;font-size: 1rem;}textarea{resize: none;}</style></head><body><table class=container><tr><td><h1>phronCare XL: Notificación</h1><h2>" + username + "</h2><h2>Token</h2><textarea class='form-control' readonly style='max-height: 150px;'>" + token + "</textarea><h3>Tu solicitud de restablecimiento de contraseña ha sido confirmada</h3><p>Para restablecer tu contraseña en phronCare, ingrese las credenciales proporcionadas.</p></td></tr></table></body></html>" ;
return htmlContent ;
}
[HttpPost]
[AllowAnonymous]
[Route("reset-password")]
public async Task < IActionResult > 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." ) ;
}
2025-03-22 01:19:48 -03:00
#endregion
2025-01-24 19:17:26 -03:00
#region GenerateAccess
private async Task < IActionResult > GenerateAccess ( IdentityUser ? user )
{
var authClaims = new List < Claim >
{
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 < Claim > authClaims )
{
2025-04-28 19:33:43 -03:00
var secret = configuration [ "JWT:Secret" ] ;
if ( string . IsNullOrWhiteSpace ( secret ) )
throw new InvalidOperationException ( "El Secret no está configurado." ) ;
// Convertir explícitamente a bytes
var keyBytes = Encoding . UTF8 . GetBytes ( secret ) ;
var authSigningKey = new SymmetricSecurityKey ( keyBytes ) ;
var credentials = new SigningCredentials ( authSigningKey , SecurityAlgorithms . HmacSha256 ) ;
2025-01-24 19:17:26 -03:00
var token = new JwtSecurityToken (
issuer : configuration [ "JWT:ValidIssuer" ] ,
audience : configuration [ "JWT:ValidAudience" ] ,
2025-04-28 19:33:43 -03:00
expires : DateTime . UtcNow . AddHours ( JWT_TOKEN_VALIDITY_HOURS ) ,
2025-01-24 19:17:26 -03:00
claims : authClaims ,
2025-04-28 19:33:43 -03:00
signingCredentials : credentials
2025-01-24 19:17:26 -03:00
) ;
2025-04-28 19:33:43 -03:00
2025-01-24 19:17:26 -03:00
return token ;
}
2025-04-28 19:33:43 -03:00
//private JwtSecurityToken GetToken(List<Claim> 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;
//}
2025-01-24 19:17:26 -03:00
#endregion
}
2025-03-22 01:19:48 -03:00
}