use std::future::Future; use std::net::IpAddr; use std::pin::Pin; use actix::Addr; use actix_identity::Identity; use actix_web::dev::Payload; use actix_web::{web, Error, FromRequest, HttpRequest}; use crate::actors::users_actor; use crate::actors::users_actor::UsersActor; use crate::data::client::Client; use crate::data::remote_ip::RemoteIP; use crate::data::session_identity::SessionIdentity; use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID}; pub enum Action<'a> { AdminCreateUser(&'a User), AdminUpdateUser(&'a User), AdminDeleteUser(&'a User), AdminResetUserPassword(&'a User), AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients), AdminClear2FAHistory(&'a User), LoginWebauthnAttempt { success: bool, user_id: UserID }, Signout, UserNeed2FAOnLogin(&'a User), UserSuccessfullyAuthenticated(&'a User), UserNeedNewPasswordOnLogin(&'a User), TryLoginWithDisabledAccount(&'a str), FailedLoginWithBadCredentials(&'a str), UserChangedPasswordOnLogin(&'a UserID), OTPLoginAttempt { user: &'a User, success: bool }, NewOpenIDSession { client: &'a Client }, ChangedHisPassword, ClearedHisLoginHistory, AddNewFactor(&'a TwoFactor), Removed2FAFactor { factor_id: &'a FactorID }, } impl<'a> Action<'a> { pub fn as_string(&self) -> String { match self { Action::AdminDeleteUser(user) => { format!("deleted account of {}", user.quick_identity()) } Action::AdminCreateUser(user) => { format!("created account of {}", user.quick_identity()) } Action::AdminUpdateUser(user) => { format!("updated account of {}", user.quick_identity()) } Action::AdminResetUserPassword(user) => { format!( "set a temporary password for the account of {}", user.quick_identity() ) } Action::AdminClear2FAHistory(user) => { format!("cleared 2FA history of {}", user.quick_identity()) } Action::AdminSetNewGrantedClientsList(user, clients) => format!( "set new granted clients list ({:?}) for user ({})", clients, user.quick_identity() ), Action::LoginWebauthnAttempt { success, user_id } => match success { true => format!( "successfully performed webauthn attempt for user {:?}", user_id ), false => format!("performed FAILED webauthn attempt for user {:?}", user_id), }, 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) => { format!("successfully authenticated as {}", user.quick_identity()) } Action::UserNeedNewPasswordOnLogin(user) => format!( "successfully authenticated as {}, but need to set a new password", user.quick_identity() ), Action::TryLoginWithDisabledAccount(login) => format!( "successfully authenticated as {}, but this is a DISABLED ACCOUNT", login ), Action::FailedLoginWithBadCredentials(login) => format!( "attempted to authenticate as {} but with a WRONG PASSWORD", login ), Action::UserChangedPasswordOnLogin(user_id) => { format!("set a new password at login as user {:?}", user_id) } Action::OTPLoginAttempt { user, success } => match success { true => format!( "successfully performed OTP attempt for user {}", user.quick_identity() ), false => format!( "performed FAILED OTP attempt for user {}", user.quick_identity() ), }, Action::NewOpenIDSession { client } => { format!("opened a new OpenID session with {:?}", client.id) } Action::ChangedHisPassword => "changed his password".to_string(), Action::ClearedHisLoginHistory => "cleared his login history".to_string(), Action::AddNewFactor(factor) => format!( "added a new {} factor with name {} and id {:?} to his account", factor.type_str(), factor.name, factor.id, ), Action::Removed2FAFactor { factor_id } => format!("Removed his factor {:?}", factor_id), } } } pub struct ActionLogger { ip: IpAddr, user: Option, } 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(), }, self.ip.to_string(), action.as_string() ) } } impl FromRequest for ActionLogger { type Error = Error; type Future = Pin>>>; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let req = req.clone(); Box::pin(async move { let user_actor: &web::Data> = req.app_data().expect("UserActor undefined!"); let user_actor: Addr = user_actor.as_ref().clone(); let user_id = Identity::from_request(&req, &mut Payload::None) .into_inner() .ok() .and_then(|id| { let sess = SessionIdentity(Some(&id)); match sess.is_authenticated() { true => Some(sess.user_id()), false => None, } }); Ok(Self { ip: RemoteIP::from_request(&req, &mut Payload::None) .await .unwrap() .0, user: match user_id { None => None, Some(u) => { user_actor .send(users_actor::GetUserRequest(u)) .await .unwrap() .0 } }, }) }) } }