services: throttle emailed codes to stop RESEND/RESETPASS email-bombing
All checks were successful
CI / check (push) Successful in 3m46s

REGISTER was rate-limited but RESEND and RESETPASS weren't — anyone could
repeatedly request confirmation/reset codes for any account with an address
on file, flooding the victim's inbox and burning the service's sender
reputation. A per-account 60s cooldown (code_issue_wait, mirroring the vhost
request throttle) now gates both paths; guessing was already infeasible
(2^40 code + 5-try limit), so this closes the abuse, not an auth hole.
This commit is contained in:
Jean Chevronnet 2026-07-16 10:41:52 +00:00
parent 320a591ae3
commit 61005f52f5
No known key found for this signature in database
7 changed files with 44 additions and 1 deletions

View file

@ -808,6 +808,22 @@
assert!(to_ns(&mut e, "RESETPASS alice 000000 x").iter().any(|a| matches!(a, NetAction::Notice { text, .. } if text.contains("Invalid or expired"))));
}
// RESETPASS is throttled per account so it can't be used to email-bomb the
// address on file: a second request inside the cooldown sends no email.
#[test]
fn resetpass_is_throttled_against_email_bombing() {
let mut e = engine_with("nsresetrl", "alice", "sesame");
e.db.set_email_enabled(true);
e.db.set_email("alice", Some("alice@example.org".into())).unwrap();
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "someone".into(), host: "h".into() , ip: "0.0.0.0".into() });
let to_ns = |e: &mut Engine, text: &str| e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: text.into() });
assert!(to_ns(&mut e, "RESETPASS alice").iter().any(|a| matches!(a, NetAction::SendEmail { .. })), "first request emails a code");
let out = to_ns(&mut e, "RESETPASS alice");
assert!(!out.iter().any(|a| matches!(a, NetAction::SendEmail { .. })), "second immediate request sends no email: {out:?}");
assert!(out.iter().any(|a| matches!(a, NetAction::Notice { text, .. } if text.contains("wait"))), "and tells the caller to wait: {out:?}");
}
// With email configured, registering with an address creates an unverified
// account and emails a confirmation code; CONFIRM <code> verifies it.
#[test]