Implement API to log in through OTP verification

This commit is contained in:
ashutosh.nehete 2025-06-07 11:47:08 +05:30
parent baa168ff8f
commit 0a8c5cf587
6 changed files with 2805 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_IsUsed_FLag_In_OTPDetails_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsUsed",
table: "OTPDetails",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsUsed",
table: "OTPDetails");
}
}
}

View File

@ -268,6 +268,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<int>("ExpriesInSec") b.Property<int>("ExpriesInSec")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("IsUsed")
.HasColumnType("tinyint(1)");
b.Property<string>("OTP") b.Property<string>("OTP")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");

View File

@ -8,6 +8,7 @@ namespace Marco.Pms.Model.Authentication
public Guid UserId { get; set; } public Guid UserId { get; set; }
public string OTP { get; set; } = string.Empty; public string OTP { get; set; } = string.Empty;
public int ExpriesInSec { get; set; } public int ExpriesInSec { get; set; }
public bool IsUsed { get; set; } = false;
public DateTime TimeStamp { get; set; } public DateTime TimeStamp { get; set; }
} }
} }

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.Dtos.Authentication
{
public class VerifyOTPDto
{
public string? Email { get; set; }
public string? OPT { get; set; }
}
}

View File

@ -349,6 +349,97 @@ namespace MarcoBMS.Services.Controllers
} }
} }
[HttpPost("login-opt")]
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
{
try
{
// Validate input
if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
string.IsNullOrWhiteSpace(verifyOTP.OPT) ||
verifyOTP.OPT.Length != 4 ||
!verifyOTP.OPT.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 && o.TenantId == requestEmployee.TenantId)
.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.OPT)
{
_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.GenerateJwtToken(
requestEmployee.ApplicationUser?.UserName,
requestEmployee.TenantId,
_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 && p.TenantId == requestEmployee.TenantId);
// 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("An unexpected error occurred during OTP login for email {Email} : {Error}", verifyOTP.Email ?? string.Empty, ex.Message);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
}
}
[HttpPost("sendmail")] [HttpPost("sendmail")]
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot) public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
{ {