using System.Net; using System.Security.Cryptography; using System.Text; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Dtos.Authentication; using Marco.Pms.Model.Dtos.Util; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Utilities; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace MarcoBMS.Services.Controllers { [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly UserManager _userManager; private readonly UserHelper _userHelper; private readonly ApplicationDbContext _context; private readonly JwtSettings _jwtSettings; private readonly RefreshTokenService _refreshTokenService; private readonly IEmailSender _emailSender; private readonly IConfiguration _configuration; private readonly EmployeeHelper _employeeHelper; private readonly ILoggingService _logger; //string tenentId = "1"; public AuthController(UserManager userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService, IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger) { _userManager = userManager; _jwtSettings = jwtSettings; _refreshTokenService = refreshTokenService; _emailSender = emailSender; _configuration = configuration; _employeeHelper = employeeHelper; _context = context; _userHelper = userHelper; _logger = logger; } [HttpPost("login")] public async Task Login([FromBody] LoginDto loginDto) { var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); if (user != null) { if (!user.IsActive) { return BadRequest(ApiResponse.ErrorResponse("User is In Active", "User is In Active", 400)); } if (!user.EmailConfirmed) { return BadRequest(ApiResponse.ErrorResponse("Your email is not verified, Please verify your email", "Your email is not verified, Please verify your email", 400)); } if (await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty)) { Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); //var refreshToken = GenerateRefreshToken(); if (user.UserName == null) return NotFound(ApiResponse.ErrorResponse("UserName Not found", "UserName Not found", 404)); ; var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings); var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings); return Ok(ApiResponse.SuccessResponse(new { token = token, refreshToken = refreshToken }, "User logged in successfully.", 200)); } } return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401)); } [HttpPost("login-mobile")] public async Task LoginMobile([FromBody] LoginDto loginDto) { // Validate input DTO if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password)) { return BadRequest(ApiResponse.ErrorResponse("Username or password is missing.", "Invalid request", 400)); } // Find user by email or phone number var user = await _context.ApplicationUsers .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); // If user not found, return unauthorized if (user == null) { return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401)); } // Check if user is inactive if (!user.IsActive) { return BadRequest(ApiResponse.ErrorResponse("User is inactive", "User is inactive", 400)); } // Check if user email is not confirmed if (!user.EmailConfirmed) { return BadRequest(ApiResponse.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400)); } // Validate password using ASP.NET Identity var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password); if (!isPasswordValid) { return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid credentials", 401)); } // Check if username is missing if (string.IsNullOrWhiteSpace(user.UserName)) { return NotFound(ApiResponse.ErrorResponse("UserName not found", "Username is missing", 404)); } // Get employee information for tenant context var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); if (emp == null) { return NotFound(ApiResponse.ErrorResponse("Employee not found", "Employee details missing", 404)); } // Generate JWT token var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings); // Generate Refresh Token and store in DB var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings); // Generate MPIN Token (custom short-term token) var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId); // Combine all tokens in response var responseData = new { token, refreshToken, mpinToken }; // Return success response return Ok(ApiResponse.SuccessResponse(responseData, "User logged in successfully.", 200)); } [HttpPost("logout")] public async Task Logout([FromBody] LogoutDto logoutDto) { if (string.IsNullOrEmpty(logoutDto.RefreshToken)) { return BadRequest(ApiResponse.ErrorResponse("Refresh token is required", "Refresh token is required", 400)); } try { // Revoke the refresh token bool isRevoked = await _refreshTokenService.RevokeRefreshTokenAsync(logoutDto.RefreshToken); if (!isRevoked) return Unauthorized(ApiResponse.ErrorResponse("Invalid or expired refresh token", "Invalid or expired refresh token", 401)); // Optional: Blacklist the access token (JWT) string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); if (!string.IsNullOrEmpty(jwtToken)) { await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken); } return Ok(ApiResponse.SuccessResponse(new { }, "Logged out successfully", 200)); } catch (Exception ex) { // _logger.LogError(ex, "Error during logout"); return BadRequest(ApiResponse.ErrorResponse("Internal server error", ex.Message, 500)); } } [HttpPost("refresh-token")] public async Task RefreshToken([FromBody] RefreshTokenDto refreshTokenDto) { var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken); if (refreshToken == null || refreshToken.ExpiryDate < DateTime.UtcNow) { return Unauthorized(ApiResponse.ErrorResponse("Invalid or expired refresh token.", "Invalid or expired refresh token.", 401)); } // Mark token as used await _refreshTokenService.MarkRefreshTokenAsUsed(refreshToken); // Generate new JWT token and refresh token var user = await _userManager.FindByIdAsync(refreshToken.UserId ?? string.Empty); if (user == null) return BadRequest(ApiResponse.ErrorResponse("Invalid request.", "Invalid request.", 400)); Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); if (user.UserName == null) return NotFound(ApiResponse.ErrorResponse("UserName Not found", "UserName Not found", 404)); var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings); var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings); return Ok(ApiResponse.SuccessResponse(new { token = newJwtToken, refreshToken = newRefreshToken }, "User refresh token generated successfully.", 200)); } [HttpPost("forgot-password")] public async Task ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto) { var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email); if (user == null) return NotFound(ApiResponse.ErrorResponse("User not found.", "User not found.", 404)); /* SEND USER REGISTRATION MAIL*/ var token = await _userManager.GeneratePasswordResetTokenAsync(user); var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}"; if (user.Email == null) return NotFound(ApiResponse.ErrorResponse("Email Not found", "Email Not found", 404)); await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink); return Ok(ApiResponse.SuccessResponse(true, "Password reset link sent.", 200)); } [HttpPost("reset-password")] public async Task ResetPassword([FromBody] ResetPasswordDto model) { var user = await _userManager.FindByEmailAsync(model.Email ?? string.Empty); if (user == null) return BadRequest(ApiResponse.ErrorResponse("Invalid request.", "Invalid request.", 400)); // var isTokenValid = await _userManager.VerifyUserTokenAsync(user,UserManager.ResetPasswordTokenPurpose, model.ResetCode); var isTokenValid = await _userManager.VerifyUserTokenAsync( user, TokenOptions.DefaultProvider, // This is the token provider UserManager.ResetPasswordTokenPurpose, WebUtility.UrlDecode(model.Token) ); string token = ""; if (!isTokenValid) { var isDecodedTokenValid = await _userManager.VerifyUserTokenAsync( user, TokenOptions.DefaultProvider, // This is the token provider UserManager.ResetPasswordTokenPurpose, model.Token ); if (!isDecodedTokenValid) return BadRequest(ApiResponse.ErrorResponse("Invalid request.", "Invalid request.", 400)); token = model.Token; } else { token = WebUtility.UrlDecode(model.Token); } var result = await _userManager.ResetPasswordAsync(user, token, model.NewPassword ?? string.Empty); if (!result.Succeeded) { var errors = result.Errors.Select(e => e.Description).ToList(); return BadRequest(ApiResponse.ErrorResponse("Failed to Change password", errors, 400)); } try { Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); await _emailSender.SendResetPasswordSuccessEmail(user.Email ?? string.Empty, emp.FirstName + " " + emp.LastName); } catch (Exception ex) { return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex.Message, 400)); } return Ok(ApiResponse.SuccessResponse(result.Succeeded, "Password reset successfully.", 200)); } [HttpPost("sendmail")] public async Task SendEmail([FromBody] EmailDot emailDot) { var user = await _userManager.FindByEmailAsync(emailDot.ToEmail ?? string.Empty); if (user == null) { return NotFound(ApiResponse.ErrorResponse("User not found.", "User not found.", 404)); } /* New User*/ //var token = await _userManager.GeneratePasswordResetTokenAsync(user); //var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}"; //await _emailSender.SendResetPasswordEmailOnRegister(emailDot.ToEmail, "Vikas", resetLink); /* Forget password*/ // var token = await _userManager.GeneratePasswordResetTokenAsync(user); var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword"); var isTokenValid = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword", token); var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}"; if (user.Email == null) return NotFound(ApiResponse.ErrorResponse("Email Not found", "Email Not found", 404)); await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink); return Ok(ApiResponse.SuccessResponse(new { }, "Password reset link sent.", 200)); } [Authorize] [HttpPost("change-password")] public async Task ChangePassword([FromBody] ChangePasswordDto changePassword) { try { // Get the currently logged-in user var loggedUser = await _userHelper.GetCurrentUserAsync(); // Validate email if (string.IsNullOrWhiteSpace(changePassword.Email)) { _logger.LogWarning("Change password attempt failed - Email is missing"); return BadRequest(ApiResponse.ErrorResponse("Email is missing", "Email is missing", 400)); } // Find the user by email var requestedUser = await _userManager.FindByEmailAsync(changePassword.Email); if (requestedUser == null) { _logger.LogWarning("Change password attempt failed - Email not found: {Email}", changePassword.Email); return BadRequest(ApiResponse.ErrorResponse("Invalid email", "User not found.", 400)); } // Validate the old password bool isOldPasswordCorrect = await _userManager.CheckPasswordAsync(requestedUser, changePassword.OldPassword ?? string.Empty); // Ensure user identity and old password match if (loggedUser?.Email != requestedUser.Email || !isOldPasswordCorrect) { _logger.LogWarning("Change password denied - User {Email} provided incorrect credentials", changePassword.Email); return BadRequest(ApiResponse.ErrorResponse("Incorrect credentials", "Invalid request.", 400)); } // Generate reset token and change password var resetToken = await _userManager.GeneratePasswordResetTokenAsync(requestedUser); var result = await _userManager.ResetPasswordAsync(requestedUser, resetToken, changePassword.NewPassword ?? string.Empty); if (!result.Succeeded) { var errors = result.Errors.Select(e => e.Description).ToList(); _logger.LogError("Password reset failed for user {Email}. Errors: {Errors}", changePassword.Email, string.Join("; ", errors)); return BadRequest(ApiResponse.ErrorResponse("Failed to change password", errors, 400)); } // Send confirmation email var emp = await _employeeHelper.GetEmployeeByApplicationUserID(requestedUser.Id); await _emailSender.SendResetPasswordSuccessEmail(requestedUser.Email ?? string.Empty, $"{emp.FirstName} {emp.LastName}"); _logger.LogInfo("Password changed successfully for user {Email}", requestedUser.Email ?? string.Empty); return Ok(ApiResponse.SuccessResponse(true, "Password changed successfully.", 200)); } catch (Exception exp) { _logger.LogError("An unexpected error occurred while changing password : {Error}", exp.Message); return StatusCode(500, ApiResponse.ErrorResponse("An unexpected error occurred.", exp.Message, 500)); } } [Authorize] [HttpPost("generate-mpin")] public async Task GenerateMPIN([FromBody] GenerateMPINDto generateMPINDto) { Guid tenantId = _userHelper.GetTenantId(); Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Get the employee for whom MPIN is being generated var requestEmployee = await _context.Employees .Include(e => e.ApplicationUser) .FirstOrDefaultAsync(e => e.Id == generateMPINDto.EmployeeId && e.TenantId == tenantId); // Validate employee and MPIN input if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN)) { _logger.LogError("Employee {EmployeeId} provided invalid information to generate MPIN", loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Provided invalid information", "Provided invalid information", 400)); } // Ensure the logged-in user is only generating their own MPIN if (requestEmployee.Id != loggedInEmployee.Id) { _logger.LogWarning("Employee {EmployeeId} tried to set MPIN for a different employee", loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("You can't create MPIN for another employee", "Unauthorized MPIN creation", 400)); } // Generate hash and token string mpinHash = ComputeSha256Hash(generateMPINDto.MPIN); string mpinToken = _refreshTokenService.CreateMPINToken( requestEmployee.ApplicationUserId, requestEmployee.TenantId.ToString(), _jwtSettings ); // Prepare MPIN entity Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty); var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId && p.TenantId == tenantId); if (existingMPIN == null) { // Add new MPIN record var mPINDetails = new MPINDetails { UserId = userId, MPIN = mpinHash, MPINToken = mpinToken, TimeStamp = DateTime.UtcNow, TenantId = tenantId }; _context.MPINDetails.Add(mPINDetails); await _context.SaveChangesAsync(); _logger.LogInfo("MPIN generated successfully for employee {EmployeeId}", requestEmployee.Id); return Ok(ApiResponse.SuccessResponse(mpinToken, "MPIN generated successfully", 200)); } else { // Update existing MPIN record existingMPIN.MPIN = mpinHash; existingMPIN.MPINToken = mpinToken; existingMPIN.TimeStamp = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInfo("MPIN updated successfully for employee {EmployeeId}", requestEmployee.Id); return Ok(ApiResponse.SuccessResponse(mpinToken, "MPIN updated successfully", 200)); } } private static string ComputeSha256Hash(string rawData) { using (SHA256 sha256 = SHA256.Create()) { // Convert the input string to bytes and compute the hash byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawData)); // Convert byte array to a readable hex string StringBuilder builder = new StringBuilder(); foreach (var b in bytes) builder.Append(b.ToString("x2")); return builder.ToString(); } } } }