From 2a729d415378d5908fa6d1f3984d8f6db2885d4f Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 28 Oct 2025 11:43:07 +0100 Subject: [PATCH] Can log actions in JSON format --- src/actors/providers_states_actor.rs | 2 +- src/actors/users_actor.rs | 2 +- src/controllers/admin_api.rs | 4 +- src/controllers/admin_controller.rs | 37 ++-- src/controllers/login_controller.rs | 26 +-- src/controllers/openid_controller.rs | 4 +- src/controllers/providers_controller.rs | 10 +- src/controllers/two_factor_api.rs | 8 +- src/data/action_logger.rs | 229 ++++++++++++++++++------ src/data/app_config.rs | 13 ++ src/data/user.rs | 24 +-- src/data/users_file_entity.rs | 19 +- 12 files changed, 261 insertions(+), 117 deletions(-) diff --git a/src/actors/providers_states_actor.rs b/src/actors/providers_states_actor.rs index fd3e644..e2b39ca 100644 --- a/src/actors/providers_states_actor.rs +++ b/src/actors/providers_states_actor.rs @@ -17,7 +17,7 @@ use crate::data::provider::ProviderID; use crate::utils::string_utils::rand_str; use crate::utils::time::time; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize)] pub struct ProviderLoginState { pub provider_id: ProviderID, pub state_id: String, diff --git a/src/actors/users_actor.rs b/src/actors/users_actor.rs index 7f6e532..3bc11c0 100644 --- a/src/actors/users_actor.rs +++ b/src/actors/users_actor.rs @@ -104,7 +104,7 @@ pub struct AddSuccessful2FALogin(pub UserID, pub IpAddr); #[rtype(result = "bool")] pub struct Clear2FALoginHistory(pub UserID); -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, serde::Serialize)] pub struct AuthorizedAuthenticationSources { pub local: bool, pub upstream: Vec, diff --git a/src/controllers/admin_api.rs b/src/controllers/admin_api.rs index 319af7d..cc94ebe 100644 --- a/src/controllers/admin_api.rs +++ b/src/controllers/admin_api.rs @@ -65,7 +65,9 @@ pub async fn delete_user( let res = users.send(DeleteUserRequest(req.0.user_id)).await.unwrap(); if res { - action_logger.log(Action::AdminDeleteUser(&user)); + action_logger.log(Action::AdminDeleteUser { + user: user.loggable(), + }); HttpResponse::Ok().finish() } else { HttpResponse::InternalServerError().finish() diff --git a/src/controllers/admin_controller.rs b/src/controllers/admin_controller.rs index d59fbcf..e0fda4e 100644 --- a/src/controllers/admin_controller.rs +++ b/src/controllers/admin_controller.rs @@ -165,7 +165,10 @@ pub async fn users_route( let factors_to_keep = update.0.two_factor.split(';').collect::>(); for factor in &edited_user.two_factor { if !factors_to_keep.contains(&factor.id.0.as_str()) { - logger.log(Action::AdminRemoveUserFactor(&edited_user, factor)); + logger.log(Action::AdminRemoveUserFactor { + user: edited_user.loggable(), + factor: factor.loggable(), + }); users .send(users_actor::Remove2FAFactor( edited_user.uid.clone(), @@ -186,10 +189,10 @@ pub async fn users_route( }; if edited_user.authorized_authentication_sources() != auth_sources { - logger.log(Action::AdminSetAuthorizedAuthenticationSources( - &edited_user, - &auth_sources, - )); + logger.log(Action::AdminSetAuthorizedAuthenticationSources { + user: edited_user.loggable(), + sources: &auth_sources, + }); users .send(users_actor::SetAuthorizedAuthenticationSources( edited_user.uid.clone(), @@ -216,10 +219,10 @@ pub async fn users_route( }; if edited_user.granted_clients() != granted_clients { - logger.log(Action::AdminSetNewGrantedClientsList( - &edited_user, - &granted_clients, - )); + logger.log(Action::AdminSetNewGrantedClientsList { + user: edited_user.loggable(), + clients: &granted_clients, + }); users .send(users_actor::SetGrantedClients( edited_user.uid.clone(), @@ -231,7 +234,9 @@ pub async fn users_route( // Clear user 2FA history if requested if update.0.clear_2fa_history.is_some() { - logger.log(Action::AdminClear2FAHistory(&edited_user)); + logger.log(Action::AdminClear2FAHistory { + user: edited_user.loggable(), + }); users .send(users_actor::Clear2FALoginHistory(edited_user.uid.clone())) .await @@ -242,7 +247,9 @@ pub async fn users_route( let new_password = match update.0.gen_new_password.is_some() { false => None, true => { - logger.log(Action::AdminResetUserPassword(&edited_user)); + logger.log(Action::AdminResetUserPassword { + user: edited_user.loggable(), + }); let temp_pass = rand_str(TEMPORARY_PASSWORDS_LEN); users @@ -269,11 +276,15 @@ pub async fn users_route( } else { success = Some(match is_creating { true => { - logger.log(Action::AdminCreateUser(&edited_user)); + logger.log(Action::AdminCreateUser { + user: edited_user.loggable(), + }); format!("User {} was successfully created!", edited_user.full_name()) } false => { - logger.log(Action::AdminUpdateUser(&edited_user)); + logger.log(Action::AdminUpdateUser { + user: edited_user.loggable(), + }); format!("User {} was successfully updated!", edited_user.full_name()) } }); diff --git a/src/controllers/login_controller.rs b/src/controllers/login_controller.rs index e797d25..04a8136 100644 --- a/src/controllers/login_controller.rs +++ b/src/controllers/login_controller.rs @@ -111,7 +111,7 @@ pub async fn login_route( // Check if user session must be closed if let Some(true) = query.logout { if let Some(id) = id { - logger.log(Action::Signout); + logger.log(Action::SignOut); id.logout(); } success = Some("Goodbye!".to_string()); @@ -155,14 +155,20 @@ pub async fn login_route( match response { LoginResult::Success(user) => { let status = if user.need_reset_password { - logger.log(Action::UserNeedNewPasswordOnLogin(&user)); + logger.log(Action::UserNeedNewPasswordOnLogin { + user: user.loggable(), + }); SessionStatus::NeedNewPassword } else if user.has_two_factor() && !user.can_bypass_two_factors_for_ip(remote_ip.0) { - logger.log(Action::UserNeed2FAOnLogin(&user)); + logger.log(Action::UserNeed2FAOnLogin { + user: user.loggable(), + }); SessionStatus::Need2FA } else { - logger.log(Action::UserSuccessfullyAuthenticated(&user)); + logger.log(Action::UserSuccessfullyAuthenticated { + user: user.loggable(), + }); SessionStatus::SignedIn }; @@ -172,7 +178,7 @@ pub async fn login_route( LoginResult::AccountDisabled => { log::warn!("Failed login for username {} : account is disabled", &login); - logger.log(Action::TryLoginWithDisabledAccount(&login)); + logger.log(Action::TryLoginWithDisabledAccount { login: &login }); danger = Some("Your account is disabled!".to_string()); } @@ -181,7 +187,7 @@ pub async fn login_route( "Failed login for username {} : attempted to use local auth, but it is forbidden", &login ); - logger.log(Action::TryLocalLoginFromUnauthorizedAccount(&login)); + logger.log(Action::TryLocalLoginFromUnauthorizedAccount { login: &login }); danger = Some("You cannot login from local auth with your account!".to_string()); } @@ -191,7 +197,7 @@ pub async fn login_route( c => { log::warn!("Failed login for ip {remote_ip:?} / username {login}: {c:?}"); - logger.log(Action::FailedLoginWithBadCredentials(&login)); + logger.log(Action::FailedLoginWithBadCredentials { login: &login }); danger = Some("Login failed.".to_string()); bruteforce @@ -272,7 +278,7 @@ pub async fn reset_password_route( danger = Some("Failed to change password!".to_string()); } else { SessionIdentity(id.as_ref()).set_status(&http_req, SessionStatus::SignedIn); - logger.log(Action::UserChangedPasswordOnLogin(&user_id)); + logger.log(Action::UserChangedPasswordOnLogin { user_id: &user_id }); return redirect_user(query.redirect.get()); } } @@ -395,7 +401,7 @@ pub async fn login_with_otp( { logger.log(Action::OTPLoginAttempt { success: false, - user: &user, + user: user.loggable(), }); danger = Some("Specified code is invalid!".to_string()); } else { @@ -412,7 +418,7 @@ pub async fn login_with_otp( session.set_status(&http_req, SessionStatus::SignedIn); logger.log(Action::OTPLoginAttempt { success: true, - user: &user, + user: user.loggable(), }); return redirect_user(query.redirect.get()); } diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index 565ef68..df88589 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -239,7 +239,7 @@ pub async fn authorize( .unwrap(); log::trace!("New OpenID session: {session:#?}"); - logger.log(Action::NewOpenIDSession { client: &client }); + logger.log(Action::NewOpenIDSession { client: &client.id }); Ok(HttpResponse::Found() .append_header(( @@ -273,7 +273,7 @@ pub async fn authorize( }; log::trace!("New OpenID id token: {:#?}", &id_token); - logger.log(Action::NewOpenIDSuccessfulImplicitAuth { client: &client }); + logger.log(Action::NewOpenIDSuccessfulImplicitAuth { client: &client.id }); Ok(HttpResponse::Found() .append_header(( diff --git a/src/controllers/providers_controller.rs b/src/controllers/providers_controller.rs index bcaae75..53964e9 100644 --- a/src/controllers/providers_controller.rs +++ b/src/controllers/providers_controller.rs @@ -357,14 +357,18 @@ pub async fn finish_login( logger.log(Action::ProviderLoginSuccessful { provider: &provider, - user: &user, + user: user.loggable(), }); let status = if user.has_two_factor() && !user.can_bypass_two_factors_for_ip(remote_ip.0) { - logger.log(Action::UserNeed2FAOnLogin(&user)); + logger.log(Action::UserNeed2FAOnLogin { + user: user.loggable(), + }); SessionStatus::Need2FA } else { - logger.log(Action::UserSuccessfullyAuthenticated(&user)); + logger.log(Action::UserSuccessfullyAuthenticated { + user: user.loggable(), + }); SessionStatus::SignedIn }; diff --git a/src/controllers/two_factor_api.rs b/src/controllers/two_factor_api.rs index 2d3eec8..c3624c7 100644 --- a/src/controllers/two_factor_api.rs +++ b/src/controllers/two_factor_api.rs @@ -57,7 +57,9 @@ pub async fn save_totp_factor( name: factor_name, kind: TwoFactorType::TOTP(key), }; - logger.log(Action::AddNewFactor(&factor)); + logger.log(Action::AddNewFactor { + factor: factor.loggable(), + }); let res = users .send(users_actor::Add2FAFactor(user.uid.clone(), factor)) @@ -104,7 +106,9 @@ pub async fn save_webauthn_factor( name: factor_name, kind: TwoFactorType::WEBAUTHN(Box::new(key)), }; - logger.log(Action::AddNewFactor(&factor)); + logger.log(Action::AddNewFactor { + factor: factor.loggable(), + }); let res = users .send(users_actor::Add2FAFactor(user.uid.clone(), factor)) diff --git a/src/data/action_logger.rs b/src/data/action_logger.rs index fae8226..a720c5d 100644 --- a/src/data/action_logger.rs +++ b/src/data/action_logger.rs @@ -11,21 +11,113 @@ use actix_web::{Error, FromRequest, HttpRequest, web}; use crate::actors::providers_states_actor::ProviderLoginState; use crate::actors::users_actor; use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor}; -use crate::data::client::Client; +use crate::data::app_config::{ActionLoggerFormat, AppConfig}; +use crate::data::client::ClientID; use crate::data::provider::{Provider, ProviderID}; use crate::data::session_identity::SessionIdentity; -use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID}; +use crate::data::user::{FactorID, GrantedClients, TwoFactor, TwoFactorType, User, UserID}; +use crate::utils::time::time; +#[derive(serde::Serialize)] +pub struct LoggableUser { + pub uid: UserID, + pub username: String, + pub email: String, + pub admin: bool, +} + +impl LoggableUser { + pub fn quick_identity(&self) -> String { + format!( + "{} {} {} ({:?})", + match self.admin { + true => "admin", + false => "user", + }, + self.username, + self.email, + self.uid + ) + } +} + +impl User { + pub fn loggable(&self) -> LoggableUser { + LoggableUser { + uid: self.uid.clone(), + username: self.username.clone(), + email: self.email.clone(), + admin: self.admin, + } + } +} + +#[derive(Debug, serde::Serialize)] +pub enum LoggableFactorType { + TOTP, + WEBAUTHN, +} + +#[derive(serde::Serialize)] +pub struct LoggableFactor { + pub id: FactorID, + pub name: String, + pub kind: LoggableFactorType, +} + +impl LoggableFactor { + pub fn quick_description(&self) -> String { + format!( + "#{} of type {:?} and name '{}'", + self.id.0, self.kind, self.name + ) + } +} + +impl TwoFactor { + pub fn loggable(&self) -> LoggableFactor { + LoggableFactor { + id: self.id.clone(), + name: self.name.to_string(), + kind: match self.kind { + TwoFactorType::TOTP(_) => LoggableFactorType::TOTP, + TwoFactorType::WEBAUTHN(_) => LoggableFactorType::WEBAUTHN, + }, + } + } +} + +#[derive(serde::Serialize)] +#[serde(tag = "type")] pub enum Action<'a> { - AdminCreateUser(&'a User), - AdminUpdateUser(&'a User), - AdminDeleteUser(&'a User), - AdminResetUserPassword(&'a User), - AdminRemoveUserFactor(&'a User, &'a TwoFactor), - AdminSetAuthorizedAuthenticationSources(&'a User, &'a AuthorizedAuthenticationSources), - AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients), - AdminClear2FAHistory(&'a User), + AdminCreateUser { + user: LoggableUser, + }, + AdminUpdateUser { + user: LoggableUser, + }, + AdminDeleteUser { + user: LoggableUser, + }, + AdminResetUserPassword { + user: LoggableUser, + }, + AdminRemoveUserFactor { + user: LoggableUser, + factor: LoggableFactor, + }, + AdminSetAuthorizedAuthenticationSources { + user: LoggableUser, + sources: &'a AuthorizedAuthenticationSources, + }, + AdminSetNewGrantedClientsList { + user: LoggableUser, + clients: &'a GrantedClients, + }, + AdminClear2FAHistory { + user: LoggableUser, + }, LoginWebauthnAttempt { success: bool, user_id: UserID, @@ -73,29 +165,45 @@ pub enum Action<'a> { }, ProviderLoginSuccessful { provider: &'a Provider, - user: &'a User, + user: LoggableUser, + }, + SignOut, + UserNeed2FAOnLogin { + user: LoggableUser, + }, + UserSuccessfullyAuthenticated { + user: LoggableUser, + }, + UserNeedNewPasswordOnLogin { + user: LoggableUser, + }, + TryLoginWithDisabledAccount { + login: &'a str, + }, + TryLocalLoginFromUnauthorizedAccount { + login: &'a str, + }, + FailedLoginWithBadCredentials { + login: &'a str, + }, + UserChangedPasswordOnLogin { + user_id: &'a UserID, }, - Signout, - UserNeed2FAOnLogin(&'a User), - UserSuccessfullyAuthenticated(&'a User), - UserNeedNewPasswordOnLogin(&'a User), - TryLoginWithDisabledAccount(&'a str), - TryLocalLoginFromUnauthorizedAccount(&'a str), - FailedLoginWithBadCredentials(&'a str), - UserChangedPasswordOnLogin(&'a UserID), OTPLoginAttempt { - user: &'a User, + user: LoggableUser, success: bool, }, NewOpenIDSession { - client: &'a Client, + client: &'a ClientID, }, NewOpenIDSuccessfulImplicitAuth { - client: &'a Client, + client: &'a ClientID, }, ChangedHisPassword, ClearedHisLoginHistory, - AddNewFactor(&'a TwoFactor), + AddNewFactor { + factor: LoggableFactor, + }, Removed2FAFactor { factor_id: &'a FactorID, }, @@ -104,35 +212,35 @@ pub enum Action<'a> { impl Action<'_> { pub fn as_string(&self) -> String { match self { - Action::AdminDeleteUser(user) => { + Action::AdminDeleteUser { user } => { format!("deleted account of {}", user.quick_identity()) } - Action::AdminCreateUser(user) => { + Action::AdminCreateUser { user } => { format!("created account of {}", user.quick_identity()) } - Action::AdminUpdateUser(user) => { + Action::AdminUpdateUser { user } => { format!("updated account of {}", user.quick_identity()) } - Action::AdminResetUserPassword(user) => { + Action::AdminResetUserPassword { user } => { format!( "set a temporary password for the account of {}", user.quick_identity() ) } - Action::AdminRemoveUserFactor(user, factor) => format!( + Action::AdminRemoveUserFactor { user, factor } => format!( "removed 2 factor ({}) of user ({})", factor.quick_description(), user.quick_identity() ), - Action::AdminClear2FAHistory(user) => { + Action::AdminClear2FAHistory { user } => { format!("cleared 2FA history of {}", user.quick_identity()) } - Action::AdminSetAuthorizedAuthenticationSources(user, sources) => format!( + Action::AdminSetAuthorizedAuthenticationSources { user, sources } => format!( "update authorized authentication sources ({:?}) for user ({})", sources, user.quick_identity() ), - Action::AdminSetNewGrantedClientsList(user, clients) => format!( + Action::AdminSetNewGrantedClientsList { user, clients } => format!( "set new granted clients list ({:?}) for user ({})", clients, user.quick_identity() @@ -191,32 +299,32 @@ impl Action<'_> { provider.id.0, user.quick_identity() ), - Action::Signout => "signed out".to_string(), - Action::UserNeed2FAOnLogin(user) => { + Action::SignOut => "signed out".to_string(), + Action::UserNeed2FAOnLogin { user } => { format!( "successfully authenticated as user {:?} but need to do 2FA authentication", user.quick_identity() ) } - Action::UserSuccessfullyAuthenticated(user) => { + Action::UserSuccessfullyAuthenticated { user } => { format!("successfully authenticated as {}", user.quick_identity()) } - Action::UserNeedNewPasswordOnLogin(user) => format!( + Action::UserNeedNewPasswordOnLogin { user } => format!( "successfully authenticated as {}, but need to set a new password", user.quick_identity() ), - Action::TryLoginWithDisabledAccount(login) => { + Action::TryLoginWithDisabledAccount { login } => { format!("successfully authenticated as {login}, but this is a DISABLED ACCOUNT") } - Action::TryLocalLoginFromUnauthorizedAccount(login) => { + Action::TryLocalLoginFromUnauthorizedAccount { login } => { format!( "successfully locally authenticated as {login}, but this is a FORBIDDEN for this account!" ) } - Action::FailedLoginWithBadCredentials(login) => { + Action::FailedLoginWithBadCredentials { login } => { format!("attempted to authenticate as {login} but with a WRONG PASSWORD") } - Action::UserChangedPasswordOnLogin(user_id) => { + Action::UserChangedPasswordOnLogin { user_id } => { format!("set a new password at login as user {user_id:?}") } Action::OTPLoginAttempt { user, success } => match success { @@ -230,15 +338,15 @@ impl Action<'_> { ), }, Action::NewOpenIDSession { client } => { - format!("opened a new OpenID session with {:?}", client.id) + format!("opened a new OpenID session with {:?}", client) } Action::NewOpenIDSuccessfulImplicitAuth { client } => format!( "finished an implicit flow connection for client {:?}", - client.id + client ), Action::ChangedHisPassword => "changed his password".to_string(), Action::ClearedHisLoginHistory => "cleared his login history".to_string(), - Action::AddNewFactor(factor) => format!( + Action::AddNewFactor { factor } => format!( "added a new factor to his account : {}", factor.quick_description(), ), @@ -247,6 +355,15 @@ impl Action<'_> { } } +#[derive(serde::Serialize)] +struct JsonActionData<'a> { + time: u64, + ip: IpAddr, + user: Option, + #[serde(flatten)] + action: Action<'a>, +} + pub struct ActionLogger { ip: IpAddr, user: Option, @@ -254,15 +371,27 @@ pub struct ActionLogger { impl ActionLogger { pub fn log(&self, action: Action) { - log::info!( - "{} from {} has {}", - match &self.user { - None => "Anonymous user".to_string(), - Some(u) => u.quick_identity(), + match AppConfig::get().action_logger_format { + ActionLoggerFormat::Text => log::info!( + "{} from {} has {}", + match &self.user { + None => "Anonymous user".to_string(), + Some(u) => u.loggable().quick_identity(), + }, + self.ip, + action.as_string() + ), + ActionLoggerFormat::Json => match serde_json::to_string(&JsonActionData { + time: time(), + ip: self.ip, + user: self.user.as_ref().map(User::loggable), + action, + }) { + Ok(j) => println!("{j}"), + Err(e) => log::error!("Failed to serialize event to JSON! {e}"), }, - self.ip, - action.as_string() - ) + ActionLoggerFormat::None => {} + } } } diff --git a/src/data/app_config.rs b/src/data/app_config.rs index 67b1ac9..06085f4 100644 --- a/src/data/app_config.rs +++ b/src/data/app_config.rs @@ -6,6 +6,15 @@ use crate::constants::{ APP_NAME, CLIENTS_LIST_FILE, OIDC_PROVIDER_CB_URI, PROVIDERS_LIST_FILE, USERS_LIST_FILE, }; +/// Action logger format +#[derive(Copy, Clone, Eq, PartialEq, Debug, clap::ValueEnum, Default)] +pub enum ActionLoggerFormat { + #[default] + Text, + Json, + None, +} + /// Basic OIDC provider #[derive(Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] @@ -45,6 +54,10 @@ pub struct AppConfig { /// Example: "https://api.geoip.rs" #[arg(long, short, env)] pub ip_location_service: Option, + + /// Action logger output format + #[arg(long, env, default_value_t, value_enum)] + pub action_logger_format: ActionLoggerFormat, } lazy_static::lazy_static! { diff --git a/src/data/user.rs b/src/data/user.rs index f3854fa..680d22e 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -26,7 +26,7 @@ pub struct GeneralSettings { pub is_admin: bool, } -#[derive(Eq, PartialEq, Clone, Debug)] +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize)] pub enum GrantedClients { AllClients, SomeClients(Vec), @@ -60,15 +60,6 @@ pub struct TwoFactor { } impl TwoFactor { - pub fn quick_description(&self) -> String { - format!( - "#{} of type {} and name '{}'", - self.id.0, - self.type_str(), - self.name - ) - } - pub fn type_str(&self) -> &'static str { match self.kind { TwoFactorType::TOTP(_) => "Authenticator app", @@ -170,19 +161,6 @@ impl User { format!("{} {}", self.first_name, self.last_name) } - pub fn quick_identity(&self) -> String { - format!( - "{} {} {} ({:?})", - match self.admin { - true => "admin", - false => "user", - }, - self.username, - self.email, - self.uid - ) - } - /// Get the list of sources from which a user can authenticate from pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources { AuthorizedAuthenticationSources { diff --git a/src/data/users_file_entity.rs b/src/data/users_file_entity.rs index d7795df..f0a4fd6 100644 --- a/src/data/users_file_entity.rs +++ b/src/data/users_file_entity.rs @@ -31,28 +31,25 @@ fn hash_password>(pwd: P) -> Res { } fn verify_password>(pwd: P, hash: &str) -> bool { - match bcrypt::verify(pwd, hash) { - Ok(r) => r, - Err(e) => { - log::warn!("Failed to verify password! {e:?}"); - false - } - } + bcrypt::verify(pwd, hash).unwrap_or_else(|e| { + log::warn!("Failed to verify password! {e:?}"); + false + }) } impl UsersSyncBackend for EntityManager { - fn find_by_email(&self, u: &str) -> Res> { + fn find_by_username_or_email(&self, u: &str) -> Res> { for entry in self.iter() { - if entry.email.eq(u) { + if entry.username.eq(u) || entry.email.eq(u) { return Ok(Some(entry.clone())); } } Ok(None) } - fn find_by_username_or_email(&self, u: &str) -> Res> { + fn find_by_email(&self, u: &str) -> Res> { for entry in self.iter() { - if entry.username.eq(u) || entry.email.eq(u) { + if entry.email.eq(u) { return Ok(Some(entry.clone())); } }