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::providers_states_actor::ProviderLoginState; use crate::actors::users_actor; use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor}; use crate::data::client::Client; use crate::data::provider::{Provider, ProviderID}; 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), AdminRemoveUserFactor(&'a User, &'a TwoFactor), AdminSetAuthorizedAuthenticationSources(&'a User, &'a AuthorizedAuthenticationSources), AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients), AdminClear2FAHistory(&'a User), LoginWebauthnAttempt { success: bool, user_id: UserID, }, StartLoginAttemptWithOpenIDProvider { provider_id: &'a ProviderID, state: &'a str, }, ProviderError { message: &'a str, }, ProviderCBInvalidState { state: &'a str, }, ProviderRateLimited, ProviderFailedGetToken { state: &'a ProviderLoginState, code: &'a str, }, ProviderFailedGetUserInfo { provider: &'a Provider, }, ProviderEmailNotValidated { provider: &'a Provider, }, ProviderMissingEmailInResponse { provider: &'a Provider, }, ProviderAccountNotFound { provider: &'a Provider, email: &'a str, }, ProviderAccountDisabled { provider: &'a Provider, email: &'a str, }, ProviderAccountNotAllowedToLoginWithProvider { provider: &'a Provider, email: &'a str, }, ProviderLoginFailed { provider: &'a Provider, email: &'a str, }, 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, 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::AdminRemoveUserFactor(user, factor) => format!( "removed 2 factor ({}) of user ({})", factor.quick_description(), user.quick_identity() ), Action::AdminClear2FAHistory(user) => { format!("cleared 2FA history of {}", user.quick_identity()) } Action::AdminSetAuthorizedAuthenticationSources(user, sources) => format!( "update authorized authentication sources ({:?}) for user ({})", sources, 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::StartLoginAttemptWithOpenIDProvider { provider_id, state } => format!( "started new authentication attempt through an OpenID provider (prov={} / state={state})", provider_id.0 ), Action::ProviderError { message } => format!("failed provider authentication with message '{message}'"), Action::ProviderCBInvalidState { state } => format!("provided invalid callback state after provider authentication: '{state}'"), Action::ProviderRateLimited => "could not complete OpenID login because it has reached failed attempts rate limit!".to_string(), Action::ProviderFailedGetToken {state, code} => format!("could not complete login from provider because the id_token could not be retrieved! (state={:?} code = {code})",state), Action::ProviderFailedGetUserInfo {provider} => format!("could not get user information from userinfo endpoint of provider {}!", provider.id.0), Action::ProviderEmailNotValidated {provider}=>format!("could not login using provider {} because its email was marked as not validated!", provider.id.0), Action::ProviderMissingEmailInResponse {provider}=>format!("could not login using provider {} because the email was not provided by userinfo endpoint!", provider.id.0), Action::ProviderAccountNotFound { provider, email } => format!("could not login using provider {} because the email {email} could not be associated to any account!", &provider.id.0), Action::ProviderAccountDisabled { provider, email } => format!("could not login using provider {} because the account associated to the email {email} is disabled!", &provider.id.0), Action::ProviderAccountNotAllowedToLoginWithProvider { provider, email } => format!("could not login using provider {} because the account associated to the email {email} is not allowed to authenticate using this provider!", &provider.id.0), Action::ProviderLoginFailed { provider, email } => format!("could not login using provider {} with the email {email} for an unknown reason!", &provider.id.0), 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 {login}, but this is a DISABLED ACCOUNT") } Action::TryLocalLoginFromUnauthorizedAccount(login) => { format!("successfully locally authenticated as {login}, but this is a FORBIDDEN for this account!") } Action::FailedLoginWithBadCredentials(login) => { format!("attempted to authenticate as {login} but with a WRONG PASSWORD") } 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 to his account : {}", factor.quick_description(), ), 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 } }, }) }) } }