engine: log out local sessions on a gossiped account suspension
All checks were successful
CI / check (push) Successful in 3m43s

A local SUSPEND logs out the account's active sessions, but a suspension
arriving by gossip only set the flag — a session already identified on a
remote node stayed logged in despite the network-wide suspension. ingest
now signals AccountChange::Suspended, and gossip_ingest ends those sessions
(the account and its channels are kept). Factors the per-session logout into
a shared logout_uid helper.
This commit is contained in:
Jean Chevronnet 2026-07-16 09:13:53 +00:00
parent 58af1187fc
commit 886fe65e4d
No known key found for this signature in database
3 changed files with 84 additions and 10 deletions

View file

@ -469,6 +469,12 @@ impl Engine {
self.handle_account_gone(&a, "was dropped");
true
}
Some(db::AccountChange::Suspended(a)) => {
// A gossiped suspension keeps the account and its channels, but its
// live sessions here must end (a local SUSPEND logs them out too).
self.logout_account_sessions(&a, "was suspended");
false
}
None => false,
};
// handle_account_gone may have released a channel that had a bot assigned.
@ -504,15 +510,9 @@ impl Engine {
}
// Log out and inform each local session that held the name.
for uid in victims {
self.network.clear_account(&uid);
self.emit_irc(NetAction::Metadata { target: uid.clone(), key: "accountname".to_string(), value: String::new() });
if let Some(ns) = &ns {
self.emit_irc(NetAction::Notice {
from: ns.clone(),
to: uid.clone(),
text: format!("Your account \x02{account}\x02 {reason}. You have been logged out."),
});
if !orphaned.is_empty() {
self.logout_uid(&uid, account, reason);
if !orphaned.is_empty() {
if let Some(ns) = &ns {
self.emit_irc(NetAction::Notice {
from: ns.clone(),
to: uid,
@ -523,6 +523,28 @@ impl Engine {
}
}
// Log a single session out of `account`, telling them why (services-sourced).
fn logout_uid(&mut self, uid: &str, account: &str, reason: &str) {
self.network.clear_account(uid);
self.emit_irc(NetAction::Metadata { target: uid.to_string(), key: "accountname".to_string(), value: String::new() });
if let Some(ns) = self.nick_service.clone() {
self.emit_irc(NetAction::Notice {
from: ns,
to: uid.to_string(),
text: format!("Your account \x02{account}\x02 {reason}. You have been logged out."),
});
}
}
// Log out every local session identified to `account` — e.g. after a gossiped
// suspension. Unlike `handle_account_gone` the account and its channels stay
// put; only the live sessions end.
fn logout_account_sessions(&mut self, account: &str, reason: &str) {
for uid in self.network.uids_logged_into(account) {
self.logout_uid(&uid, account, reason);
}
}
// Compact the log if it has grown past the live account count. Returns
// whether it rewrote anything.
pub fn maybe_compact(&mut self) -> std::io::Result<bool> {