auth: offload IDENTIFY + SASL PLAIN verify off the engine lock
All checks were successful
CI / check (push) Successful in 3m47s
All checks were successful
CI / check (push) Successful in 3m47s
Both ran scram verify_plain (~1s PBKDF2 at 1.2M iters) inline under the engine lock, freezing the whole daemon per login. Add NetAction::DeferAuthenticate + AuthThen continuation + ctx.defer_authenticate, mirroring DeferPassword: the module/SASL path fetches the verifier cheaply and defers; the link layer runs verify_plain on spawn_blocking, then Engine::complete_authenticate finishes the login (IDENTIFY reuses the same ctx helpers, so its login/AJOIN/vhost/memo side-effects are unchanged). Tests resolve the defer inline (cfg(test)). The gRPC web-login path was already fixed; no login path stalls services now. (GHOST/DROP/CERT/GROUP still verify inline but are rare account ops, not logins.)
This commit is contained in:
parent
63fc2fb2c0
commit
2f9790feac
9 changed files with 144 additions and 36 deletions
|
|
@ -122,6 +122,12 @@ pub enum NetAction {
|
|||
// Internal only: a password change awaiting the same off-thread derivation.
|
||||
// The link layer derives, then calls Engine::complete_password_change.
|
||||
DeferPassword { account: String, password: String, agent: String, uid: String },
|
||||
// Internal only: a password VERIFY (IDENTIFY / SASL PLAIN) awaiting the same
|
||||
// off-thread work — the PBKDF2 that verify_plain runs is ~1s at production
|
||||
// iteration counts, so it must never run under the engine lock. The link layer
|
||||
// fetches `verifier` cheaply, runs verify_plain off-thread, then calls
|
||||
// Engine::complete_authenticate with the boolean result and `then`.
|
||||
DeferAuthenticate { verifier: String, password: String, then: AuthThen },
|
||||
// Internal only: send an email (plaintext + optional HTML). The link layer
|
||||
// pipes it to the configured mail command off-thread; never serialized.
|
||||
SendEmail { to: String, subject: String, text: String, html: Option<String> },
|
||||
|
|
@ -141,6 +147,18 @@ pub enum RegReply {
|
|||
NickServ { agent: String, uid: String, nick: String },
|
||||
}
|
||||
|
||||
/// What to do once a deferred password verify (see [`NetAction::DeferAuthenticate`])
|
||||
/// completes, given the boolean result. The cheap pre-checks already ran; this is
|
||||
/// only the success/failure finish.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AuthThen {
|
||||
// NickServ IDENTIFY: `uid` logs in, `agent` (NickServ uid) sends the notices,
|
||||
// `name` is what the user typed (for lockout/note_auth), `account` is canonical.
|
||||
Identify { uid: String, agent: String, name: String, account: String },
|
||||
// SASL PLAIN: finish the SASL exchange for `client`, sourced from `agent`.
|
||||
Sasl { agent: String, client: String, account: String },
|
||||
}
|
||||
|
||||
/// The ircd link layer. The engine only ever sees [`NetEvent`] / [`NetAction`];
|
||||
/// raw server-to-server lines live entirely behind a `Protocol` impl, so a new
|
||||
/// ircd is one new module and the engine is untouched.
|
||||
|
|
@ -317,6 +335,16 @@ impl ServiceCtx {
|
|||
});
|
||||
}
|
||||
|
||||
// Hand a password verify (IDENTIFY / SASL PLAIN) to the engine to finish: the
|
||||
// ~1s PBKDF2 runs off the reactor, then the engine completes it via `then`.
|
||||
pub fn defer_authenticate(&mut self, verifier: impl Into<String>, password: impl Into<String>, then: AuthThen) {
|
||||
self.actions.push(NetAction::DeferAuthenticate {
|
||||
verifier: verifier.into(),
|
||||
password: password.into(),
|
||||
then,
|
||||
});
|
||||
}
|
||||
|
||||
// Log a user into an account: sets the accountname the ircd turns into
|
||||
// RPL_LOGGEDIN (900) and exposes to account-tag / WHOX, the same login the
|
||||
// SASL agent applies. Used after a successful REGISTER / IDENTIFY.
|
||||
|
|
@ -926,6 +954,10 @@ pub trait Store {
|
|||
fn accounts_by_email(&self, pattern: &str) -> Vec<String>;
|
||||
// The canonical account name if the password is correct, else None.
|
||||
fn authenticate(&self, name: &str, password: &str) -> Option<&str>;
|
||||
/// The account's canonical name and its SHA-256 verifier (owned), so the
|
||||
/// caller can run the ~1s PBKDF2 verify OFF the engine lock via
|
||||
/// `ctx.defer_authenticate` rather than the blocking `authenticate`.
|
||||
fn scram_verifier(&self, name: &str) -> Option<(String, String)>;
|
||||
fn grouped_nicks(&self, account: &str) -> Vec<String>;
|
||||
fn certfps(&self, account: &str) -> &[String];
|
||||
fn is_verified(&self, account: &str) -> bool;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue