update
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
public interface IHashService
|
||||
{
|
||||
// Computes a deterministic SHA-256 hex string for the given input (legacy helper)
|
||||
string ComputeSha256Hex(string input);
|
||||
// Compares plain input with a stored hex hash (case-insensitive) (legacy helper)
|
||||
bool VerifySha256Hex(string input, string? expectedHexHash);
|
||||
|
||||
// Computes HMAC-SHA256 hex using a provided secret (for OTP and similar)
|
||||
string ComputeHmacSha256Hex(string input, string secret);
|
||||
// Verifies input against expected HMAC-SHA256 hex using the secret
|
||||
bool VerifyHmacSha256Hex(string input, string? expectedHexHash, string secret);
|
||||
|
||||
// Creates a salted password hash using PBKDF2; returns an encoded string containing algorithm params and salt
|
||||
string HashPassword(string password);
|
||||
// Verifies a password against the stored hash; supports both PBKDF2 format and legacy SHA-256 hex
|
||||
bool VerifyPassword(string password, string? storedHash);
|
||||
}
|
||||
@@ -7,11 +7,13 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler<CreateNewOtpToken
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IConfiguration _cfg;
|
||||
private readonly IHashService _hashService;
|
||||
|
||||
public CreateNewOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg)
|
||||
public CreateNewOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg, IHashService hashService)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
_hashService = hashService;
|
||||
}
|
||||
|
||||
const int CodeLength = 6;
|
||||
@@ -41,7 +43,8 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler<CreateNewOtpToken
|
||||
|
||||
// تولید کد
|
||||
var code = GenerateNumericCode(CodeLength);
|
||||
var codeHash = Hash(code);
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
var codeHash = _hashService.ComputeHmacSha256Hex(code, secret);
|
||||
|
||||
var entity = new OtpToken
|
||||
{
|
||||
@@ -75,13 +78,4 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler<CreateNewOtpToken
|
||||
foreach (var b in bytes) sb.Append((b % 10).ToString());
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string Hash(string code)
|
||||
{
|
||||
// HMAC با secret اپ (نیازی به ذخیره salt جدا نیست)
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
var hash = h.ComputeHash(Encoding.UTF8.GetBytes(code));
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using CMSMicroservice.Domain.Events;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;
|
||||
public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenCommand, VerifyOtpTokenResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IConfiguration _cfg;
|
||||
private readonly IHashService _hashService;
|
||||
|
||||
public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg)
|
||||
public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg, IHashService hashService)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
_hashService = hashService;
|
||||
}
|
||||
|
||||
const int MaxAttempts = 5; // محدودیت تلاش
|
||||
@@ -21,7 +21,6 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
|
||||
var purpose = request.Purpose?.ToLowerInvariant() ?? "signup";
|
||||
var now = DateTime.Now;
|
||||
|
||||
|
||||
var otp = await _context.OtpTokens
|
||||
.Where(o => o.Mobile == mobile && o.Purpose == purpose && !o.IsUsed && o.ExpiresAt > now)
|
||||
.OrderByDescending(o => o.Created)
|
||||
@@ -33,7 +32,8 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
|
||||
|
||||
otp.Attempts++;
|
||||
|
||||
if (!VerifyHash(request.Code, otp.CodeHash))
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
if (!_hashService.VerifyHmacSha256Hex(request.Code, otp.CodeHash, secret))
|
||||
{
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return new VerifyOtpTokenResponseDto() { Success = false, Message = "کد نادرست است." };
|
||||
@@ -96,15 +96,4 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
|
||||
RemainingSeconds = (otp.ExpiresAt - now).Seconds
|
||||
};
|
||||
}
|
||||
|
||||
private string Hash(string code)
|
||||
{
|
||||
// HMAC با secret اپ (نیازی به ذخیره salt جدا نیست)
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set");
|
||||
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
var hash = h.ComputeHash(Encoding.UTF8.GetBytes(code));
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
private bool VerifyHash(string code, string hexHash) => Hash(code).Equals(hexHash, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,33 @@ namespace CMSMicroservice.Application.UserCQ.Queries.AdminGetJwtToken;
|
||||
public class AdminGetJwtTokenQueryHandler : IRequestHandler<AdminGetJwtTokenQuery, AdminGetJwtTokenResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IGenerateJwtToken _generateJwt;
|
||||
private readonly IHashService _hashService;
|
||||
|
||||
public AdminGetJwtTokenQueryHandler(IApplicationDbContext context)
|
||||
public AdminGetJwtTokenQueryHandler(IApplicationDbContext context, IGenerateJwtToken generateJwt, IHashService hashService)
|
||||
{
|
||||
_context = context;
|
||||
_generateJwt = generateJwt;
|
||||
_hashService = hashService;
|
||||
}
|
||||
|
||||
public async Task<AdminGetJwtTokenResponseDto> Handle(AdminGetJwtTokenQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
//TODO: Implement your business logic
|
||||
return new AdminGetJwtTokenResponseDto();
|
||||
var user = await _context.Users
|
||||
.Include(u => u.UserRoles)
|
||||
.ThenInclude(ur => ur.Role)
|
||||
.FirstOrDefaultAsync(f => f.Mobile == request.Username, cancellationToken);
|
||||
if (user == null)
|
||||
throw new Exception("Invalid username or password.");
|
||||
|
||||
// verify password (supports PBKDF2 or legacy SHA-256)
|
||||
if (!_hashService.VerifyPassword(request.Password, user.HashPassword))
|
||||
throw new Exception("Invalid username or password.");
|
||||
|
||||
return new AdminGetJwtTokenResponseDto()
|
||||
{
|
||||
Token = await _generateJwt.GenerateJwtToken(user),
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user