469 lines
22 KiB
C#
469 lines
22 KiB
C#
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<ApplicationUser> _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<ApplicationUser> 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<IActionResult> 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<object>.ErrorResponse("User is In Active", "User is In Active", 400));
|
|
}
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
return BadRequest(ApiResponse<object>.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<object>.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<object>.SuccessResponse(new { token = token, refreshToken = refreshToken }, "User logged in successfully.", 200));
|
|
}
|
|
|
|
}
|
|
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
[HttpPost("login-mobile")]
|
|
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
|
{
|
|
// Validate input DTO
|
|
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
|
{
|
|
return BadRequest(ApiResponse<object>.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<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// Check if user is inactive
|
|
if (!user.IsActive)
|
|
{
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
|
}
|
|
|
|
// Check if user email is not confirmed
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
return BadRequest(ApiResponse<object>.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<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
|
}
|
|
|
|
// Check if username is missing
|
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
return NotFound(ApiResponse<object>.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<object>.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<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
|
{
|
|
if (string.IsNullOrEmpty(logoutDto.RefreshToken))
|
|
{
|
|
return BadRequest(ApiResponse<object>.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<object>.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<object>.SuccessResponse(new { }, "Logged out successfully", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// _logger.LogError(ex, "Error during logout");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Internal server error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("refresh-token")]
|
|
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenDto refreshTokenDto)
|
|
{
|
|
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
|
if (refreshToken == null || refreshToken.ExpiryDate < DateTime.UtcNow)
|
|
{
|
|
return Unauthorized(ApiResponse<object>.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<object>.ErrorResponse("Invalid request.", "Invalid request.", 400));
|
|
|
|
Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
|
|
if (user.UserName == null) return NotFound(ApiResponse<object>.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<object>.SuccessResponse(new { token = newJwtToken, refreshToken = newRefreshToken }, "User refresh token generated successfully.", 200));
|
|
}
|
|
|
|
[HttpPost("forgot-password")]
|
|
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto)
|
|
{
|
|
var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email);
|
|
if (user == null)
|
|
return NotFound(ApiResponse<object>.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<object>.ErrorResponse("Email Not found", "Email Not found", 404));
|
|
|
|
await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink);
|
|
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent.", 200));
|
|
}
|
|
|
|
[HttpPost("reset-password")]
|
|
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto model)
|
|
{
|
|
var user = await _userManager.FindByEmailAsync(model.Email ?? string.Empty);
|
|
if (user == null)
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid request.", 400));
|
|
|
|
// var isTokenValid = await _userManager.VerifyUserTokenAsync(user,UserManager<ApplicationUser>.ResetPasswordTokenPurpose, model.ResetCode);
|
|
var isTokenValid = await _userManager.VerifyUserTokenAsync(
|
|
user,
|
|
TokenOptions.DefaultProvider, // This is the token provider
|
|
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
|
WebUtility.UrlDecode(model.Token)
|
|
);
|
|
string token = "";
|
|
|
|
if (!isTokenValid)
|
|
{
|
|
var isDecodedTokenValid = await _userManager.VerifyUserTokenAsync(
|
|
user,
|
|
TokenOptions.DefaultProvider, // This is the token provider
|
|
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
|
model.Token
|
|
);
|
|
if (!isDecodedTokenValid)
|
|
return BadRequest(ApiResponse<object>.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<object>.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<object>.ErrorResponse(ex.Message, ex.Message, 400));
|
|
}
|
|
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(result.Succeeded, "Password reset successfully.", 200));
|
|
}
|
|
|
|
[HttpPost("sendmail")]
|
|
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
|
|
{
|
|
|
|
|
|
|
|
var user = await _userManager.FindByEmailAsync(emailDot.ToEmail ?? string.Empty);
|
|
if (user == null)
|
|
{
|
|
return NotFound(ApiResponse<object>.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<object>.ErrorResponse("Email Not found", "Email Not found", 404));
|
|
|
|
await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Password reset link sent.", 200));
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("change-password")]
|
|
public async Task<IActionResult> 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<object>.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<object>.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<object>.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<object>.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<object>.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<object>.ErrorResponse("An unexpected error occurred.", exp.Message, 500));
|
|
}
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("generate-mpin")]
|
|
public async Task<IActionResult> 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<object>.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<object>.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<object>.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<object>.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();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|