using Domain.Entities; using Domain.Exceptions; using Infrastructure.PasswordHashing; using Infrastructure.Repository.Auth; namespace Service.Auth; /// /// Handles authenticating users by verifying their credentials and issuing access/refresh tokens. /// /// Repository used to look up user accounts and their active credentials. /// Infrastructure component used to verify a plain-text password against a stored hash. /// Service used to generate access and refresh tokens for an authenticated user. public class LoginService( IAuthRepository authRepo, IPasswordInfrastructure passwordInfrastructure, ITokenService tokenService ) : ILoginService { /// /// Authenticates a user by username and password, issuing a new access and refresh token on success. /// /// The username of the account to authenticate. /// The plain-text password to verify against the stored credential. /// A containing the authenticated user and issued tokens. /// /// 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 LoginAsync( string username, string password ) { // Attempt lookup by username // the user was not found var user = await authRepo.GetUserByUsernameAsync(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(password, activeCred.Hash)) throw new UnauthorizedException("Invalid username or password."); string accessToken = tokenService.GenerateAccessToken(user); string refreshToken = tokenService.GenerateRefreshToken(user); return new LoginServiceReturn(user, refreshToken, accessToken); } }