feat(udt-011): T400.10 — inject TimeProvider into all Application handlers

All command handlers that call domain mutators now inject TimeProvider
via constructor and use _timeProvider.GetUtcNow().UtcDateTime as the
explicit 'now' argument. Replaces previous direct DateTime.UtcNow usage.
This commit is contained in:
2026-04-18 10:12:17 -03:00
parent 4e1d8f69ab
commit d69da5ff4c
27 changed files with 164 additions and 59 deletions

View File

@@ -22,6 +22,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
private readonly IRolPermisoRepository _rolPermisoRepository;
private readonly ISecurityEventLogger _security;
private readonly ILogger<LoginCommandHandler> _logger;
private readonly TimeProvider _timeProvider;
public LoginCommandHandler(
IUsuarioRepository repository,
@@ -33,7 +34,8 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
AuthOptions authOptions,
IRolPermisoRepository rolPermisoRepository,
ISecurityEventLogger security,
ILogger<LoginCommandHandler> logger)
ILogger<LoginCommandHandler> logger,
TimeProvider timeProvider)
{
_repository = repository;
_hasher = hasher;
@@ -45,6 +47,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
_rolPermisoRepository = rolPermisoRepository;
_security = security;
_logger = logger;
_timeProvider = timeProvider;
}
public async Task<LoginResponseDto> Handle(LoginCommand command)
@@ -81,7 +84,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
// 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 now = _timeProvider.GetUtcNow().UtcDateTime;
var ttl = TimeSpan.FromDays(_authOptions.RefreshTokenDays);
var entity = RefreshToken.IssueForNewFamily(
usuario.Id, hash, now, ttl,

View File

@@ -8,18 +8,24 @@ public sealed class LogoutCommandHandler : ICommandHandler<LogoutCommand, Logout
{
private readonly IRefreshTokenRepository _refreshRepo;
private readonly ISecurityEventLogger _security;
private readonly TimeProvider _timeProvider;
public LogoutCommandHandler(IRefreshTokenRepository refreshRepo, ISecurityEventLogger security)
public LogoutCommandHandler(
IRefreshTokenRepository refreshRepo,
ISecurityEventLogger security,
TimeProvider timeProvider)
{
_refreshRepo = refreshRepo;
_security = security;
_timeProvider = timeProvider;
}
public async Task<LogoutResponseDto> Handle(LogoutCommand command)
{
// Revoke all active tokens for the user across all families.
// Idempotent: 0 rows affected is not an error.
await _refreshRepo.RevokeAllActiveForUserAsync(command.UsuarioId, DateTime.UtcNow);
var now = _timeProvider.GetUtcNow().UtcDateTime;
await _refreshRepo.RevokeAllActiveForUserAsync(command.UsuarioId, now);
await _security.LogAsync("logout", "success", actorUserId: command.UsuarioId);
return new LogoutResponseDto(true, "Sesión cerrada correctamente");
}

View File

@@ -17,6 +17,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
private readonly IClientContext _clientCtx;
private readonly AuthOptions _authOptions;
private readonly ISecurityEventLogger _security;
private readonly TimeProvider _timeProvider;
public RefreshCommandHandler(
IRefreshTokenRepository refreshRepo,
@@ -25,7 +26,8 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
IRefreshTokenGenerator refreshGenerator,
IClientContext clientCtx,
AuthOptions authOptions,
ISecurityEventLogger security)
ISecurityEventLogger security,
TimeProvider timeProvider)
{
_refreshRepo = refreshRepo;
_usuarioRepo = usuarioRepo;
@@ -34,6 +36,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
_clientCtx = clientCtx;
_authOptions = authOptions;
_security = security;
_timeProvider = timeProvider;
}
public async Task<RefreshResponseDto> Handle(RefreshCommand command)
@@ -60,7 +63,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
if (stored is null)
throw new InvalidRefreshTokenException();
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
// 4. Reuse detection: already revoked → chain revocation and throw
if (stored.IsRevoked)