diff --git a/src/modules/cloudflare_challenge.rs b/src/modules/cloudflare_challenge.rs index ae9b6d2..5cdda9c 100644 --- a/src/modules/cloudflare_challenge.rs +++ b/src/modules/cloudflare_challenge.rs @@ -19,6 +19,11 @@ use crate::Uid; #[derive(Default)] struct Passed(HashSet); +/// uids already handed a challenge, so a held client sending extra commands doesn't +/// get the challenge notice re-issued each time. In `Server.ext`. +#[derive(Default)] +struct Challenged(HashSet); + fn enabled(s: &Server) -> bool { s.conf_bool("cloudflare_challenge", false) && s.conf("cloudflare_secret").is_some_and(|v| !v.is_empty()) @@ -66,6 +71,9 @@ impl Module for CloudflareChallenge { if let Some(p) = s.ext.get_mut::() { p.0.remove(&uid); } + if let Some(c) = s.ext.get_mut::() { + c.0.remove(&uid); + } } fn on_user_register(&mut self, srv: &mut Server, uid: Uid) -> ModResult { @@ -75,6 +83,18 @@ impl Module for CloudflareChallenge { if passed(srv, uid) || port_whitelisted(srv, uid) { return ModResult::Passthru; } + // issue the challenge exactly once; later held attempts just keep holding + if srv + .ext + .get::() + .is_some_and(|c| c.0.contains(&uid)) + { + return ModResult::Hold; + } + srv.ext + .get_or_insert_with::(Challenged::default) + .0 + .insert(uid); let (nick, token) = ( srv.users .get(&uid) diff --git a/src/modules/recaptcha.rs b/src/modules/recaptcha.rs index 0922f47..aff3b71 100644 --- a/src/modules/recaptcha.rs +++ b/src/modules/recaptcha.rs @@ -26,6 +26,11 @@ use crate::Uid; #[derive(Default)] struct Verified(HashSet); +/// The set of uids already handed a challenge, so a held client sending extra +/// commands doesn't get the challenge notice re-issued each time. In `Server.ext`. +#[derive(Default)] +struct Challenged(HashSet); + fn enabled(s: &Server) -> bool { s.conf_bool("recaptcha", false) && s.conf("recaptcha_secret").is_some_and(|v| !v.is_empty()) @@ -75,6 +80,9 @@ impl Module for ReCaptcha { if let Some(v) = s.ext.get_mut::() { v.0.remove(&uid); } + if let Some(c) = s.ext.get_mut::() { + c.0.remove(&uid); + } } fn on_user_register(&mut self, srv: &mut Server, uid: Uid) -> ModResult { @@ -84,7 +92,19 @@ impl Module for ReCaptcha { if is_verified(srv, uid) || port_whitelisted(srv, uid) { return ModResult::Passthru; } - // hand out a challenge and refuse the link until they verify + // issue the challenge exactly once; later held attempts just keep holding + if srv + .ext + .get::() + .is_some_and(|c| c.0.contains(&uid)) + { + return ModResult::Hold; + } + srv.ext + .get_or_insert_with::(Challenged::default) + .0 + .insert(uid); + // hand out a challenge and hold the link until they verify let (nick, token) = ( srv.users .get(&uid)