Files
SIG-CM2.0/src/api/SIGCM2.Application/Usuarios/ResetPassword/ResetUsuarioPasswordCommandHandler.cs
dmolinari d69da5ff4c 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.
2026-04-18 10:12:17 -03:00

66 lines
2.5 KiB
C#

using System.Transactions;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Application.Audit;
using SIGCM2.Application.Common;
using SIGCM2.Domain.Exceptions;
namespace SIGCM2.Application.Usuarios.ResetPassword;
public sealed class ResetUsuarioPasswordCommandHandler : ICommandHandler<ResetUsuarioPasswordCommand, ResetUsuarioPasswordResponse>
{
private readonly IUsuarioRepository _repository;
private readonly IPasswordHasher _hasher;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ResetUsuarioPasswordCommandHandler(
IUsuarioRepository repository,
IPasswordHasher hasher,
IRefreshTokenRepository refreshTokenRepository,
IAuditLogger audit,
TimeProvider timeProvider)
{
_repository = repository;
_hasher = hasher;
_refreshTokenRepository = refreshTokenRepository;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<ResetUsuarioPasswordResponse> Handle(ResetUsuarioPasswordCommand cmd)
{
// Cannot self-reset: admin must use /me/password
if (cmd.CallerId == cmd.TargetId)
throw new CannotSelfResetException();
var target = await _repository.GetByIdAsync(cmd.TargetId)
?? throw new UsuarioNotFoundException(cmd.TargetId);
var temp = TempPasswordGenerator.Generate(12);
// SECURITY: NEVER log tempPassword — it is returned to the caller, never persisted.
var hash = _hasher.Hash(temp);
using var tx = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled);
var now = _timeProvider.GetUtcNow().UtcDateTime;
await _repository.UpdatePasswordAsync(cmd.TargetId, hash, mustChangePassword: true);
await _refreshTokenRepository.RevokeAllActiveForUserAsync(cmd.TargetId, now);
await _audit.LogAsync(
action: "usuario.password_reset",
targetType: "Usuario",
targetId: cmd.TargetId.ToString(),
metadata: new { targetId = cmd.TargetId }); // NO tempPassword in metadata
tx.Complete();
return new ResetUsuarioPasswordResponse(temp, MustChangeOnLogin: true);
}
}