using AutoMapper; using MediatR; using mws.backend.dotnet.application.Common; using mws.backend.dotnet.application.Users; using mws.backend.dotnet.domain.Audit; namespace mws.backend.dotnet.application.Auth; public record LoginCommand(LoginRequest Request) : IRequest; public class LoginHandler(IUnitOfWork uow, IPasswordHasher passwordHasher, ITokenService tokenService, IMapper mapper, ICurrentUser currentUser) : IRequestHandler { public async Task Handle(LoginCommand command, CancellationToken ct) { var username = command.Request.Username.Trim(); var user = await uow.Users.GetByUsernameWithRoleAsync(username, ct); if (user is null) { await WriteLoginLogAsync(username, null, false, "Unknown username", ct); throw new UnauthorizedException("Invalid username or password"); } if (!passwordHasher.Verify(command.Request.Password, user.PasswordHash)) { await WriteLoginLogAsync(username, user.Id, false, "Invalid password", ct); throw new UnauthorizedException("Invalid username or password"); } if (!user.IsActive) { await WriteLoginLogAsync(username, user.Id, false, "Account disabled", ct); throw new ForbiddenException("Account disabled"); } await WriteLoginLogAsync(username, user.Id, true, null, ct); return new LoginResponse { Token = tokenService.CreateToken(user.Id, user.Username), User = mapper.Map(user), }; } private async Task WriteLoginLogAsync(string username, Guid? userId, bool success, string? failureReason, CancellationToken ct) { uow.LoginLogs.Add(new LoginLog { Id = Guid.NewGuid(), Username = username.Length <= 100 ? username : username[..100], UserId = userId, Success = success, FailureReason = failureReason, IpAddress = currentUser.IpAddress, UserAgent = currentUser.UserAgent, CreatedAt = DateTimeOffset.UtcNow, }); await uow.SaveChangesAsync(ct); } }