Log all user actions on stdout
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:
180
src/data/action_logger.rs
Normal file
180
src/data/action_logger.rs
Normal file
@ -0,0 +1,180 @@
|
||||
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, TwoFactor, User, UserID};
|
||||
|
||||
pub enum Action<'a> {
|
||||
AdminCreateUser(&'a User),
|
||||
AdminUpdateUser(&'a User),
|
||||
AdminDeleteUser(&'a User),
|
||||
AdminResetUserPassword(&'a User),
|
||||
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::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<User>,
|
||||
}
|
||||
|
||||
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<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let req = req.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let user_actor: &web::Data<Addr<UsersActor>> =
|
||||
req.app_data().expect("UserActor undefined!");
|
||||
|
||||
let user_actor: Addr<UsersActor> = 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
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod access_token;
|
||||
pub mod action_logger;
|
||||
pub mod app_config;
|
||||
pub mod client;
|
||||
pub mod code_challenge;
|
||||
|
@ -113,6 +113,19 @@ 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
|
||||
)
|
||||
}
|
||||
|
||||
pub fn can_access_app(&self, id: &ClientID) -> bool {
|
||||
match &self.authorized_clients {
|
||||
None => true,
|
||||
|
Reference in New Issue
Block a user