Can log actions in JSON format
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		| @@ -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, | ||||
|   | ||||
| @@ -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<ProviderID>, | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -165,7 +165,10 @@ pub async fn users_route( | ||||
|         let factors_to_keep = update.0.two_factor.split(';').collect::<Vec<_>>(); | ||||
|         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()) | ||||
|                 } | ||||
|             }); | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|   | ||||
| @@ -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(( | ||||
|   | ||||
| @@ -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 | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -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<LoggableUser>, | ||||
|     #[serde(flatten)] | ||||
|     action: Action<'a>, | ||||
| } | ||||
|  | ||||
| pub struct ActionLogger { | ||||
|     ip: IpAddr, | ||||
|     user: Option<User>, | ||||
| @@ -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 => {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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<String>, | ||||
|  | ||||
|     /// Action logger output format | ||||
|     #[arg(long, env, default_value_t, value_enum)] | ||||
|     pub action_logger_format: ActionLoggerFormat, | ||||
| } | ||||
|  | ||||
| lazy_static::lazy_static! { | ||||
|   | ||||
| @@ -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<ClientID>), | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -31,28 +31,25 @@ fn hash_password<P: AsRef<[u8]>>(pwd: P) -> Res<String> { | ||||
| } | ||||
|  | ||||
| fn verify_password<P: AsRef<[u8]>>(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<User> { | ||||
|     fn find_by_email(&self, u: &str) -> Res<Option<User>> { | ||||
|     fn find_by_username_or_email(&self, u: &str) -> Res<Option<User>> { | ||||
|         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<Option<User>> { | ||||
|     fn find_by_email(&self, u: &str) -> Res<Option<User>> { | ||||
|         for entry in self.iter() { | ||||
|             if entry.username.eq(u) || entry.email.eq(u) { | ||||
|             if entry.email.eq(u) { | ||||
|                 return Ok(Some(entry.clone())); | ||||
|             } | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user