Add NickServ LOGOUT command

Clears the account (RPL_LOGGEDOUT) and renames the user to a
configurable guest nick (default Guest) via SVSNICK.
This commit is contained in:
Jean Chevronnet 2026-07-12 05:06:34 +00:00
parent a382ac8ad3
commit 60fc687162
No known key found for this signature in database
7 changed files with 77 additions and 2 deletions

View file

@ -468,7 +468,7 @@ mod tests {
let mut db = Db::open(&path);
db.scram_iterations = 4096; // keep the debug-build verifier cheap in tests
assert!(db.register(account, password, None).is_ok());
Engine::new(vec![Box::new(NickServ { uid: "42SAAAAAA".to_string() })], db)
Engine::new(vec![Box::new(NickServ { uid: "42SAAAAAA".to_string(), guest_nick: "Guest".to_string(), guest_seq: 12345 })], db)
}
fn sasl(engine: &mut Engine, mode: &str, chunk: &str) -> Vec<NetAction> {
@ -721,4 +721,27 @@ mod tests {
e.db.register("bar", "sesame", None).unwrap();
assert!(matches!(e.db.certfp_add("bar", fp), Err(db::CertError::InUse)), "a fingerprint maps to one account");
}
// IDENTIFY logs in, LOGOUT clears the accountname (ircd emits RPL_LOGGEDOUT).
#[test]
fn nickserv_logout_clears_account() {
let mut e = engine_with("nslogout", "foo", "sesame");
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "foo".into() });
let login = e.handle(NetEvent::Privmsg {
from: "000AAAAAB".into(),
to: "42SAAAAAA".into(),
text: "IDENTIFY sesame".into(),
});
assert!(login.iter().any(|a| matches!(a, NetAction::Metadata { key, value, .. } if key == "accountname" && value == "foo")), "{login:?}");
let out = e.handle(NetEvent::Privmsg {
from: "000AAAAAB".into(),
to: "42SAAAAAA".into(),
text: "LOGOUT".into(),
});
assert!(out.iter().any(|a| matches!(a, NetAction::Metadata { key, value, .. } if key == "accountname" && value.is_empty())), "logout clears accountname: {out:?}");
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick == "Guest12345")), "logout renames to guest nick: {out:?}");
assert!(out.iter().any(|a| matches!(a, NetAction::Notice { text, .. } if text.contains("logged out"))), "{out:?}");
}
}

View file

@ -54,4 +54,22 @@ impl ServiceCtx {
value: account.to_string(),
});
}
// Log a user out: clearing the accountname the ircd turns into RPL_LOGGEDOUT
// (901) and drops from account-tag / WHOX. The inverse of `login`.
pub fn logout(&mut self, uid: &str) {
self.actions.push(NetAction::Metadata {
target: uid.to_string(),
key: "accountname".to_string(),
value: String::new(),
});
}
// Force a user's nick (SVSNICK), e.g. to a guest nick after logout.
pub fn force_nick(&mut self, uid: &str, nick: &str) {
self.actions.push(NetAction::ForceNick {
uid: uid.to_string(),
nick: nick.to_string(),
});
}
}