feat(app): update LoginCommandHandler to persist hashed refresh token on login
This commit is contained in:
12
src/api/SIGCM2.Application/Auth/AuthOptions.cs
Normal file
12
src/api/SIGCM2.Application/Auth/AuthOptions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace SIGCM2.Application.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration values for authentication token generation.
|
||||
/// Populated from the "Jwt" configuration section via IOptions in the Infrastructure layer.
|
||||
/// Lives in Application to avoid circular dependency with Infrastructure.
|
||||
/// </summary>
|
||||
public sealed class AuthOptions
|
||||
{
|
||||
public int AccessTokenMinutes { get; set; } = 60;
|
||||
public int RefreshTokenDays { get; set; } = 7;
|
||||
}
|
||||
@@ -2,7 +2,9 @@ using System.Text.Json;
|
||||
using SIGCM2.Application.Abstractions;
|
||||
using SIGCM2.Application.Abstractions.Persistence;
|
||||
using SIGCM2.Application.Abstractions.Security;
|
||||
using SIGCM2.Domain.Entities;
|
||||
using SIGCM2.Domain.Exceptions;
|
||||
using SIGCM2.Domain.Security;
|
||||
|
||||
namespace SIGCM2.Application.Auth.Login;
|
||||
|
||||
@@ -11,15 +13,27 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
|
||||
private readonly IUsuarioRepository _repository;
|
||||
private readonly IPasswordHasher _hasher;
|
||||
private readonly IJwtService _jwtService;
|
||||
private readonly IRefreshTokenRepository _refreshRepository;
|
||||
private readonly IRefreshTokenGenerator _refreshGenerator;
|
||||
private readonly IClientContext _clientContext;
|
||||
private readonly AuthOptions _authOptions;
|
||||
|
||||
public LoginCommandHandler(
|
||||
IUsuarioRepository repository,
|
||||
IPasswordHasher hasher,
|
||||
IJwtService jwtService)
|
||||
IJwtService jwtService,
|
||||
IRefreshTokenRepository refreshRepository,
|
||||
IRefreshTokenGenerator refreshGenerator,
|
||||
IClientContext clientContext,
|
||||
AuthOptions authOptions)
|
||||
{
|
||||
_repository = repository;
|
||||
_hasher = hasher;
|
||||
_jwtService = jwtService;
|
||||
_refreshRepository = refreshRepository;
|
||||
_refreshGenerator = refreshGenerator;
|
||||
_clientContext = clientContext;
|
||||
_authOptions = authOptions;
|
||||
}
|
||||
|
||||
public async Task<LoginResponseDto> Handle(LoginCommand command)
|
||||
@@ -34,15 +48,24 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
|
||||
throw new InvalidCredentialsException();
|
||||
|
||||
var accessToken = _jwtService.GenerateAccessToken(usuario);
|
||||
var refreshToken = Guid.NewGuid().ToString("N"); // opaque, not persisted in UDT-001
|
||||
|
||||
// Generate and persist refresh token — only the hash hits the DB
|
||||
var rawRefresh = _refreshGenerator.Generate();
|
||||
var hash = TokenHasher.Sha256Base64Url(rawRefresh);
|
||||
var now = DateTime.UtcNow;
|
||||
var ttl = TimeSpan.FromDays(_authOptions.RefreshTokenDays);
|
||||
var entity = RefreshToken.IssueForNewFamily(
|
||||
usuario.Id, hash, now, ttl,
|
||||
_clientContext.Ip, _clientContext.UserAgent);
|
||||
await _refreshRepository.AddAsync(entity);
|
||||
|
||||
var permisos = JsonSerializer.Deserialize<string[]>(usuario.PermisosJson)
|
||||
?? Array.Empty<string>();
|
||||
|
||||
return new LoginResponseDto(
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: 3600,
|
||||
RefreshToken: rawRefresh, // raw to client — never stored
|
||||
ExpiresIn: _authOptions.AccessTokenMinutes * 60,
|
||||
Usuario: new UsuarioDto(
|
||||
Id: usuario.Id,
|
||||
Nombre: $"{usuario.Nombre} {usuario.Apellido}".Trim(),
|
||||
|
||||
Reference in New Issue
Block a user