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 { 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 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); } }