fix: improve auth flow and verification
This commit is contained in:
@@ -52,5 +52,23 @@ public class AuthController : ControllerBase
|
|||||||
var canResend = await authService.CanResendCodeAsync(mobile, cancellationToken);
|
var canResend = await authService.CanResendCodeAsync(mobile, cancellationToken);
|
||||||
return Ok(new { canResend });
|
return Ok(new { canResend });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("me")]
|
||||||
|
public async Task<ActionResult<UserDto>> Me(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var authHeader = Request.Headers.Authorization.ToString();
|
||||||
|
var token = authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? authHeader.Substring("Bearer ".Length).Trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var user = await authService.GetUserByTokenAsync(token, cancellationToken);
|
||||||
|
if (user == null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
return Ok(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ public interface IAuthService
|
|||||||
Task<SendCodeResponse> SendVerificationCodeAsync(SendCodeRequest request, CancellationToken cancellationToken);
|
Task<SendCodeResponse> SendVerificationCodeAsync(SendCodeRequest request, CancellationToken cancellationToken);
|
||||||
Task<VerifyCodeResponse> VerifyCodeAsync(VerifyCodeRequest request, CancellationToken cancellationToken);
|
Task<VerifyCodeResponse> VerifyCodeAsync(VerifyCodeRequest request, CancellationToken cancellationToken);
|
||||||
Task<bool> CanResendCodeAsync(string mobile, CancellationToken cancellationToken);
|
Task<bool> CanResendCodeAsync(string mobile, CancellationToken cancellationToken);
|
||||||
|
Task<UserDto?> GetUserByTokenAsync(string token, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using GreenHome.Application;
|
|||||||
using GreenHome.Infrastructure.Data;
|
using GreenHome.Infrastructure.Data;
|
||||||
using GreenHome.Sms.Ippanel;
|
using GreenHome.Sms.Ippanel;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -13,17 +14,20 @@ public sealed class AuthService : IAuthService
|
|||||||
private readonly GreenHomeDbContext _dbContext;
|
private readonly GreenHomeDbContext _dbContext;
|
||||||
private readonly ISmsService _smsService;
|
private readonly ISmsService _smsService;
|
||||||
private readonly ILogger<AuthService> _logger;
|
private readonly ILogger<AuthService> _logger;
|
||||||
|
private readonly string _tokenSecret;
|
||||||
private const int CodeExpirationMinutes = 5;
|
private const int CodeExpirationMinutes = 5;
|
||||||
private const int ResendCooldownSeconds = 120;
|
private const int ResendCooldownSeconds = 120;
|
||||||
|
|
||||||
public AuthService(
|
public AuthService(
|
||||||
GreenHomeDbContext dbContext,
|
GreenHomeDbContext dbContext,
|
||||||
ISmsService smsService,
|
ISmsService smsService,
|
||||||
|
IConfiguration configuration,
|
||||||
ILogger<AuthService> logger)
|
ILogger<AuthService> logger)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
_smsService = smsService;
|
_smsService = smsService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_tokenSecret = configuration["Auth:TokenSecret"] ?? "greenhome-dev-secret-change-me";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SendCodeResponse> SendVerificationCodeAsync(
|
public async Task<SendCodeResponse> SendVerificationCodeAsync(
|
||||||
@@ -162,7 +166,7 @@ public sealed class AuthService : IAuthService
|
|||||||
|
|
||||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
var token = GenerateToken(user.Id, mobile);
|
var token = GenerateToken(user.Id);
|
||||||
|
|
||||||
return new VerifyCodeResponse
|
return new VerifyCodeResponse
|
||||||
{
|
{
|
||||||
@@ -201,12 +205,51 @@ public sealed class AuthService : IAuthService
|
|||||||
return random.Next(1000, 9999).ToString();
|
return random.Next(1000, 9999).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateToken(int userId, string mobile)
|
public async Task<UserDto?> GetUserByTokenAsync(string token, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var data = $"{userId}:{mobile}:{DateTime.UtcNow:yyyyMMddHHmmss}";
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
var bytes = Encoding.UTF8.GetBytes(data);
|
return null;
|
||||||
var hash = SHA256.HashData(bytes);
|
|
||||||
return Convert.ToBase64String(hash);
|
var parts = token.Split('.');
|
||||||
|
if (parts.Length != 3)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!int.TryParse(parts[0], out var userId) || !long.TryParse(parts[1], out var expiresAt))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (expiresAt < DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var payload = $"{parts[0]}.{parts[1]}";
|
||||||
|
var expectedSignature = Convert.ToBase64String(
|
||||||
|
HMACSHA256.HashData(Encoding.UTF8.GetBytes(_tokenSecret), Encoding.UTF8.GetBytes(payload)));
|
||||||
|
|
||||||
|
if (!CryptographicOperations.FixedTimeEquals(
|
||||||
|
Encoding.UTF8.GetBytes(expectedSignature),
|
||||||
|
Encoding.UTF8.GetBytes(parts[2])))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||||
|
if (user == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new UserDto
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Mobile = user.Mobile,
|
||||||
|
Name = user.Name,
|
||||||
|
Family = user.Family,
|
||||||
|
Role = user.Role
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateToken(int userId)
|
||||||
|
{
|
||||||
|
var expiresAt = DateTimeOffset.UtcNow.AddDays(30).ToUnixTimeSeconds();
|
||||||
|
var payload = $"{userId}.{expiresAt}";
|
||||||
|
var signature = Convert.ToBase64String(
|
||||||
|
HMACSHA256.HashData(Encoding.UTF8.GetBytes(_tokenSecret), Encoding.UTF8.GetBytes(payload)));
|
||||||
|
return $"{payload}.{signature}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeMobile(string mobile)
|
private static string NormalizeMobile(string mobile)
|
||||||
|
|||||||
Reference in New Issue
Block a user