using Domain.Entities; using Domain.Exceptions; using Features.Auth.Dtos; using Features.Auth.Repository; using Features.Auth.Services; using Infrastructure.PasswordHashing; using MediatR; namespace Features.Auth.Commands.Login; /// /// Handles by verifying credentials and issuing access/refresh tokens. /// public class LoginHandler( IAuthRepository authRepo, IPasswordInfrastructure passwordInfrastructure, ITokenService tokenService ) : IRequestHandler { /// /// Thrown when the username does not match any account, the account has no active credential, /// or the supplied password does not match the stored hash. /// public async Task Handle(LoginCommand request, CancellationToken cancellationToken) { UserAccount user = await authRepo.GetUserByUsernameAsync(request.Username) ?? throw new UnauthorizedException("Invalid username or password."); // @todo handle expired passwords UserCredential activeCred = await authRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId) ?? throw new UnauthorizedException("Invalid username or password."); if (!passwordInfrastructure.Verify(request.Password, activeCred.Hash)) throw new UnauthorizedException("Invalid username or password."); string accessToken = tokenService.GenerateAccessToken(user.UserAccountId, user.Username); string refreshToken = tokenService.GenerateRefreshToken(user.UserAccountId, user.Username); return new LoginPayload(user.UserAccountId, user.Username, refreshToken, accessToken); } }