From 049781400460ebdd19577fa705af17e848f9ecef Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 29 Jun 2026 16:04:24 +0800 Subject: [PATCH] Add authentication details to connection audit (#15456) * Add authentication details to connection audit Signed-off-by: 21pages * rename normalize_conn_audit_primary_auth to normalize_conn_audit_auth_fields Signed-off-by: 21pages * Merge permanent password audit methods Signed-off-by: 21pages * Simplify connection audit auth methods Signed-off-by: 21pages --------- Signed-off-by: 21pages --- src/server/connection.rs | 76 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index fb55a16aa..a3e0ccafa 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -240,6 +240,36 @@ pub enum AuthConnType { Terminal, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i64)] +enum ConnAuditPrimaryAuth { + None = 0, + Click = 1, + TemporaryPassword = 2, + PermanentPassword = 3, + SwitchSides = 4, +} + +impl ConnAuditPrimaryAuth { + fn as_i64(self) -> i64 { + self as i64 + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i64)] +enum ConnAuditTwoFactor { + None = 0, + Totp = 1, + TrustedDevice = 2, +} + +impl ConnAuditTwoFactor { + fn as_i64(self) -> i64 { + self as i64 + } +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] #[derive(Clone, Debug)] enum TerminalUserToken { @@ -345,6 +375,8 @@ pub struct Connection { // For post requests that need to be sent sequentially. // eg. post_conn_audit tx_post_seq: mpsc::UnboundedSender<(String, Value)>, + conn_audit_primary_auth: ConnAuditPrimaryAuth, + conn_audit_two_factor: ConnAuditTwoFactor, // Tracks read job IDs delegated to CM process. // When a read job is delegated to CM (via FS::ReadFile), the job id is added here. // Used to filter stale responses (FileBlockFromCM, FileReadDone, etc.) for @@ -542,6 +574,8 @@ impl Connection { #[cfg(not(any(target_os = "android", target_os = "ios")))] terminal_user_token: None, terminal_generic_service: None, + conn_audit_primary_auth: ConnAuditPrimaryAuth::None, + conn_audit_two_factor: ConnAuditTwoFactor::None, }; let addr = hbb_common::try_into_v4(addr); if !conn.on_open(addr).await { @@ -625,6 +659,7 @@ impl Connection { Some(data) = rx_from_cm.recv() => { match data { ipc::Data::Authorize => { + conn.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::Click); conn.require_2fa.take(); if !conn.send_logon_response_and_keep_alive().await { break; @@ -1471,6 +1506,23 @@ impl Connection { crate::post_request(url, v.to_string(), "").await } + fn set_conn_audit_primary_auth(&mut self, method: ConnAuditPrimaryAuth) { + self.conn_audit_primary_auth = method; + } + + fn set_conn_audit_two_factor(&mut self, two_factor: ConnAuditTwoFactor) { + self.conn_audit_two_factor = two_factor; + } + + fn normalize_conn_audit_auth_fields(&mut self) { + if matches!( + self.conn_audit_primary_auth, + ConnAuditPrimaryAuth::Click | ConnAuditPrimaryAuth::SwitchSides + ) { + self.conn_audit_two_factor = ConnAuditTwoFactor::None; + } + } + fn normalize_port_forward_target(pf: &mut PortForward) -> (String, bool) { let mut is_rdp = false; if pf.host == "RDP" && pf.port == 0 { @@ -1594,10 +1646,15 @@ impl Connection { .unwrap() .get(&self.session_key()) .map(|s| s.last_recv_time.clone()); - self.post_conn_audit(json!({ - "peer": ((&self.lr.my_id, &self.lr.my_name)), - "type": conn_type, - })); + self.normalize_conn_audit_auth_fields(); + let mut audit = json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}); + if self.conn_audit_primary_auth != ConnAuditPrimaryAuth::None { + audit["primary_auth"] = json!(self.conn_audit_primary_auth.as_i64()); + } + if self.conn_audit_two_factor != ConnAuditTwoFactor::None { + audit["two_factor"] = json!(self.conn_audit_two_factor.as_i64()); + } + self.post_conn_audit(audit); #[allow(unused_mut)] let mut username = crate::platform::get_active_username(); let mut res = LoginResponse::new(); @@ -2209,6 +2266,7 @@ impl Connection { if password::temporary_enabled() { let password = password::temporary_password(); if self.validate_password_plain(&password) { + self.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::TemporaryPassword); raii::AuthedConnID::update_or_insert_session( self.session_key(), Some(password), @@ -2232,6 +2290,7 @@ impl Connection { if local_permanent_password_storage_is_usable_for_auth(&local_storage, &local_salt) && self.validate_password_storage(&local_storage) { + self.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::PermanentPassword); print_fallback(); return true; } @@ -2240,6 +2299,7 @@ impl Connection { if preset_permanent_password_storage_is_usable_for_auth(&hard, &salt) && self.validate_preset_password_storage(&hard, &salt) { + self.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::PermanentPassword); print_fallback(); return true; } @@ -2264,6 +2324,11 @@ impl Connection { && (tfa && session.tfa || !tfa && self.validate_password_plain(&session.random_password)) { + if tfa { + self.set_conn_audit_two_factor(ConnAuditTwoFactor::Totp); + } else { + self.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::TemporaryPassword); + } log::info!("is recent session"); return true; } @@ -2357,6 +2422,7 @@ impl Connection { && device.platform == lr.my_platform { log::info!("2FA bypassed by trusted devices"); + self.set_conn_audit_two_factor(ConnAuditTwoFactor::TrustedDevice); self.require_2fa = None; } } @@ -2649,6 +2715,7 @@ impl Connection { if res { self.update_failure(failure, true, 1); self.require_2fa.take(); + self.set_conn_audit_two_factor(ConnAuditTwoFactor::Totp); raii::AuthedConnID::set_session_2fa(self.session_key()); if !self.send_logon_response_and_keep_alive().await { return false; @@ -2704,6 +2771,7 @@ impl Connection { if let Some((_instant, uuid_old)) = uuid_old { if uuid == uuid_old { self.from_switch = true; + self.set_conn_audit_primary_auth(ConnAuditPrimaryAuth::SwitchSides); if !self.send_logon_response_and_keep_alive().await { return false; }