using Domain.Exceptions; using Features.Auth.Dtos; using Features.Auth.Repository; using Features.Auth.Services; using Infrastructure.PasswordHashing; using MediatR; namespace Features.Auth.Queries.Login; /// /// Handles by verifying credentials and issuing access/refresh tokens. /// /// Repository used to look up the user account and its active credential. /// Infrastructure component used to verify a plain-text password against a stored hash. /// Service used to generate access and refresh tokens for the authenticated user. 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(LoginQuery request, CancellationToken cancellationToken) { var user = await authRepo.GetUserByUsernameAsync(request.Username) ?? throw new UnauthorizedException("Invalid username or password."); // @todo handle expired passwords var 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."); var accessToken = tokenService.GenerateAccessToken(user); var refreshToken = tokenService.GenerateRefreshToken(user); return new LoginPayload(user.UserAccountId, user.Username, refreshToken, accessToken); } }