Changed the Login Flow for auth controller and added the get tenant list and select tenant
This commit is contained in:
parent
ee1cb73fe5
commit
bcc416f47e
6119
Marco.Pms.DataAccess/Migrations/20250920041347_Removed_TenantId_From_MPIN_And_OTP.Designer.cs
generated
Normal file
6119
Marco.Pms.DataAccess/Migrations/20250920041347_Removed_TenantId_From_MPIN_And_OTP.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Removed_TenantId_From_MPIN_And_OTP : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_MPINDetails_Tenants_TenantId",
|
||||||
|
table: "MPINDetails");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_OTPDetails_Tenants_TenantId",
|
||||||
|
table: "OTPDetails");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_OTPDetails_TenantId",
|
||||||
|
table: "OTPDetails");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_MPINDetails_TenantId",
|
||||||
|
table: "MPINDetails");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TenantId",
|
||||||
|
table: "OTPDetails");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TenantId",
|
||||||
|
table: "MPINDetails");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "TenantId",
|
||||||
|
table: "OTPDetails",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||||
|
collation: "ascii_general_ci");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "TenantId",
|
||||||
|
table: "MPINDetails",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||||
|
collation: "ascii_general_ci");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OTPDetails_TenantId",
|
||||||
|
table: "OTPDetails",
|
||||||
|
column: "TenantId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MPINDetails_TenantId",
|
||||||
|
table: "MPINDetails",
|
||||||
|
column: "TenantId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_MPINDetails_Tenants_TenantId",
|
||||||
|
table: "MPINDetails",
|
||||||
|
column: "TenantId",
|
||||||
|
principalTable: "Tenants",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_OTPDetails_Tenants_TenantId",
|
||||||
|
table: "OTPDetails",
|
||||||
|
column: "TenantId",
|
||||||
|
principalTable: "Tenants",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -284,9 +284,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
|
||||||
.HasColumnType("char(36)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("TimeStamp")
|
b.Property<DateTime>("TimeStamp")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@ -295,8 +292,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
|
||||||
|
|
||||||
b.ToTable("MPINDetails");
|
b.ToTable("MPINDetails");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -316,9 +311,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
|
||||||
.HasColumnType("char(36)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("TimeStamp")
|
b.Property<DateTime>("TimeStamp")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@ -327,8 +319,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
|
||||||
|
|
||||||
b.ToTable("OTPDetails");
|
b.ToTable("OTPDetails");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -4720,28 +4710,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.Navigation("UpdatedByEmployee");
|
b.Navigation("UpdatedByEmployee");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.MPINDetails", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("TenantId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Tenant");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.OTPDetails", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("TenantId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Tenant");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
using Marco.Pms.Model.Utilities;
|
namespace Marco.Pms.Model.Authentication
|
||||||
|
|
||||||
namespace Marco.Pms.Model.Authentication
|
|
||||||
{
|
{
|
||||||
public class MPINDetails : TenantRelation
|
public class MPINDetails
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
using Marco.Pms.Model.Utilities;
|
namespace Marco.Pms.Model.Authentication
|
||||||
|
|
||||||
namespace Marco.Pms.Model.Authentication
|
|
||||||
{
|
{
|
||||||
public class OTPDetails : TenantRelation
|
public class OTPDetails
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
using Marco.Pms.DataAccess.Data;
|
using AutoMapper;
|
||||||
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Model.Authentication;
|
using Marco.Pms.Model.Authentication;
|
||||||
using Marco.Pms.Model.Dtos.Authentication;
|
using Marco.Pms.Model.Dtos.Authentication;
|
||||||
using Marco.Pms.Model.Dtos.Util;
|
using Marco.Pms.Model.Dtos.Util;
|
||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
using Marco.Pms.Model.Entitlements;
|
using Marco.Pms.Model.Entitlements;
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Tenant;
|
||||||
using MarcoBMS.Services.Helpers;
|
using MarcoBMS.Services.Helpers;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -22,33 +24,37 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class AuthController : ControllerBase
|
public class AuthController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly UserHelper _userHelper;
|
|
||||||
private readonly ApplicationDbContext _context;
|
|
||||||
private readonly JwtSettings _jwtSettings;
|
private readonly JwtSettings _jwtSettings;
|
||||||
private readonly RefreshTokenService _refreshTokenService;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly EmployeeHelper _employeeHelper;
|
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
//string tenentId = "1";
|
|
||||||
public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService,
|
public AuthController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger)
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
JwtSettings jwtSettings,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILoggingService logger)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
||||||
_jwtSettings = jwtSettings;
|
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||||
_refreshTokenService = refreshTokenService;
|
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
|
||||||
_emailSender = emailSender;
|
_jwtSettings = jwtSettings ?? throw new ArgumentNullException(nameof(jwtSettings));
|
||||||
_configuration = configuration;
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||||
_employeeHelper = employeeHelper;
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_context = context;
|
|
||||||
_userHelper = userHelper;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// old login APIs
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Find user by email or phone number
|
// Find user by email or phone number
|
||||||
@ -118,6 +124,11 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("login-mobile")]
|
[HttpPost("login-mobile")]
|
||||||
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
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>();
|
||||||
|
|
||||||
// Validate input DTO
|
// Validate input DTO
|
||||||
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
||||||
{
|
{
|
||||||
@ -173,7 +184,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||||
|
|
||||||
// Fetch MPIN Token
|
// Fetch MPIN Token
|
||||||
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId);
|
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id));
|
||||||
|
|
||||||
// Combine all tokens in response
|
// Combine all tokens in response
|
||||||
var responseData = new
|
var responseData = new
|
||||||
@ -190,6 +201,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("login-mpin")]
|
[HttpPost("login-mpin")]
|
||||||
public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Validate the MPIN token and extract claims
|
// Validate the MPIN token and extract claims
|
||||||
@ -240,7 +255,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
// Retrieve MPIN details
|
// Retrieve MPIN details
|
||||||
var mpinDetails = await _context.MPINDetails
|
var mpinDetails = await _context.MPINDetails
|
||||||
.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId) && p.TenantId == tenantId);
|
.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId));
|
||||||
|
|
||||||
if (mpinDetails == null)
|
if (mpinDetails == null)
|
||||||
{
|
{
|
||||||
@ -275,9 +290,265 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new login APIs
|
||||||
|
|
||||||
|
[HttpPost("login/v2")]
|
||||||
|
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/v2")]
|
||||||
|
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")]
|
[HttpPost("logout")]
|
||||||
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
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>();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Logout failed: Refresh token is missing");
|
_logger.LogWarning("Logout failed: Refresh token is missing");
|
||||||
@ -315,6 +586,11 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("refresh-token")]
|
[HttpPost("refresh-token")]
|
||||||
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenDto refreshTokenDto)
|
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))
|
if (string.IsNullOrWhiteSpace(refreshTokenDto.RefreshToken))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Refresh token is missing from the request body.");
|
_logger.LogWarning("Refresh token is missing from the request body.");
|
||||||
@ -323,6 +599,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
try
|
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
|
// Step 1: Fetch and validate the refresh token
|
||||||
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
||||||
if (refreshToken == null)
|
if (refreshToken == null)
|
||||||
@ -358,8 +646,8 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
// Step 4: Fetch employee and generate new tokens
|
// Step 4: Fetch employee and generate new tokens
|
||||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||||
|
|
||||||
var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, _jwtSettings);
|
var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, tenantId, _jwtSettings);
|
||||||
var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, tenantId.ToString(), _jwtSettings);
|
||||||
|
|
||||||
_logger.LogInfo("New access and refresh token issued for user: {UserId}", user.Id);
|
_logger.LogInfo("New access and refresh token issued for user: {UserId}", user.Id);
|
||||||
|
|
||||||
@ -378,6 +666,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("forgot-password")]
|
[HttpPost("forgot-password")]
|
||||||
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto)
|
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))
|
if (string.IsNullOrWhiteSpace(forgotPasswordDto.Email))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("ForgotPassword request received without email.");
|
_logger.LogWarning("ForgotPassword request received without email.");
|
||||||
@ -414,6 +706,11 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("reset-password")]
|
[HttpPost("reset-password")]
|
||||||
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto model)
|
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);
|
_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))
|
if (string.IsNullOrWhiteSpace(model.Email) || string.IsNullOrWhiteSpace(model.Token) || string.IsNullOrWhiteSpace(model.NewPassword))
|
||||||
@ -491,6 +788,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("send-otp")]
|
[HttpPost("send-otp")]
|
||||||
public async Task<IActionResult> SendOtpEmail([FromBody] GenerateOTPDto generateOTP)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Validate input email
|
// Validate input email
|
||||||
@ -511,16 +812,15 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
.FirstOrDefaultAsync(e => e.ApplicationUserId == requestedUser.Id);
|
.FirstOrDefaultAsync(e => e.ApplicationUserId == requestedUser.Id);
|
||||||
|
|
||||||
// Generate a random 4-digit OTP
|
// Generate a random 4-digit OTP
|
||||||
string otp = new Random().Next(1000, 9999).ToString();
|
string otp = GenerateSecureOtp();
|
||||||
|
|
||||||
// Store OTP in database
|
// Store OTP in database
|
||||||
var otpDetails = new OTPDetails
|
var otpDetails = new OTPDetails
|
||||||
{
|
{
|
||||||
UserId = Guid.Parse(requestedUser.Id),
|
UserId = Guid.Parse(requestedUser.Id),
|
||||||
OTP = otp,
|
OTP = otp,
|
||||||
ExpriesInSec = 300, // 10 minutes
|
ExpriesInSec = 300, // 5 minutes
|
||||||
TimeStamp = DateTime.UtcNow,
|
TimeStamp = DateTime.UtcNow
|
||||||
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.OTPDetails.Add(otpDetails);
|
_context.OTPDetails.Add(otpDetails);
|
||||||
@ -555,6 +855,10 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("login-otp")]
|
[HttpPost("login-otp")]
|
||||||
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Validate input
|
// Validate input
|
||||||
@ -582,7 +886,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
|
|
||||||
// Fetch most recent OTP
|
// Fetch most recent OTP
|
||||||
var otpDetails = await _context.OTPDetails
|
var otpDetails = await _context.OTPDetails
|
||||||
.Where(o => o.UserId == userId && o.TenantId == requestEmployee.TenantId)
|
.Where(o => o.UserId == userId)
|
||||||
.OrderByDescending(o => o.TimeStamp)
|
.OrderByDescending(o => o.TimeStamp)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
@ -608,21 +912,24 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate access and refresh tokens
|
// Generate access and refresh tokens
|
||||||
var accessToken = _refreshTokenService.GenerateJwtToken(
|
var accessToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
|
||||||
requestEmployee.ApplicationUser?.UserName,
|
var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);
|
||||||
requestEmployee.TenantId ?? Guid.Empty,
|
|
||||||
_jwtSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(
|
//var accessToken = _refreshTokenService.GenerateJwtToken(
|
||||||
requestEmployee.ApplicationUserId,
|
// requestEmployee.ApplicationUser?.UserName,
|
||||||
requestEmployee.TenantId.ToString(),
|
// requestEmployee.TenantId ?? Guid.Empty,
|
||||||
_jwtSettings
|
// _jwtSettings
|
||||||
);
|
//);
|
||||||
|
|
||||||
|
//var refreshToken = await _refreshTokenService.CreateRefreshToken(
|
||||||
|
// requestEmployee.ApplicationUserId,
|
||||||
|
// requestEmployee.TenantId.ToString(),
|
||||||
|
// _jwtSettings
|
||||||
|
//);
|
||||||
|
|
||||||
// Fetch MPIN token if exists
|
// Fetch MPIN token if exists
|
||||||
var mpinDetails = await _context.MPINDetails
|
var mpinDetails = await _context.MPINDetails
|
||||||
.FirstOrDefaultAsync(p => p.UserId == userId && p.TenantId == requestEmployee.TenantId);
|
.FirstOrDefaultAsync(p => p.UserId == userId);
|
||||||
|
|
||||||
// Build and return response
|
// Build and return response
|
||||||
var response = new
|
var response = new
|
||||||
@ -646,8 +953,9 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("sendmail")]
|
[HttpPost("sendmail")]
|
||||||
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
|
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);
|
var user = await _userManager.FindByEmailAsync(emailDot.ToEmail ?? string.Empty);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@ -682,8 +990,15 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("change-password")]
|
[HttpPost("change-password")]
|
||||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto changePassword)
|
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto changePassword)
|
||||||
{
|
{
|
||||||
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
||||||
try
|
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
|
// Get the currently logged-in user
|
||||||
var loggedUser = await _userHelper.GetCurrentUserAsync();
|
var loggedUser = await _userHelper.GetCurrentUserAsync();
|
||||||
|
|
||||||
@ -741,13 +1056,18 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
[HttpPost("generate-mpin")]
|
[HttpPost("generate-mpin")]
|
||||||
public async Task<IActionResult> GenerateMPIN([FromBody] GenerateMPINDto generateMPINDto)
|
public async Task<IActionResult> GenerateMPIN([FromBody] GenerateMPINDto generateMPINDto)
|
||||||
{
|
{
|
||||||
Guid tenantId = _userHelper.GetTenantId();
|
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();
|
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
|
||||||
// Get the employee for whom MPIN is being generated
|
// Get the employee for whom MPIN is being generated
|
||||||
var requestEmployee = await _context.Employees
|
var requestEmployee = await _context.Employees
|
||||||
.Include(e => e.ApplicationUser)
|
.Include(e => e.ApplicationUser)
|
||||||
.FirstOrDefaultAsync(e => e.Id == generateMPINDto.EmployeeId && e.TenantId == tenantId);
|
.FirstOrDefaultAsync(e => e.Id == generateMPINDto.EmployeeId);
|
||||||
|
|
||||||
// Validate employee and MPIN input
|
// Validate employee and MPIN input
|
||||||
if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 4 || !generateMPINDto.MPIN.All(char.IsDigit))
|
if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 4 || !generateMPINDto.MPIN.All(char.IsDigit))
|
||||||
@ -767,13 +1087,13 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
string mpinHash = ComputeSha256Hash(generateMPINDto.MPIN);
|
string mpinHash = ComputeSha256Hash(generateMPINDto.MPIN);
|
||||||
string mpinToken = _refreshTokenService.CreateMPINToken(
|
string mpinToken = _refreshTokenService.CreateMPINToken(
|
||||||
requestEmployee.ApplicationUserId,
|
requestEmployee.ApplicationUserId,
|
||||||
requestEmployee.TenantId.ToString(),
|
requestEmployee.OrganizationId.ToString(),
|
||||||
_jwtSettings
|
_jwtSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prepare MPIN entity
|
// Prepare MPIN entity
|
||||||
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty);
|
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty);
|
||||||
var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId && p.TenantId == tenantId);
|
var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId);
|
||||||
|
|
||||||
if (existingMPIN == null)
|
if (existingMPIN == null)
|
||||||
{
|
{
|
||||||
@ -783,8 +1103,7 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
UserId = userId,
|
UserId = userId,
|
||||||
MPIN = mpinHash,
|
MPIN = mpinHash,
|
||||||
MPINToken = mpinToken,
|
MPINToken = mpinToken,
|
||||||
TimeStamp = DateTime.UtcNow,
|
TimeStamp = DateTime.UtcNow
|
||||||
TenantId = tenantId
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.MPINDetails.Add(mPINDetails);
|
_context.MPINDetails.Add(mPINDetails);
|
||||||
@ -806,6 +1125,130 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 200));
|
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 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)
|
||||||
|
.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.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 tenantOrganization = await _context.TenantOrgMappings
|
||||||
|
.FirstOrDefaultAsync(to => to.TenantId == tenantId && to.OrganizationId == organizationId);
|
||||||
|
|
||||||
|
if (tenantOrganization == null)
|
||||||
|
{
|
||||||
|
_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, tenantOrganization.TenantId, _jwtSettings);
|
||||||
|
|
||||||
|
// Generate and store refresh token
|
||||||
|
var refreshToken = await _refreshTokenService.CreateRefreshToken(loggedInEmployee.ApplicationUserId, tenantOrganization.TenantId.ToString(), _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)
|
private static string ComputeSha256Hash(string rawData)
|
||||||
{
|
{
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
using (SHA256 sha256 = SHA256.Create())
|
||||||
@ -822,5 +1265,20 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,79 @@ namespace MarcoBMS.Services.Service
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GenerateJwtTokenWithOrganization(string username, Guid organizationId, JwtSettings _jwtSettings)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Custom claims
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, username),
|
||||||
|
new Claim("OrganizationId", organizationId.ToString()), // Add TenantId claim
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) };
|
||||||
|
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
|
||||||
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
issuer: _jwtSettings.Issuer,
|
||||||
|
audience: _jwtSettings.Audience,
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiresInMinutes),
|
||||||
|
signingCredentials: creds);
|
||||||
|
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
public async Task<string> CreateRefreshTokenWithOrganization(string userId, Guid organizationId, JwtSettings jwtSettings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||||
|
new Claim("OrganizationId", organizationId.ToString()),
|
||||||
|
new Claim("token_type", "refresh")
|
||||||
|
};
|
||||||
|
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
|
||||||
|
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
|
||||||
|
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(claims),
|
||||||
|
Expires = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||||
|
Issuer = jwtSettings.Issuer,
|
||||||
|
Audience = jwtSettings.Audience,
|
||||||
|
SigningCredentials = credentials
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var refreshTokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||||
|
|
||||||
|
var refreshToken = new RefreshToken
|
||||||
|
{
|
||||||
|
Token = refreshTokenString,
|
||||||
|
UserId = userId,
|
||||||
|
ExpiryDate = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||||
|
IsRevoked = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the record exists
|
||||||
|
var existingEntity = await _context.RefreshTokens.AnyAsync(c => c.UserId == userId);
|
||||||
|
if (existingEntity) { _context.RefreshTokens.Update(refreshToken); }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_context.RefreshTokens.Add(refreshToken);
|
||||||
|
}
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return refreshTokenString;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error occured while creating new JWT token for user {UserId}", userId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
public string GenerateJwtToken(string username, Guid tenantId, JwtSettings _jwtSettings)
|
public string GenerateJwtToken(string username, Guid tenantId, JwtSettings _jwtSettings)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -47,7 +120,6 @@ namespace MarcoBMS.Services.Service
|
|||||||
|
|
||||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings jwtSettings)
|
public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings jwtSettings)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -98,7 +170,7 @@ namespace MarcoBMS.Services.Service
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public string CreateMPINToken(string userId, string tenantId, JwtSettings jwtSettings)
|
public string CreateMPINToken(string userId, string organizationId, JwtSettings jwtSettings)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -106,7 +178,7 @@ namespace MarcoBMS.Services.Service
|
|||||||
var claims = new[]
|
var claims = new[]
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||||
new Claim("TenantId", tenantId),
|
new Claim("OrganizationId", organizationId),
|
||||||
new Claim("token_type", "mpin")
|
new Claim("token_type", "mpin")
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,30 +197,23 @@ namespace MarcoBMS.Services.Service
|
|||||||
var tokenHandler = new JwtSecurityTokenHandler();
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
var MPINToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
var MPINToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||||
return MPINToken;
|
return MPINToken;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}", userId, tenantId);
|
_logger.LogError(ex, "Error creating MPIN token for userId: {UserId}, organizationId: {OrganizationId}", userId, organizationId);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RefreshToken> GetRefreshToken(string token)
|
public async Task<RefreshToken> GetRefreshToken(string token)
|
||||||
{
|
{
|
||||||
return await _context.RefreshTokens.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked && !rt.IsUsed) ?? new RefreshToken();
|
return await _context.RefreshTokens.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked && !rt.IsUsed) ?? new RefreshToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MarkRefreshTokenAsUsed(RefreshToken refreshToken)
|
public async Task MarkRefreshTokenAsUsed(RefreshToken refreshToken)
|
||||||
{
|
{
|
||||||
refreshToken.IsUsed = true;
|
refreshToken.IsUsed = true;
|
||||||
_context.RefreshTokens.Update(refreshToken);
|
_context.RefreshTokens.Update(refreshToken);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RevokeRefreshToken(RefreshToken refreshToken)
|
public async Task RevokeRefreshToken(RefreshToken refreshToken)
|
||||||
{
|
{
|
||||||
refreshToken.IsRevoked = true;
|
refreshToken.IsRevoked = true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user