use domain::{AuthPort, PasswordHashPort, User, UserRepository}; pub enum AuthError { InvalidCredentials, RegistrationClosed, Repository(E), Hash(String), } impl std::fmt::Display for AuthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidCredentials => write!(f, "invalid credentials"), Self::RegistrationClosed => write!(f, "registration closed (users already exist)"), Self::Repository(e) => write!(f, "repository error: {e:?}"), Self::Hash(e) => write!(f, "hash error: {e}"), } } } pub async fn login( config: &C, auth: &A, hasher: &H, username: &str, password: &str, ) -> Result> where C: UserRepository, A: AuthPort, H: PasswordHashPort, { let user = config .get_user_by_username(username) .await .map_err(AuthError::Repository)? .ok_or(AuthError::InvalidCredentials)?; let valid = hasher .verify(password, &user.password_hash) .await .map_err(AuthError::Hash)?; if !valid { return Err(AuthError::InvalidCredentials); } Ok(auth.generate_token(user.id)) } pub async fn register( config: &C, hasher: &H, username: &str, password: &str, ) -> Result<(), AuthError> where C: UserRepository, H: PasswordHashPort, { let count = config.count_users().await.map_err(AuthError::Repository)?; if count > 0 { return Err(AuthError::RegistrationClosed); } let hash = hasher.hash(password).await.map_err(AuthError::Hash)?; let user = User { id: 0, username: username.to_string(), password_hash: hash, }; config .save_user(&user) .await .map_err(AuthError::Repository)?; Ok(()) }