diff --git a/src/accounts.rs b/src/accounts.rs index f970711..f363f01 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -26,11 +26,11 @@ impl Server { /// Log `uid` into `account` (services-driven): sets the account name, flips /// `+r`, and reflects the mode back to the user. pub fn set_login(&mut self, uid: Uid, account: &str) { - let (nick, prefix) = match self.users.get_mut(&uid) { + let (nick, prefix, registered) = match self.users.get_mut(&uid) { Some(u) => { u.account = Some(account.to_string()); u.flags.logged_in = true; - (u.nick.clone(), u.prefix()) + (u.nick.clone(), u.prefix(), u.registered) } None => return, }; @@ -39,6 +39,12 @@ impl Server { self.notify_peers(uid, &format!(":{prefix} ACCOUNT {account}"), |c| { c.account_notify }); + // Operator notice, but only for a login that happens AFTER the user is + // connected. A SASL-at-connect login runs before registration completes, so + // it's already reflected in the "Client connecting: … account: …" notice. + if registered { + self.snotice_c('c', &format!("Client {nick} is now logged in as {account}")); + } } /// Log `uid` out of any account (services-driven): clears `+r`. diff --git a/src/server.rs b/src/server.rs index 02d85ea..8b6ce74 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1745,6 +1745,42 @@ mod tests { assert!(logged("#all", "CONNMSG"), "connect notice goes to #all: {lines:?}"); } + #[test] + fn post_connect_login_notifies_opers() { + let mut s = srv(); + // an operator watching client (c) notices + let orx = add_user(&mut s, 1, "watcher"); + if let Some(u) = s.users.get_mut(&1) { + u.flags.oper = true; + u.flags.snomask = true; + u.flags.snomask_cats = "c".to_string(); + } + // a REGISTERED user logging in after connecting -> the notice fires + let _a = add_user(&mut s, 2, "alice"); + if let Some(u) = s.users.get_mut(&2) { + u.registered = true; + } + s.set_login(2, "aliceacct"); + // a not-yet-registered user (SASL at connect) -> silent, since the connect + // notice already carries the account + let _b = add_user(&mut s, 3, "bob"); + if let Some(u) = s.users.get_mut(&3) { + u.registered = false; + } + s.set_login(3, "bobacct"); + let seen: String = std::iter::from_fn(|| orx.try_recv().ok()) + .collect::>() + .join("\n"); + assert!( + seen.contains("Client alice is now logged in as aliceacct"), + "post-connect login fires the notice: {seen}" + ); + assert!( + !seen.contains("bob is now logged in"), + "an at-connect (unregistered) login stays silent: {seen}" + ); + } + #[test] fn banned_user_message_shows_expiry() { let mut s = srv();