import sqlite3 from src.login_attempt_store import LoginAttemptStore, client_source def test_forwarded_client_is_used_only_for_explicitly_trusted_proxies(): trusted = "127.0.0.0/8, 10.0.0.0/8" assert client_source("198.51.100.4", "203.0.113.7", trusted) == "198.51.100.4" assert ( client_source("127.0.0.1", "203.0.113.7, 10.1.2.3", trusted) == "203.0.113.7" ) assert client_source("127.0.0.1", "not-an-ip", trusted) == "127.0.0.1" def test_failure_budget_is_shared_across_store_instances_and_expires(tmp_path): now = [1_000.0] database = tmp_path / "login-attempts.sqlite3" first = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, ) second = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, ) assert first.retry_after("203.0.113.7") == 0 first.record_failure("203.0.113.7") second.record_failure("203.0.113.7") first.record_failure("203.0.113.7") assert second.retry_after("203.0.113.7") == 60 now[0] = 1_060.0 assert first.retry_after("203.0.113.7") == 0 def test_recording_a_failure_prunes_expired_source_records(tmp_path): now = [1_000.0] store = LoginAttemptStore( tmp_path / "login-attempts.sqlite3", clock=lambda: now[0], max_failures=3, window_seconds=60, ) store.record_failure("203.0.113.1") store.record_failure("203.0.113.2") now[0] = 1_060.0 store.record_failure("203.0.113.3") with sqlite3.connect(store.path) as connection: assert connection.execute("SELECT COUNT(*) FROM login_attempts").fetchone() == (1,) def test_failure_ledger_evicts_oldest_sources_at_its_size_limit(tmp_path): store = LoginAttemptStore( tmp_path / "login-attempts.sqlite3", clock=lambda: 1_000.0, max_failures=3, window_seconds=60, max_entries=2, ) for address in ("203.0.113.1", "203.0.113.2", "203.0.113.3"): store.record_failure(address) with sqlite3.connect(store.path) as connection: assert connection.execute("SELECT COUNT(*) FROM login_attempts").fetchone() == (2,) def test_failed_sign_ins_are_aggregated_across_workers_and_survive_throttle_clear(tmp_path): now = [1_000.0] database = tmp_path / "login-attempts.sqlite3" first = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, alert_bucket_seconds=300, ) second = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, alert_bucket_seconds=300, ) first.record_failure("203.0.113.7", method="token") now[0] = 1_010.0 second.record_failure("198.51.100.4", method="token") first.clear("203.0.113.7") assert second.list_alerts() == [ { "method": "token", "failed_count": 2, "blocked_count": 0, "first_at": 1_000, "last_at": 1_010, } ] def test_rate_blocked_sign_ins_increment_a_distinct_privacy_safe_count(tmp_path): store = LoginAttemptStore( tmp_path / "login-attempts.sqlite3", clock=lambda: 1_000.0, max_failures=3, window_seconds=60, ) store.record_blocked(method="token") assert store.list_alerts() == [ { "method": "token", "failed_count": 0, "blocked_count": 1, "first_at": 1_000, "last_at": 1_000, } ] with sqlite3.connect(store.path) as connection: columns = [row[1] for row in connection.execute("PRAGMA table_info(login_alerts)")] assert "source_hash" not in columns def test_expired_sign_in_alert_buckets_are_not_returned(tmp_path): now = [1_000.0] store = LoginAttemptStore( tmp_path / "login-attempts.sqlite3", clock=lambda: now[0], max_failures=3, window_seconds=60, alert_retention_seconds=300, ) store.record_failure("203.0.113.7") now[0] = 1_301.0 assert store.list_alerts() == [] def test_sign_in_alert_bucket_count_is_bounded_during_rotating_source_floods(tmp_path): now = [1_000.0] store = LoginAttemptStore( tmp_path / "login-attempts.sqlite3", clock=lambda: now[0], max_failures=3, window_seconds=60, alert_bucket_seconds=60, max_alert_buckets=2, ) for index, timestamp in enumerate((1_000.0, 1_060.0, 1_120.0)): now[0] = timestamp store.record_failure(f"203.0.113.{index}") with sqlite3.connect(store.path) as connection: count = connection.execute("SELECT COUNT(*) FROM login_alerts").fetchone()[0] assert count == 2 def test_named_admission_budget_is_atomic_across_instances_and_source_scoped(tmp_path): now = [1_000.0] database = tmp_path / "login-attempts.sqlite3" first = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, ) second = LoginAttemptStore( database, clock=lambda: now[0], max_failures=3, window_seconds=60, ) assert first.admit("passkey_options", "203.0.113.7", limit=2) == 0 assert second.admit("passkey_options", "203.0.113.7", limit=2) == 0 assert first.admit("passkey_options", "203.0.113.7", limit=2) == 60 assert first.admit("passkey_options", "203.0.113.8", limit=2) == 0 now[0] = 1_060.0 assert second.admit("passkey_options", "203.0.113.7", limit=2) == 0