Merge pull request 'Added tables for MPIN and OTP as well created an Login API for Mobile Application' (#83) from Ashutosh_Task#469_Mobile_Login into Issue_Jun_1W_2
Reviewed-on: #83
This commit is contained in:
commit
7ef2c720cb
@ -70,6 +70,9 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<MailingList> MailingList { get; set; }
|
||||
public DbSet<MailDetails> MailDetails { get; set; }
|
||||
public DbSet<MailLog> MailLogs { get; set; }
|
||||
public DbSet<OTPDetails> OTPDetails { get; set; }
|
||||
public DbSet<MPINDetails> MPINDetails { get; set; }
|
||||
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
|
2670
Marco.Pms.DataAccess/Migrations/20250605102139_Added_OTP_And_MPIN_Table.Designer.cs
generated
Normal file
2670
Marco.Pms.DataAccess/Migrations/20250605102139_Added_OTP_And_MPIN_Table.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_OTP_And_MPIN_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MPINDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
MPIN = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MPINToken = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
TimeStamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MPINDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MPINDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OTPDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OTP = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ExpriesInSec = table.Column<int>(type: "int", nullable: false),
|
||||
TimeStamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OTPDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OTPDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MPINDetails_TenantId",
|
||||
table: "MPINDetails",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OTPDetails_TenantId",
|
||||
table: "OTPDetails",
|
||||
column: "TenantId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MPINDetails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OTPDetails");
|
||||
}
|
||||
}
|
||||
}
|
@ -229,6 +229,65 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("AttendanceLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.MPINDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("MPIN")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("MPINToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("MPINDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.OTPDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("ExpriesInSec")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("OTP")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("OTPDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -2118,6 +2177,28 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("UpdatedByEmployee");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.MPINDetails", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Entitlements.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.Entitlements.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
|
13
Marco.Pms.Model/Authentication/MPINDetails.cs
Normal file
13
Marco.Pms.Model/Authentication/MPINDetails.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Model.Authentication
|
||||
{
|
||||
public class MPINDetails : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string MPIN { get; set; } = string.Empty;
|
||||
public string MPINToken { get; set; } = string.Empty;
|
||||
public DateTime TimeStamp { get; set; }
|
||||
}
|
||||
}
|
13
Marco.Pms.Model/Authentication/OTPDetails.cs
Normal file
13
Marco.Pms.Model/Authentication/OTPDetails.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Model.Authentication
|
||||
{
|
||||
public class OTPDetails : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string OTP { get; set; } = string.Empty;
|
||||
public int ExpriesInSec { get; set; }
|
||||
public DateTime TimeStamp { get; set; }
|
||||
}
|
||||
}
|
@ -71,6 +71,79 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
}
|
||||
|
||||
[HttpPost("login-mobile")]
|
||||
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
||||
{
|
||||
// Validate input DTO
|
||||
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
|
||||
}
|
||||
|
||||
// Find user by email or phone number
|
||||
var user = await _context.ApplicationUsers
|
||||
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||
|
||||
// If user not found, return unauthorized
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
}
|
||||
|
||||
// Check if user is inactive
|
||||
if (!user.IsActive)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
||||
}
|
||||
|
||||
// Check if user email is not confirmed
|
||||
if (!user.EmailConfirmed)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
|
||||
}
|
||||
|
||||
// Validate password using ASP.NET Identity
|
||||
var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
|
||||
if (!isPasswordValid)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
||||
}
|
||||
|
||||
// Check if username is missing
|
||||
if (string.IsNullOrWhiteSpace(user.UserName))
|
||||
{
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
|
||||
}
|
||||
|
||||
// Get employee information for tenant context
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
if (emp == null)
|
||||
{
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
|
||||
// Generate Refresh Token and store in DB
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
// Generate MPIN Token (custom short-term token)
|
||||
var mpinToken = await _refreshTokenService.CreateMPINToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
// Combine all tokens in response
|
||||
var responseData = new
|
||||
{
|
||||
token,
|
||||
refreshToken,
|
||||
mpinToken
|
||||
};
|
||||
|
||||
// Return success response
|
||||
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
||||
{
|
||||
@ -206,8 +279,6 @@ namespace MarcoBMS.Services.Controllers
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result.Succeeded, "Password reset successfully.", 200));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost("sendmail")]
|
||||
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
|
||||
{
|
||||
|
@ -48,36 +48,37 @@ namespace MarcoBMS.Services.Service
|
||||
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
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.UTF8.GetBytes(_jwtSettings.Key);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId),
|
||||
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(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId), // Add TenantId claim
|
||||
|
||||
new Claim("token_type", "refresh") // Custom claim to differentiate refresh tokens
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7), // Refresh token valid for 7 days
|
||||
Issuer = _jwtSettings.Issuer,
|
||||
Audience = _jwtSettings.Audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||
Issuer = jwtSettings.Issuer,
|
||||
Audience = jwtSettings.Audience,
|
||||
SigningCredentials = credentials
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
string strToken = tokenHandler.WriteToken(token);
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var refreshTokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
|
||||
var refreshToken = new RefreshToken
|
||||
{
|
||||
Token = strToken,
|
||||
Token = refreshTokenString,
|
||||
UserId = userId,
|
||||
ExpiryDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiresInDays),
|
||||
ExpiryDate = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||
IsRevoked = false
|
||||
};
|
||||
|
||||
@ -89,7 +90,7 @@ namespace MarcoBMS.Services.Service
|
||||
_context.RefreshTokens.Add(refreshToken);
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
return strToken;
|
||||
return refreshTokenString;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -97,6 +98,52 @@ namespace MarcoBMS.Services.Service
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public async Task<string> CreateMPINToken(string userId, string tenantId, JwtSettings jwtSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(userId) && p.TenantId == Guid.Parse(tenantId));
|
||||
if (existingMPIN != null)
|
||||
{
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId),
|
||||
new Claim("token_type", "mpin")
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Issuer = jwtSettings.Issuer,
|
||||
Audience = jwtSettings.Audience,
|
||||
SigningCredentials = creds
|
||||
// No 'Expires' means the token won't expire
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var MPINToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
|
||||
|
||||
|
||||
existingMPIN.MPINToken = MPINToken;
|
||||
await _context.SaveChangesAsync();
|
||||
return MPINToken;
|
||||
}
|
||||
return null;
|
||||
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}, error : {Error}", userId, tenantId, ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RefreshToken> GetRefreshToken(string token)
|
||||
{
|
||||
|
@ -34,7 +34,7 @@
|
||||
"RefreshTokenExpiresInDays": 7
|
||||
},
|
||||
"MailingList": {
|
||||
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com",
|
||||
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
|
||||
//"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
|
||||
},
|
||||
"AWS": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user