1443 lines
74 KiB
C#
1443 lines
74 KiB
C#
using AutoMapper;
|
|
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 Marco.Pms.Model.ViewModels.Tenant;
|
|
using MarcoBMS.Services.Helpers;
|
|
using MarcoBMS.Services.Service;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Net;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace MarcoBMS.Services.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class AuthController : ControllerBase
|
|
{
|
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly JwtSettings _jwtSettings;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILoggingService _logger;
|
|
|
|
public AuthController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
|
IServiceScopeFactory serviceScopeFactory,
|
|
UserManager<ApplicationUser> userManager,
|
|
JwtSettings jwtSettings,
|
|
IConfiguration configuration,
|
|
ILoggingService logger)
|
|
{
|
|
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
|
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
|
|
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
|
|
_jwtSettings = jwtSettings ?? throw new ArgumentNullException(nameof(jwtSettings));
|
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
// old login APIs
|
|
|
|
[HttpPost("login/v1")]
|
|
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
try
|
|
{
|
|
|
|
var user = await _context.ApplicationUsers
|
|
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
|
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// Check if the user is active
|
|
if (!user.IsActive)
|
|
{
|
|
_logger.LogWarning("Login failed: Inactive user attempted login - UserId: {UserId}", user.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
|
}
|
|
|
|
// Ensure the user's email is confirmed
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
_logger.LogWarning("Login failed: Email not confirmed for UserId: {UserId}", user.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Email not verified", "Your email is not verified, please verify your email", 400));
|
|
}
|
|
|
|
// Validate the password
|
|
if (!await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
|
|
{
|
|
_logger.LogWarning("Login failed: Incorrect password for UserId: {UserId}", user.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// Retrieve employee details
|
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
if (emp == null)
|
|
{
|
|
_logger.LogWarning("Login failed: No employee record found for UserId: {UserId}", user.Id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee record not found", "Employee not found", 404));
|
|
}
|
|
|
|
// Ensure UserName exists for JWT
|
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
_logger.LogWarning("Login failed: Username not found for UserId: {UserId}", user.Id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Username not found", "Username not found", 404));
|
|
}
|
|
|
|
// Generate tokens
|
|
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, emp.OrganizationId, _jwtSettings);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), emp.OrganizationId, _jwtSettings);
|
|
|
|
_logger.LogInfo("User login successful - UserId: {UserId}", user.Id);
|
|
return Ok(ApiResponse<object>.SuccessResponse(new
|
|
{
|
|
token,
|
|
refreshToken
|
|
}, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error during login");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles mobile user login, validates credentials, sends a test push notification,
|
|
/// and generates JWT, Refresh, and MPIN tokens upon successful authentication.
|
|
/// </summary>
|
|
/// <param name="loginDto">Data Transfer Object containing the user's login credentials and device token.</param>
|
|
/// <returns>An IActionResult containing the authentication tokens or an error response.</returns>
|
|
|
|
[HttpPost("login-mobile")]
|
|
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
try
|
|
{
|
|
// 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));
|
|
}
|
|
|
|
// --- User Retrieval ---
|
|
// Find the user in the database by their email or phone number.
|
|
_logger.LogInfo("Searching for user: {Username}", loginDto.Username);
|
|
var user = await _context.ApplicationUsers
|
|
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
|
|
|
// If no user is found, return an unauthorized response.
|
|
if (user == null || string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
_logger.LogWarning("Login failed: User not found for username {Username}", loginDto.Username);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// --- User Status Checks ---
|
|
// Check if the user's account is marked as inactive.
|
|
if (!user.IsActive)
|
|
{
|
|
_logger.LogWarning("Login failed: User '{Username}' account is inactive.", user.UserName);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
|
}
|
|
|
|
// Check if the user has confirmed their email address.
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
_logger.LogWarning("Login failed: User '{Username}' email is not verified.", user.UserName);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
|
|
}
|
|
|
|
// --- Password Validation ---
|
|
// Use ASP.NET Identity's UserManager to securely check the password.
|
|
_logger.LogInfo("Validating password for user: {Username}", user.UserName);
|
|
var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
|
|
if (!isPasswordValid)
|
|
{
|
|
_logger.LogWarning("Login failed: Invalid password for user {Username}", user.UserName);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
|
}
|
|
_logger.LogInfo("Password validation successful for user: {Username}", user.UserName);
|
|
|
|
// Check if the username property on the user object is populated.
|
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
// This is an unlikely edge case, but good to handle.
|
|
_logger.LogWarning("Login failed: User object for ID {UserId} is missing a UserName.", user.Id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
|
|
}
|
|
|
|
// --- Employee and Tenant Context Retrieval ---
|
|
// Fetch associated employee details to get tenant context for token generation.
|
|
_logger.LogInfo("Fetching employee details for user ID: {UserId}", user.Id);
|
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
if (emp == null)
|
|
{
|
|
_logger.LogWarning("Login failed: Could not find associated employee record for user ID {UserId}", user.Id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
|
|
}
|
|
_logger.LogInfo("Successfully found employee details for tenant ID: {TenantId}", emp.TenantId ?? Guid.Empty);
|
|
|
|
// Generate JWT token
|
|
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, emp.OrganizationId, _jwtSettings);
|
|
|
|
// Generate a new refresh token and store it in the database.
|
|
_logger.LogInfo("Generating and storing Refresh Token for user: {Username}", user.UserName);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), emp.OrganizationId, _jwtSettings);
|
|
|
|
// Fetch MPIN Token
|
|
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id));
|
|
|
|
// --- Response Assembly ---
|
|
// Combine all tokens into a single response object.
|
|
var responseData = new
|
|
{
|
|
token,
|
|
refreshToken,
|
|
mpinToken = mpinToken?.MPINToken // Safely access the MPIN token, will be null if not found.
|
|
};
|
|
|
|
// Return a successful response with the generated tokens.
|
|
_logger.LogInfo("User {Username} logged in successfully.", user.UserName);
|
|
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", emp.Id);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
|
}
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// --- Global Exception Handling ---
|
|
// Catch any unexpected exceptions during the login process.
|
|
_logger.LogError(ex, "An unexpected error occurred during the LoginMobile process for user: {Username}", loginDto?.Username ?? "N/A");
|
|
|
|
// Return a generic 500 Internal Server Error to avoid leaking implementation details.
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred.", "Server Error", 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("login-mpin/v1")]
|
|
public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
|
|
try
|
|
{
|
|
// Validate the MPIN token and extract claims
|
|
var claimsPrincipal = _refreshTokenService.ValidateToken(verifyMPIN.MPINToken, _jwtSettings);
|
|
if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
|
|
{
|
|
_logger.LogWarning("Invalid or unauthenticated MPIN token");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
|
|
}
|
|
|
|
string? tokenType = claimsPrincipal.FindFirst("token_type")?.Value;
|
|
string? tokenTenantId = claimsPrincipal.FindFirst("TenantId")?.Value;
|
|
string? tokenUserId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
|
|
// Validate essential claims
|
|
if (string.IsNullOrWhiteSpace(tokenType) || string.IsNullOrWhiteSpace(tokenTenantId) || string.IsNullOrWhiteSpace(tokenUserId))
|
|
{
|
|
_logger.LogWarning("MPIN token claims are incomplete");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid token claims", "MPIN token does not match your identity", 401));
|
|
}
|
|
|
|
Guid tenantId = Guid.Parse(tokenTenantId);
|
|
|
|
// Fetch employee by ID and tenant
|
|
var requestEmployee = await _context.Employees
|
|
.Include(e => e.ApplicationUser)
|
|
.FirstOrDefaultAsync(e => e.Id == verifyMPIN.EmployeeId && e.TenantId == tenantId && e.ApplicationUserId == tokenUserId && e.IsActive);
|
|
|
|
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
|
{
|
|
_logger.LogWarning("Employee not found or invalid for verification - EmployeeId: {EmployeeId}", verifyMPIN.EmployeeId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "Provided invalid employee information", 400));
|
|
}
|
|
|
|
// Validate that the token belongs to the same employee making the request
|
|
if (requestEmployee.ApplicationUserId != tokenUserId || tokenType != "mpin")
|
|
{
|
|
_logger.LogWarning("Token identity does not match employee info - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "MPIN token does not match your identity", 401));
|
|
}
|
|
|
|
// Ensure MPIN input is valid
|
|
if (string.IsNullOrWhiteSpace(verifyMPIN.MPIN))
|
|
{
|
|
_logger.LogWarning("MPIN not provided for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "MPIN not provided", 400));
|
|
}
|
|
|
|
// Retrieve MPIN details
|
|
var mpinDetails = await _context.MPINDetails
|
|
.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId));
|
|
|
|
if (mpinDetails == null)
|
|
{
|
|
_logger.LogWarning("MPIN not set for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("MPIN not set", "You have not set an MPIN", 400));
|
|
}
|
|
|
|
// Compare hashed MPIN
|
|
var providedMPINHash = ComputeSha256Hash(verifyMPIN.MPIN);
|
|
if (providedMPINHash != mpinDetails.MPIN)
|
|
{
|
|
_logger.LogWarning("MPIN mismatch for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(verifyMPIN.FcmToken))
|
|
{
|
|
var existingFCMTokenMapping = await _context.FCMTokenMappings.Where(ft => ft.FcmToken == verifyMPIN.FcmToken).ToListAsync();
|
|
|
|
if (existingFCMTokenMapping.Any())
|
|
{
|
|
_context.FCMTokenMappings.RemoveRange(existingFCMTokenMapping);
|
|
}
|
|
|
|
var fcmTokenMapping = new FCMTokenMapping
|
|
{
|
|
EmployeeId = requestEmployee.Id,
|
|
FcmToken = verifyMPIN.FcmToken,
|
|
ExpiredAt = DateTime.UtcNow.AddDays(6),
|
|
TenantId = tenantId
|
|
};
|
|
_context.FCMTokenMappings.Add(fcmTokenMapping);
|
|
_logger.LogInfo("New FCM Token registering for employee {EmployeeId}", requestEmployee.Id);
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", requestEmployee.Id);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
|
}
|
|
|
|
}
|
|
|
|
// Generate new tokens
|
|
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, requestEmployee.OrganizationId, _jwtSettings);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), requestEmployee.OrganizationId, _jwtSettings);
|
|
|
|
_logger.LogInfo("MPIN verification successful - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(new
|
|
{
|
|
token = jwtToken,
|
|
refreshToken
|
|
}, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error occurred while verifying MPIN");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
// new login APIs
|
|
|
|
[HttpPost("login")]
|
|
public async Task<IActionResult> LoginAsync([FromBody] LoginDto loginDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
try
|
|
{
|
|
// Retrieve employee details
|
|
var emp = await _context.Employees.FirstOrDefaultAsync(e => e.Email == loginDto.Username && e.IsActive && e.HasApplicationAccess);
|
|
if (emp == null)
|
|
{
|
|
_logger.LogWarning("Login failed: No employee record found for Email: {Email}", loginDto.Username ?? string.Empty);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 404));
|
|
}
|
|
|
|
// Find user by email
|
|
var user = await _context.ApplicationUsers
|
|
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
|
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// Check if the user is active
|
|
if (!user.IsActive)
|
|
{
|
|
_logger.LogWarning("Login failed: Inactive user attempted login - UserId: {UserId}", user.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 400));
|
|
}
|
|
|
|
// Ensure the user's email is confirmed
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
_logger.LogWarning("Login failed: Email not confirmed for UserId: {UserId}", user.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 400));
|
|
}
|
|
|
|
// Validate the password
|
|
if (!await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
|
|
{
|
|
_logger.LogWarning("Login failed: Incorrect password for UserId: {UserId}", user.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
|
}
|
|
|
|
// Ensure UserName exists for JWT
|
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
_logger.LogWarning("Login failed: Username not found for UserId: {UserId}", user.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 404));
|
|
}
|
|
|
|
// Generate tokens
|
|
var token = _refreshTokenService.GenerateJwtTokenWithOrganization(user.UserName, emp.OrganizationId, _jwtSettings);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(user.Id, emp.OrganizationId, _jwtSettings);
|
|
|
|
//var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, _jwtSettings);
|
|
//var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
|
|
|
_logger.LogInfo("User login successful - UserId: {UserId}", user.Id);
|
|
return Ok(ApiResponse<object>.SuccessResponse(new
|
|
{
|
|
token,
|
|
refreshToken
|
|
}, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error during login");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("app/login")]
|
|
public async Task<IActionResult> LoginMobileAsync([FromBody] LoginDto loginDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
|
|
// 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.GenerateJwtTokenWithOrganization(user.UserName, emp.OrganizationId, _jwtSettings);
|
|
|
|
// Generate Refresh Token and store in DB
|
|
var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(user.Id, emp.OrganizationId, _jwtSettings);
|
|
|
|
//// Generate JWT token
|
|
//var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, _jwtSettings);
|
|
|
|
//// Generate Refresh Token and store in DB
|
|
//var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
|
|
|
// Fetch MPIN Token
|
|
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id));
|
|
|
|
// Combine all tokens in response
|
|
var responseData = new
|
|
{
|
|
token,
|
|
refreshToken,
|
|
mpinToken = mpinToken?.MPINToken
|
|
};
|
|
|
|
// Return success response
|
|
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
|
}
|
|
|
|
[HttpPost("login-mpin")]
|
|
public async Task<IActionResult> VerifyMPINAsync([FromBody] VerifyMPINDto verifyMPIN)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
|
|
try
|
|
{
|
|
// Validate the MPIN token and extract claims
|
|
var claimsPrincipal = _refreshTokenService.ValidateToken(verifyMPIN.MPINToken, _jwtSettings);
|
|
if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
|
|
{
|
|
_logger.LogWarning("Invalid or unauthenticated MPIN token");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
|
|
}
|
|
|
|
string? tokenType = claimsPrincipal.FindFirst("token_type")?.Value;
|
|
string? tokenUserId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
|
|
// Validate essential claims
|
|
if (string.IsNullOrWhiteSpace(tokenType) || string.IsNullOrWhiteSpace(tokenUserId))
|
|
{
|
|
_logger.LogWarning("MPIN token claims are incomplete");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid token claims", "MPIN token does not match your identity", 401));
|
|
}
|
|
|
|
// Fetch employee by ID and tenant
|
|
var requestEmployee = await _context.Employees
|
|
.Include(e => e.ApplicationUser)
|
|
.FirstOrDefaultAsync(e => e.Id == verifyMPIN.EmployeeId && e.HasApplicationAccess && e.ApplicationUserId == tokenUserId && e.IsActive);
|
|
|
|
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
|
{
|
|
_logger.LogWarning("Employee not found or invalid for verification - EmployeeId: {EmployeeId}", verifyMPIN.EmployeeId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "Provided invalid employee information", 400));
|
|
}
|
|
|
|
// Validate that the token belongs to the same employee making the request
|
|
if (requestEmployee.ApplicationUserId != tokenUserId || tokenType != "mpin")
|
|
{
|
|
_logger.LogWarning("Token identity does not match employee info - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "MPIN token does not match your identity", 401));
|
|
}
|
|
|
|
// Ensure MPIN input is valid
|
|
if (string.IsNullOrWhiteSpace(verifyMPIN.MPIN))
|
|
{
|
|
_logger.LogWarning("MPIN not provided for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "MPIN not provided", 400));
|
|
}
|
|
|
|
// Retrieve MPIN details
|
|
var mpinDetails = await _context.MPINDetails
|
|
.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId));
|
|
|
|
if (mpinDetails == null)
|
|
{
|
|
_logger.LogWarning("MPIN not set for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("MPIN not set", "You have not set an MPIN", 400));
|
|
}
|
|
|
|
// Compare hashed MPIN
|
|
var providedMPINHash = ComputeSha256Hash(verifyMPIN.MPIN);
|
|
if (providedMPINHash != mpinDetails.MPIN)
|
|
{
|
|
_logger.LogWarning("MPIN mismatch for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
|
|
}
|
|
|
|
// Generate new tokens
|
|
var jwtToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);
|
|
|
|
//var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
|
|
//var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);
|
|
|
|
_logger.LogInfo("MPIN verification successful - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(new
|
|
{
|
|
token = jwtToken,
|
|
refreshToken
|
|
}, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error occurred while verifying MPIN");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
|
|
var tenantId = _userHelper.GetTenantId();
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
|
{
|
|
_logger.LogWarning("Logout failed: Refresh token is missing");
|
|
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)
|
|
{
|
|
_logger.LogWarning("Logout failed: Invalid or expired refresh token");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token", "Invalid or expired refresh token", 401));
|
|
}
|
|
|
|
// Optional: Blacklist the JWT access token
|
|
string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
|
|
if (!string.IsNullOrWhiteSpace(jwtToken))
|
|
{
|
|
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
|
_logger.LogInfo("JWT access token blacklisted successfully");
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(logoutDto.FcmToken))
|
|
{
|
|
var fcmTokenMapping = await _context.FCMTokenMappings.FirstOrDefaultAsync(ft => ft.EmployeeId == loggedInEmployee.Id && ft.FcmToken == logoutDto.FcmToken && ft.TenantId == tenantId);
|
|
if (fcmTokenMapping != null)
|
|
{
|
|
_context.FCMTokenMappings.Remove(fcmTokenMapping);
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
}
|
|
_logger.LogInfo("User logged out successfully");
|
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Logged out successfully", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error during logout");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("refresh-token")]
|
|
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenDto refreshTokenDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
|
|
if (string.IsNullOrWhiteSpace(refreshTokenDto.RefreshToken))
|
|
{
|
|
_logger.LogWarning("Refresh token is missing from the request body.");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Refresh token is required.", "Missing refresh token.", 400));
|
|
}
|
|
|
|
try
|
|
{
|
|
// Validate the MPIN token and extract claims
|
|
var claimsPrincipal = _refreshTokenService.ValidateToken(refreshTokenDto.RefreshToken, _jwtSettings);
|
|
if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
|
|
{
|
|
_logger.LogWarning("Invalid or unauthenticated MPIN token");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
|
|
}
|
|
|
|
string? tokenTenantId = claimsPrincipal.FindFirst("TenantId")?.Value ?? string.Empty;
|
|
|
|
var tenantId = Guid.Parse(tokenTenantId);
|
|
|
|
// Step 1: Fetch and validate the refresh token
|
|
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
|
if (refreshToken == null)
|
|
{
|
|
_logger.LogWarning("Refresh token not found in the database");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token.", "Token not found.", 401));
|
|
}
|
|
|
|
if (refreshToken.ExpiryDate < DateTime.UtcNow)
|
|
{
|
|
_logger.LogWarning("Refresh token expired");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Refresh token expired.", "Token expired.", 401));
|
|
}
|
|
|
|
// Step 2: Mark the token as used
|
|
await _refreshTokenService.MarkRefreshTokenAsUsed(refreshToken);
|
|
_logger.LogInfo("Refresh token marked as used");
|
|
|
|
// Step 3: Validate and retrieve user
|
|
var user = await _userManager.FindByIdAsync(refreshToken.UserId ?? string.Empty);
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("User not found for RefreshToken: {Token}", refreshTokenDto.RefreshToken);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "User not found.", 400));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(user.UserName))
|
|
{
|
|
_logger.LogWarning("Username missing for user ID: {UserId}", user.Id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Username not found.", "Username not found.", 404));
|
|
}
|
|
|
|
// Step 4: Fetch employee and generate new tokens
|
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
|
|
var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, tenantId, emp.OrganizationId, _jwtSettings);
|
|
var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, tenantId.ToString(), emp.OrganizationId, _jwtSettings);
|
|
|
|
_logger.LogInfo("New access and refresh token issued for user: {UserId}", user.Id);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(
|
|
new { token = newJwtToken, refreshToken = newRefreshToken },
|
|
"User refresh token generated successfully.",
|
|
200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "An unexpected error occurred during token refresh.");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred.", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("forgot-password")]
|
|
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
|
|
if (string.IsNullOrWhiteSpace(forgotPasswordDto.Email))
|
|
{
|
|
_logger.LogWarning("ForgotPassword request received without email.");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Email is required.", "Email is required.", 400));
|
|
}
|
|
|
|
var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email);
|
|
if (user == null || user.Email == null)
|
|
{
|
|
_logger.LogWarning("ForgotPassword requested for non-existent or null-email user: {Email}", forgotPasswordDto.Email);
|
|
// Do not disclose whether the email exists (security best practice)
|
|
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
|
|
}
|
|
|
|
try
|
|
{
|
|
// Generate token and build reset link
|
|
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
|
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
|
|
|
// Send reset email
|
|
await _emailSender.SendResetPasswordEmail(user.Email, user.UserName ?? "User", resetLink);
|
|
|
|
_logger.LogInfo("Password reset link sent to user: {Email}", user.Email);
|
|
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while sending password reset email to");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Error sending password reset email.", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("reset-password")]
|
|
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto model)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
|
|
_logger.LogInfo("Password reset request received for email: {Email}", model.Email ?? string.Empty);
|
|
|
|
if (string.IsNullOrWhiteSpace(model.Email) || string.IsNullOrWhiteSpace(model.Token) || string.IsNullOrWhiteSpace(model.NewPassword))
|
|
{
|
|
_logger.LogWarning("Reset password failed due to missing input fields for email: {Email}", model.Email ?? string.Empty);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("All fields are required.", "Invalid input.", 400));
|
|
}
|
|
|
|
var user = await _userManager.FindByEmailAsync(model.Email);
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("Reset password failed - user not found for email: {Email}", model.Email);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid user.", 400));
|
|
}
|
|
|
|
var isTokenValid = await _userManager.VerifyUserTokenAsync(
|
|
user,
|
|
TokenOptions.DefaultProvider, // This is the token provider
|
|
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
|
WebUtility.UrlDecode(model.Token)
|
|
);
|
|
string token = "";
|
|
|
|
if (!isTokenValid)
|
|
{
|
|
_logger.LogWarning("Decoded token failed, retrying with raw token for email: {Email}", model.Email);
|
|
|
|
var isDecodedTokenValid = await _userManager.VerifyUserTokenAsync(
|
|
user,
|
|
TokenOptions.DefaultProvider,
|
|
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
|
model.Token
|
|
);
|
|
|
|
if (!isDecodedTokenValid)
|
|
{
|
|
_logger.LogWarning("Both decoded and raw token failed for email: {Email}", model.Email);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid or expired reset token.", 400));
|
|
}
|
|
|
|
token = model.Token;
|
|
}
|
|
else
|
|
{
|
|
token = WebUtility.UrlDecode(model.Token);
|
|
}
|
|
|
|
var result = await _userManager.ResetPasswordAsync(user, token, model.NewPassword);
|
|
if (!result.Succeeded)
|
|
{
|
|
var errors = result.Errors.Select(e => e.Description).ToList();
|
|
_logger.LogWarning("Reset password failed for user: {Email}. Errors: {Errors}", model.Email, string.Join(", ", errors));
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to reset password.", errors, 400));
|
|
}
|
|
|
|
try
|
|
{
|
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
|
string fullName = $"{emp.FirstName} {emp.LastName}".Trim();
|
|
|
|
await _emailSender.SendResetPasswordSuccessEmail(user.Email!, fullName);
|
|
|
|
_logger.LogInfo("Reset password success email sent to user: {Email}", model.Email);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error while sending reset password success email to user");
|
|
// Continue, do not fail because of email issue
|
|
}
|
|
|
|
_logger.LogInfo("Password reset successful for user: {Email}", model.Email);
|
|
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset successfully.", 200));
|
|
}
|
|
|
|
[HttpPost("send-otp")]
|
|
public async Task<IActionResult> SendOtpEmail([FromBody] GenerateOTPDto generateOTP)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
|
|
try
|
|
{
|
|
// Validate input email
|
|
if (string.IsNullOrWhiteSpace(generateOTP.Email))
|
|
{
|
|
_logger.LogWarning("Send OTP failed - Email is missing");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Email is required", "Invalid email", 400));
|
|
}
|
|
|
|
// Fetch user by email
|
|
var requestedUser = await _userManager.FindByEmailAsync(generateOTP.Email);
|
|
string title = "Send OTP";
|
|
|
|
if (requestedUser != null && requestedUser.IsActive)
|
|
{
|
|
// Fetch employee details
|
|
var requestedEmployee = await _context.Employees
|
|
.FirstOrDefaultAsync(e => e.ApplicationUserId == requestedUser.Id);
|
|
|
|
// Generate a random 4-digit OTP
|
|
string otp = GenerateSecureOtp();
|
|
|
|
// Store OTP in database
|
|
var otpDetails = new OTPDetails
|
|
{
|
|
UserId = Guid.Parse(requestedUser.Id),
|
|
OTP = otp,
|
|
ExpriesInSec = 300, // 5 minutes
|
|
TimeStamp = DateTime.UtcNow
|
|
};
|
|
|
|
_context.OTPDetails.Add(otpDetails);
|
|
await _context.SaveChangesAsync();
|
|
|
|
// Prepare email
|
|
List<string> toEmails = [generateOTP.Email];
|
|
string name = $"{requestedEmployee?.FirstName} {requestedEmployee?.LastName}".Trim();
|
|
var mailTemplate = await _context.MailingList
|
|
.FirstOrDefaultAsync(t => t.Title.ToLower() == title.ToLower());
|
|
|
|
string subject = mailTemplate?.Subject ?? string.Empty;
|
|
string emailBody = mailTemplate?.Body ?? string.Empty;
|
|
|
|
// Send OTP via email
|
|
await _emailSender.SendOTP(toEmails, emailBody, name, otp, subject);
|
|
|
|
_logger.LogInfo("OTP sent successfully to {Email}", generateOTP.Email);
|
|
return Ok(ApiResponse<object>.SuccessResponse("Success", "OTP generated successfully", 200));
|
|
}
|
|
|
|
_logger.LogWarning("Send OTP failed - Invalid or inactive user: {Email}", generateOTP.Email);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Provided invalid information", "User not found or inactive", 400));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "An unexpected error occurred while sending OTP to {Email}", generateOTP.Email ?? "");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("login-otp")]
|
|
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
|
|
try
|
|
{
|
|
// Validate input
|
|
if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
|
|
string.IsNullOrWhiteSpace(verifyOTP.OTP) ||
|
|
verifyOTP.OTP.Length != 4 ||
|
|
!verifyOTP.OTP.All(char.IsDigit))
|
|
{
|
|
_logger.LogWarning("OTP login failed - invalid input provided");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid input", "Please provide a valid 4-digit OTP and Email", 400));
|
|
}
|
|
|
|
// Fetch employee by email
|
|
var requestEmployee = await _context.Employees
|
|
.Include(e => e.ApplicationUser)
|
|
.FirstOrDefaultAsync(e => e.Email == verifyOTP.Email && e.IsActive);
|
|
|
|
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
|
{
|
|
_logger.LogWarning("OTP login failed - user not found for email {Email}", verifyOTP.Email);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("User not found", "User not found", 404));
|
|
}
|
|
|
|
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId);
|
|
|
|
// Fetch most recent OTP
|
|
var otpDetails = await _context.OTPDetails
|
|
.Where(o => o.UserId == userId)
|
|
.OrderByDescending(o => o.TimeStamp)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (otpDetails == null)
|
|
{
|
|
_logger.LogWarning("OTP login failed - no OTP found for user {UserId}", userId);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("OTP not found", "No OTP was generated for this user", 404));
|
|
}
|
|
|
|
// Validate OTP expiration
|
|
var validUntil = otpDetails.TimeStamp.AddSeconds(otpDetails.ExpriesInSec);
|
|
if (DateTime.UtcNow > validUntil || otpDetails.IsUsed)
|
|
{
|
|
_logger.LogWarning("OTP login failed - OTP expired for user {UserId}", userId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("OTP expired", "The OTP has expired, please request a new one", 400));
|
|
}
|
|
|
|
// Match OTP
|
|
if (otpDetails.OTP != verifyOTP.OTP)
|
|
{
|
|
_logger.LogWarning("OTP login failed - incorrect OTP entered for user {UserId}", userId);
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid OTP", "OTP did not match", 401));
|
|
}
|
|
|
|
// Generate access and refresh tokens
|
|
var accessToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
|
|
var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);
|
|
|
|
//var accessToken = _refreshTokenService.GenerateJwtToken(
|
|
// requestEmployee.ApplicationUser?.UserName,
|
|
// requestEmployee.TenantId ?? Guid.Empty,
|
|
// _jwtSettings
|
|
//);
|
|
|
|
//var refreshToken = await _refreshTokenService.CreateRefreshToken(
|
|
// requestEmployee.ApplicationUserId,
|
|
// requestEmployee.TenantId.ToString(),
|
|
// _jwtSettings
|
|
//);
|
|
|
|
// Fetch MPIN token if exists
|
|
var mpinDetails = await _context.MPINDetails
|
|
.FirstOrDefaultAsync(p => p.UserId == userId);
|
|
|
|
// Build and return response
|
|
var response = new
|
|
{
|
|
token = accessToken,
|
|
refreshToken,
|
|
mpinToken = mpinDetails?.MPINToken
|
|
};
|
|
otpDetails.IsUsed = true;
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInfo("OTP login successful for employee {EmployeeId}", requestEmployee.Id);
|
|
return Ok(ApiResponse<object>.SuccessResponse(response, "User logged in successfully.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "An unexpected error occurred during OTP login for email {Email}", verifyOTP.Email ?? string.Empty);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("sendmail")]
|
|
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
|
|
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)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
|
|
try
|
|
{
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
|
|
|
|
// 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.LogWarning("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(exp, "An unexpected error occurred while changing password");
|
|
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)
|
|
{
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
|
|
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);
|
|
|
|
// Validate employee and MPIN input
|
|
if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 4 || !generateMPINDto.MPIN.All(char.IsDigit))
|
|
{
|
|
_logger.LogWarning("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.OrganizationId.ToString(),
|
|
_jwtSettings
|
|
);
|
|
|
|
// Prepare MPIN entity
|
|
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty);
|
|
var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId);
|
|
|
|
if (existingMPIN == null)
|
|
{
|
|
// Add new MPIN record
|
|
var mPINDetails = new MPINDetails
|
|
{
|
|
UserId = userId,
|
|
MPIN = mpinHash,
|
|
MPINToken = mpinToken,
|
|
TimeStamp = DateTime.UtcNow
|
|
};
|
|
|
|
_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));
|
|
}
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("set/device-token")]
|
|
public async Task<IActionResult> StoreDeviceToken([FromBody] FCMTokenDto model)
|
|
{
|
|
// Create DbContext asynchronously for fresh connection
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
|
|
// Create a scope to get scoped services
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
|
|
var tenantId = _userHelper.GetTenantId();
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
var existingFCMTokenMapping = await _context.FCMTokenMappings.Where(ft => ft.FcmToken == model.FcmToken).ToListAsync();
|
|
|
|
if (existingFCMTokenMapping.Any())
|
|
{
|
|
_context.FCMTokenMappings.RemoveRange(existingFCMTokenMapping);
|
|
}
|
|
|
|
var fcmTokenMapping = new FCMTokenMapping
|
|
{
|
|
EmployeeId = loggedInEmployee.Id,
|
|
FcmToken = model.FcmToken,
|
|
ExpiredAt = DateTime.UtcNow.AddDays(6),
|
|
TenantId = tenantId
|
|
};
|
|
_context.FCMTokenMappings.Add(fcmTokenMapping);
|
|
_logger.LogInfo("New FCM Token registering for employee {EmployeeId}", loggedInEmployee.Id);
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", loggedInEmployee.Id);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
|
|
}
|
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "FCM Token registered Successfuly", 200));
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("get/user/tenants")]
|
|
public async Task<IActionResult> GetTenantListByEmployeeAsync()
|
|
{
|
|
// Create DbContext asynchronously to ensure DB connection is established fresh
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
|
|
// Create a service scope to resolve scoped services like IHttpContextAccessor, IMapper, ILogger
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
|
|
var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
|
|
_logger.LogDebug("Starting GetTenantAsync method.");
|
|
|
|
// Extract OrganizationId from current user's claims
|
|
string stringOrganizationId = _httpContextAccessor.HttpContext?.User.FindFirst("OrganizationId")?.Value ?? string.Empty;
|
|
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
if (string.IsNullOrEmpty(stringOrganizationId))
|
|
{
|
|
_logger.LogWarning("OrganizationId claim missing in user token.");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("OrganizationId claim is missing", 400));
|
|
}
|
|
|
|
if (!Guid.TryParse(stringOrganizationId, out var organizationId))
|
|
{
|
|
_logger.LogWarning("Invalid OrganizationId format: {OrganizationId}", stringOrganizationId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid OrganizationId format", 400));
|
|
}
|
|
|
|
_logger.LogInfo("Fetching TenantOrgMappings for OrganizationId: {OrganizationId}", organizationId);
|
|
|
|
// Retrieve all TenantOrgMappings that match the organizationId and have a related Tenant
|
|
var tenantOrganizationMapping = await _context.TenantOrgMappings
|
|
.Include(to => to.Tenant)
|
|
.ThenInclude(t => t!.TenantStatus)
|
|
.Include(to => to.Tenant)
|
|
.ThenInclude(t => t!.Industry)
|
|
.Where(to => to.OrganizationId == organizationId && to.Tenant != null)
|
|
.ToListAsync();
|
|
|
|
var tenantList = tenantOrganizationMapping.Select(to => to.Tenant!).ToList();
|
|
|
|
// Additionally fetch the Tenant record associated directly with this OrganizationId if any
|
|
var tenant = await _context.Tenants
|
|
.Include(t => t.Industry)
|
|
.Include(t => t.TenantStatus)
|
|
.FirstOrDefaultAsync(t => t.OrganizationId == organizationId);
|
|
if (tenant != null)
|
|
{
|
|
tenantList.Add(tenant);
|
|
}
|
|
|
|
|
|
tenantList = tenantList.Distinct().ToList();
|
|
|
|
// Map the tenant entities to TenantListVM view models
|
|
var response = _mapper.Map<List<TenantListVM>>(tenantList);
|
|
|
|
_logger.LogInfo("Fetched {Count} tenants for OrganizationId: {OrganizationId}", tenantList.Count, organizationId);
|
|
_logger.LogDebug("GetTenantAsync method completed successfully.");
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully fetched the list of tenant", 200));
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("select-tenant/{tenantId}")]
|
|
public async Task<IActionResult> SelectTenantAsync(Guid tenantId)
|
|
{
|
|
// Create DbContext asynchronously for fresh connection
|
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
|
|
|
// Create a scope to get scoped services
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
|
|
var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
|
|
var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
|
|
|
|
_logger.LogDebug("Starting SelectTenantAsync for tenantId: {TenantId}", tenantId);
|
|
|
|
// Get the current logged-in employee
|
|
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
// Extract OrganizationId from user claims
|
|
string stringOrganizationId = _httpContextAccessor.HttpContext?.User.FindFirst("OrganizationId")?.Value ?? string.Empty;
|
|
|
|
if (string.IsNullOrEmpty(stringOrganizationId))
|
|
{
|
|
_logger.LogWarning("OrganizationId claim is missing.");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("OrganizationId claim is missing", 400));
|
|
}
|
|
|
|
if (!Guid.TryParse(stringOrganizationId, out var organizationId))
|
|
{
|
|
_logger.LogWarning("Invalid OrganizationId format: {OrganizationId}", stringOrganizationId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid OrganizationId format", 400));
|
|
}
|
|
|
|
// Find TenantOrgMapping for given tenantId and organizationId to validate access
|
|
var tenantOrganizationTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.TenantOrgMappings
|
|
.FirstOrDefaultAsync(to => to.TenantId == tenantId && to.OrganizationId == organizationId);
|
|
|
|
});
|
|
|
|
var primaryOrganizationTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.Tenants
|
|
.Where(t => t.Id == tenantId && t.OrganizationId == organizationId).ToListAsync();
|
|
|
|
});
|
|
|
|
await Task.WhenAll(tenantOrganizationTask, primaryOrganizationTask);
|
|
|
|
var tenantOrganization = tenantOrganizationTask.Result;
|
|
var primaryOrganization = primaryOrganizationTask.Result;
|
|
|
|
if (tenantOrganization == null && !primaryOrganization.Any())
|
|
{
|
|
_logger.LogWarning("Tenant Organization Mapping not found for TenantId: {TenantId} and OrganizationId: {OrganizationId}", tenantId, organizationId);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Tenant Organization Mapping not found", "Tenant Organization Mapping not found in database", 404));
|
|
}
|
|
|
|
// Optional: Blacklist the JWT access token
|
|
string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
|
|
if (!string.IsNullOrWhiteSpace(jwtToken))
|
|
{
|
|
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
|
_logger.LogInfo("JWT access token blacklisted successfully");
|
|
}
|
|
|
|
// Generate JWT token scoped to selected tenant and logged-in employee
|
|
var token = _refreshTokenService.GenerateJwtToken(loggedInEmployee.Email, tenantId, loggedInEmployee.OrganizationId, _jwtSettings);
|
|
|
|
// Generate and store refresh token
|
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(loggedInEmployee.ApplicationUserId, tenantId.ToString(), loggedInEmployee.OrganizationId, _jwtSettings);
|
|
|
|
_logger.LogInfo("Tenant selected and tokens generated for TenantId: {TenantId} and Employee: {EmployeeEmail}", tenantId, loggedInEmployee.Email ?? string.Empty);
|
|
|
|
// Return success response including tokens
|
|
return Ok(ApiResponse<object>.SuccessResponse(new { Token = token, RefreshToken = refreshToken }, "Tenant is selected", 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();
|
|
}
|
|
}
|
|
|
|
private static string GenerateSecureOtp()
|
|
{
|
|
var randomNumber = new byte[2]; // 2 bytes can store values up to 65535
|
|
using (var rng = RandomNumberGenerator.Create())
|
|
{
|
|
rng.GetBytes(randomNumber);
|
|
}
|
|
|
|
// Convert to int and restrict to 4 digit range (0-9999)
|
|
int randomValue = BitConverter.ToUInt16(randomNumber, 0) % 10000;
|
|
|
|
// Format with leading zeros if necessary to ensure 4 digits
|
|
return randomValue.ToString("D4");
|
|
}
|
|
|
|
}
|
|
}
|