Add NickServ LOGIN to identify and reclaim your nick in one command
All checks were successful
CI / check (push) Successful in 5m24s
All checks were successful
CI / check (push) Successful in 5m24s
This commit is contained in:
parent
aa270e061e
commit
c2a7dfa8f7
12 changed files with 158 additions and 62 deletions
|
|
@ -371,43 +371,13 @@ impl Engine {
|
|||
/// AJOIN, vhost, memo notice) stay identical.
|
||||
pub fn complete_authenticate(&mut self, ok: bool, then: AuthThen) -> Vec<NetAction> {
|
||||
let actions = match then {
|
||||
AuthThen::Identify { uid, agent, name, account } => {
|
||||
self.db.note_auth(&name, ok);
|
||||
let lang = self.lang_for_account(&account);
|
||||
let mut ctx = ServiceCtx { lang: lang.clone(), ..Default::default() };
|
||||
if !ok {
|
||||
ctx.count("nickserv.identify_fail");
|
||||
ctx.fail(&agent, &uid, "IDENTIFY", "INVALID_CREDENTIALS", "Invalid password. Please try again.");
|
||||
} else {
|
||||
ctx.login(&uid, &account);
|
||||
ctx.count("nickserv.identify");
|
||||
ctx.notice(&agent, &uid, echo_api::render(&lang, "You're now identified as \x02{account}\x02. Welcome back!", &[("account", account.clone())]));
|
||||
for entry in self.db.ajoin_list(&account) {
|
||||
ctx.force_join(&uid, &entry.channel, &entry.key);
|
||||
}
|
||||
let now = self.now_secs();
|
||||
let vhost = self.db.account(&account).and_then(|a| {
|
||||
a.vhost.as_ref().filter(|v| v.expires.is_none_or(|e| e > now)).map(|v| v.host.clone())
|
||||
});
|
||||
if let Some(host) = vhost {
|
||||
ctx.apply_vhost(&uid, &host);
|
||||
}
|
||||
let unread = self.db.unread_memos(&account);
|
||||
if unread > 0 && self.db.memo_notify_on(&account) {
|
||||
ctx.notice(&agent, &uid, echo_api::render_plural(&lang, unread as u64, "You have \x02{unread}\x02 new memo. Read it with \x02/msg MemoServ READ NEW\x02.", "You have \x02{unread}\x02 new memos. Read them with \x02/msg MemoServ READ NEW\x02.", &[("unread", unread.to_string())]));
|
||||
}
|
||||
AuthThen::Identify { uid, agent, name, account } => self.finish_identify(ok, uid, agent, name, account),
|
||||
AuthThen::Login { uid, agent, name, account, nick } => {
|
||||
let mut out = self.finish_identify(ok, uid.clone(), agent, name, account);
|
||||
if ok {
|
||||
out.extend(self.recover_nick(&uid, &nick));
|
||||
}
|
||||
for key in std::mem::take(&mut ctx.stats) {
|
||||
self.bump(&key);
|
||||
}
|
||||
let feed = if ok {
|
||||
self.auth_report(true, Some(&account), "NickServ IDENTIFY", &uid, None)
|
||||
} else {
|
||||
self.auth_report(false, Some(&name), "NickServ IDENTIFY", &uid, Some("bad password"))
|
||||
};
|
||||
let mut actions = ctx.actions;
|
||||
actions.extend(feed);
|
||||
actions
|
||||
out
|
||||
}
|
||||
AuthThen::Sasl { agent, client, account, password } => {
|
||||
// Feed the same brute-force throttle IDENTIFY uses (success clears it,
|
||||
|
|
@ -432,4 +402,61 @@ impl Engine {
|
|||
self.track_accounts(&actions);
|
||||
actions
|
||||
}
|
||||
|
||||
// The login side-effects shared by IDENTIFY and LOGIN: throttle bookkeeping,
|
||||
// the welcome + auto-join + vhost + waiting-memo notice, and the auth-feed line.
|
||||
fn finish_identify(&mut self, ok: bool, uid: String, agent: String, name: String, account: String) -> Vec<NetAction> {
|
||||
self.db.note_auth(&name, ok);
|
||||
let lang = self.lang_for_account(&account);
|
||||
let mut ctx = ServiceCtx { lang: lang.clone(), ..Default::default() };
|
||||
if !ok {
|
||||
ctx.count("nickserv.identify_fail");
|
||||
ctx.fail(&agent, &uid, "IDENTIFY", "INVALID_CREDENTIALS", "Invalid password. Please try again.");
|
||||
} else {
|
||||
ctx.login(&uid, &account);
|
||||
ctx.count("nickserv.identify");
|
||||
ctx.notice(&agent, &uid, echo_api::render(&lang, "You're now identified as \x02{account}\x02. Welcome back!", &[("account", account.clone())]));
|
||||
for entry in self.db.ajoin_list(&account) {
|
||||
ctx.force_join(&uid, &entry.channel, &entry.key);
|
||||
}
|
||||
let now = self.now_secs();
|
||||
let vhost = self.db.account(&account).and_then(|a| {
|
||||
a.vhost.as_ref().filter(|v| v.expires.is_none_or(|e| e > now)).map(|v| v.host.clone())
|
||||
});
|
||||
if let Some(host) = vhost {
|
||||
ctx.apply_vhost(&uid, &host);
|
||||
}
|
||||
let unread = self.db.unread_memos(&account);
|
||||
if unread > 0 && self.db.memo_notify_on(&account) {
|
||||
ctx.notice(&agent, &uid, echo_api::render_plural(&lang, unread as u64, "You have \x02{unread}\x02 new memo. Read it with \x02/msg MemoServ READ NEW\x02.", "You have \x02{unread}\x02 new memos. Read them with \x02/msg MemoServ READ NEW\x02.", &[("unread", unread.to_string())]));
|
||||
}
|
||||
}
|
||||
for key in std::mem::take(&mut ctx.stats) {
|
||||
self.bump(&key);
|
||||
}
|
||||
let feed = if ok {
|
||||
self.auth_report(true, Some(&account), "NickServ IDENTIFY", &uid, None)
|
||||
} else {
|
||||
self.auth_report(false, Some(&name), "NickServ IDENTIFY", &uid, Some("bad password"))
|
||||
};
|
||||
let mut actions = ctx.actions;
|
||||
actions.extend(feed);
|
||||
actions
|
||||
}
|
||||
|
||||
// Reclaim `nick` for `uid` after a LOGIN: rename any other session off it (to a
|
||||
// guest nick), then move the caller onto it.
|
||||
fn recover_nick(&mut self, uid: &str, nick: &str) -> Vec<NetAction> {
|
||||
let mut out = Vec::new();
|
||||
if let Some(ghost) = self.network.uid_by_nick(nick).map(str::to_string) {
|
||||
if ghost != uid {
|
||||
let guest = echo_api::next_guest_nick(&self.guest_nick, &mut self.enforce_seq, &self.network, &self.db);
|
||||
out.push(NetAction::ForceNick { uid: ghost, nick: guest });
|
||||
}
|
||||
}
|
||||
if self.network.nick_of(uid) != Some(nick) {
|
||||
out.push(NetAction::ForceNick { uid: uid.to_string(), nick: nick.to_string() });
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -854,6 +854,20 @@
|
|||
assert_eq!(e.network.account_of("000AAAAAB"), Some("foo"), "and is authoritative in echo's own map");
|
||||
}
|
||||
|
||||
// NS LOGIN identifies AND reclaims the nick: after the deferred verify succeeds,
|
||||
// any ghost on the target nick is renamed off and the caller is moved onto it.
|
||||
#[test]
|
||||
fn login_recovers_the_nick_after_the_deferred_verify() {
|
||||
let mut e = engine_with("login", "alice", "sesame");
|
||||
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "Guest7".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||
e.handle(NetEvent::UserConnect { uid: "000AAAAAC".into(), nick: "alice".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||
let then = echo_api::AuthThen::Login { uid: "000AAAAAB".into(), agent: "42SAAAAAA".into(), name: "alice".into(), account: "alice".into(), nick: "alice".into() };
|
||||
let out = e.complete_authenticate(true, then);
|
||||
assert_eq!(e.network.account_of("000AAAAAB"), Some("alice"), "caller is identified");
|
||||
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAC" && nick != "alice")), "ghost renamed off the nick: {out:?}");
|
||||
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick == "alice")), "caller takes the nick: {out:?}");
|
||||
}
|
||||
|
||||
// A suspension that arrives by gossip must end local sessions on that account,
|
||||
// just as a local SUSPEND does — the account and its channels stay put.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue