fix(security): change pairing lockout to per-client accounting

Replace global failed-attempt counter with per-client HashMap keyed by
client identity (IP address for gateway, chat_id for Telegram).  This
prevents a single attacker from locking out all legitimate clients.

Bounded state: entries are evicted after lockout expiry, and the map is
capped at 1024 tracked clients.

Closes #603

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alex Gorevski 2026-02-19 07:33:11 -08:00
parent ba500a606e
commit 56af0d169e
3 changed files with 73 additions and 28 deletions

View file

@ -610,7 +610,7 @@ async fn handle_pair(
.and_then(|v| v.to_str().ok())
.unwrap_or("");
match state.pairing.try_pair(code).await {
match state.pairing.try_pair(code, &rate_key).await {
Ok(Some(token)) => {
tracing::info!("🔐 New client paired successfully");
if let Err(err) = persist_pairing_tokens(state.config.clone(), &state.pairing).await {
@ -1457,7 +1457,7 @@ mod tests {
let guard = PairingGuard::new(true, &[]);
let code = guard.pairing_code().unwrap();
let token = guard.try_pair(&code).await.unwrap().unwrap();
let token = guard.try_pair(&code, "test_client").await.unwrap().unwrap();
assert!(guard.is_authenticated(&token));
let shared_config = Arc::new(Mutex::new(config));