LOGIN from an already-identified session reclaims the nick without a needless re-verify
All checks were successful
CI / check (push) Successful in 5m18s
All checks were successful
CI / check (push) Successful in 5m18s
This commit is contained in:
parent
c2a7dfa8f7
commit
7fa7831390
3 changed files with 25 additions and 4 deletions
|
|
@ -114,7 +114,7 @@ impl Service for NickServ {
|
||||||
match cmd.as_deref() {
|
match cmd.as_deref() {
|
||||||
Some("REGISTER") => register::handle(me, from, args, ctx, db),
|
Some("REGISTER") => register::handle(me, from, args, ctx, db),
|
||||||
Some("IDENTIFY") | Some("ID") => identify::handle(me, from, args, ctx, db),
|
Some("IDENTIFY") | Some("ID") => identify::handle(me, from, args, ctx, db),
|
||||||
Some("LOGIN") => login::handle(me, from, args, ctx, db),
|
Some("LOGIN") => login::handle(me, &self.guest_nick, &mut self.guest_seq, from, args, ctx, net, db),
|
||||||
Some("LOGOUT") | Some("LOGOFF") => logout::handle(me, &self.guest_nick, &mut self.guest_seq, from, ctx, net, db),
|
Some("LOGOUT") | Some("LOGOFF") => logout::handle(me, &self.guest_nick, &mut self.guest_seq, from, ctx, net, db),
|
||||||
Some("CERT") => cert::handle(me, from, args, ctx, db),
|
Some("CERT") => cert::handle(me, from, args, ctx, db),
|
||||||
Some("INFO") => info::handle(me, from, args, ctx, net, db),
|
Some("INFO") => info::handle(me, from, args, ctx, net, db),
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
use echo_api::{AuthThen, Sender, ServiceCtx, Store};
|
use echo_api::{AuthThen, NetView, Sender, ServiceCtx, Store};
|
||||||
|
|
||||||
use super::identify;
|
use super::{ghost, identify};
|
||||||
|
|
||||||
// LOGIN <nick> <password>: identify to the account owning <nick> and, on success,
|
// LOGIN <nick> <password>: identify to the account owning <nick> and, on success,
|
||||||
// reclaim the nick — freeing any ghost holding it and moving you onto it. A
|
// reclaim the nick — freeing any ghost holding it and moving you onto it. A
|
||||||
// one-shot IDENTIFY + RECOVER for someone who connected under a guest nick.
|
// one-shot IDENTIFY + RECOVER for someone who connected under a guest nick.
|
||||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
|
||||||
let (Some(&nick), Some(&password)) = (args.get(1), args.get(2)) else {
|
let (Some(&nick), Some(&password)) = (args.get(1), args.get(2)) else {
|
||||||
ctx.notice(me, from.uid, "Syntax: LOGIN <nick> <password>");
|
ctx.notice(me, from.uid, "Syntax: LOGIN <nick> <password>");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Already identified to this account: don't burn a needless ~1s re-verify — just
|
||||||
|
// reclaim the nick, exactly like RECOVER (which needs no password for an owner).
|
||||||
|
if from.account.is_some() && db.resolve_account(nick) == from.account {
|
||||||
|
ghost::handle(me, guest_nick, guest_seq, from, args, ctx, net, db, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some((account, verifier)) = identify::precheck(me, from, "LOGIN", nick, ctx, db) else {
|
let Some((account, verifier)) = identify::precheck(me, from, "LOGIN", nick, ctx, db) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -868,6 +868,20 @@
|
||||||
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick == "alice")), "caller takes the nick: {out:?}");
|
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick == "alice")), "caller takes the nick: {out:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LOGIN from a session already identified to that account skips the (wasteful)
|
||||||
|
// re-verify and just reclaims the nick, like RECOVER.
|
||||||
|
#[test]
|
||||||
|
fn login_while_identified_reclaims_without_reverify() {
|
||||||
|
let mut e = engine_with("loginid", "alice", "sesame");
|
||||||
|
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "Guest1".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||||
|
e.complete_authenticate(true, echo_api::AuthThen::Identify { uid: "000AAAAAB".into(), agent: "42SAAAAAA".into(), name: "alice".into(), account: "alice".into() });
|
||||||
|
e.handle(NetEvent::UserConnect { uid: "000AAAAAC".into(), nick: "alice".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||||
|
let out = e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: "LOGIN alice sesame".into() });
|
||||||
|
assert!(!out.iter().any(|a| matches!(a, NetAction::DeferAuthenticate { .. })), "no re-verify when already identified: {out:?}");
|
||||||
|
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAC" && nick != "alice")), "ghost freed: {out:?}");
|
||||||
|
assert!(out.iter().any(|a| matches!(a, NetAction::ForceNick { uid, nick } if uid == "000AAAAAB" && nick == "alice")), "caller regains the nick: {out:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// A suspension that arrives by gossip must end local sessions on that account,
|
// 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.
|
// just as a local SUSPEND does — the account and its channels stay put.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue