Initial commit: CI Agent competitive-intelligence monitoring app

FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring,
source collection, LLM-based change analysis, enrichment, and account
security (Turnstile, escalating lockout, email verification).
This commit is contained in:
2026-08-05 10:48:20 -04:00
commit 1a4c80958f
365 changed files with 43541 additions and 0 deletions
@@ -0,0 +1,245 @@
"""ip_throttle_service: the escalation engine backing resend-verification,
resend-password-reset, and failed-login throttling. Every stage is walked
by monkeypatching `_now()` forward - no real waiting, no manual
brute-forcing, per the explicit "test it smarter" requirement this feature
was built under."""
from __future__ import annotations
import uuid
from datetime import UTC, datetime, timedelta
import pytest
from app.db.base import ensure_aware_utc
from app.models.enums import ThrottleAction
from app.repositories.ip_throttle_repository import IpThrottleRepository
from app.services import ip_throttle_service
from app.services.ip_throttle_service import (
LOGIN_BACKOFF_SECONDS,
RESEND_BACKOFF_SECONDS,
TIMEOUT_LADDER_SECONDS,
peek_throttle,
record_attempt,
reset_on_success,
)
def _unique_ip() -> str:
return f"10.{uuid.uuid4().int % 250}.{uuid.uuid4().int % 250}.{uuid.uuid4().int % 250}"
class _Clock:
"""A monkeypatchable fake clock - advances only when told to, so tests
can jump straight past a 5-hour timeout without real time passing."""
def __init__(self) -> None:
self.now = datetime(2026, 1, 1, tzinfo=UTC)
def __call__(self) -> datetime:
return self.now
def advance(self, seconds: float) -> None:
self.now += timedelta(seconds=seconds)
@pytest.fixture()
def clock(monkeypatch):
fake_clock = _Clock()
monkeypatch.setattr(ip_throttle_service, "_now", fake_clock)
return fake_clock
async def _exhaust_stages_into_timeout(db_session, clock, ip: str, action: ThrottleAction) -> None:
"""Drives one full stage array to exhaustion (advancing the clock past
each required wait, exactly as a real repeated-offender caller would),
landing the state in a fresh timeout. Always starts from a peek (so a
prior *completed* timeout cycle is lazily reset first, mirroring how
every real call site checks peek_throttle before record_attempt)."""
for _ in RESEND_BACKOFF_SECONDS:
peeked = await peek_throttle(db_session, ip, action)
assert peeked.allowed is True
await record_attempt(db_session, ip, action, RESEND_BACKOFF_SECONDS)
state = await IpThrottleRepository(db_session).get_state(ip, action)
if state.next_allowed_at is not None:
next_allowed_at = ensure_aware_utc(state.next_allowed_at)
clock.advance((next_allowed_at - clock.now).total_seconds())
# One more attempt now that every stage is consumed - this is the one
# that exhausts the array and enters a timeout.
await record_attempt(db_session, ip, action, RESEND_BACKOFF_SECONDS)
async def test_first_attempt_is_always_allowed(db_session, clock):
ip = _unique_ip()
result = await peek_throttle(db_session, ip, ThrottleAction.RESEND_VERIFICATION)
assert result.allowed is True
assert result.banned is False
async def test_resend_backoff_stages_match_spec_exactly(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_VERIFICATION
for expected_wait in RESEND_BACKOFF_SECONDS:
result = await peek_throttle(db_session, ip, action)
assert result.allowed is True
await record_attempt(db_session, ip, action, RESEND_BACKOFF_SECONDS)
blocked = await peek_throttle(db_session, ip, action)
assert blocked.allowed is False
assert blocked.retry_after_seconds == expected_wait
clock.advance(expected_wait)
async def test_resend_exhausting_stages_enters_first_timeout(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
blocked = await peek_throttle(db_session, ip, action)
assert blocked.allowed is False
assert blocked.retry_after_seconds == TIMEOUT_LADDER_SECONDS[0]
async def test_timeout_expires_resets_attempts_but_keeps_offense_memory(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
repo = IpThrottleRepository(db_session)
state = await repo.get_state(ip, action)
assert state.offense_count == 1
clock.advance(TIMEOUT_LADDER_SECONDS[0])
result = await peek_throttle(db_session, ip, action)
assert result.allowed is True
state = await repo.get_state(ip, action)
assert state.attempt_count == 0
assert state.offense_count == 1 # memory kept, exactly as specified
async def test_repeat_offense_uses_next_longer_timeout(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
await _exhaust_stages_into_timeout(db_session, clock, ip, action) # offense #1 -> 30min
clock.advance(TIMEOUT_LADDER_SECONDS[0])
await _exhaust_stages_into_timeout(db_session, clock, ip, action) # offense #2 -> 1h
blocked = await peek_throttle(db_session, ip, action)
assert blocked.retry_after_seconds == TIMEOUT_LADDER_SECONDS[1]
async def test_escalation_past_the_ladder_results_in_permanent_ban(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
for i, timeout_seconds in enumerate(TIMEOUT_LADDER_SECONDS):
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
clock.advance(timeout_seconds)
result = await peek_throttle(db_session, ip, action)
assert result.banned is False, f"should not be banned yet after offense {i + 1}"
# One more full cycle exhausts past the ladder entirely -> permanent ban.
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
result = await peek_throttle(db_session, ip, action)
assert result.banned is True
assert result.allowed is False
async def test_login_five_instant_attempts_then_escalating_delays(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.FAILED_LOGIN
for _ in range(5):
result = await peek_throttle(db_session, ip, action)
assert result.allowed is True
await record_attempt(db_session, ip, action, LOGIN_BACKOFF_SECONDS)
# All 5 were genuinely free - no wait was ever imposed before any of them.
blocked = await peek_throttle(db_session, ip, action)
assert blocked.allowed is False
assert blocked.retry_after_seconds == 5 # first real delay stage, gating attempt 6
async def test_login_all_delay_stages_match_spec_in_order(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.FAILED_LOGIN
for _ in range(5):
await record_attempt(db_session, ip, action, LOGIN_BACKOFF_SECONDS)
expected_delays = [5, 15, 30, 60, 120, 300, 900]
for expected_wait in expected_delays:
blocked = await peek_throttle(db_session, ip, action)
assert blocked.retry_after_seconds == expected_wait
clock.advance(expected_wait)
await record_attempt(db_session, ip, action, LOGIN_BACKOFF_SECONDS)
# That was the 12th recorded attempt (5 free + 7 delayed) - stages are
# now exhausted, so the IP itself enters its first timeout.
blocked = await peek_throttle(db_session, ip, action)
assert blocked.retry_after_seconds == TIMEOUT_LADDER_SECONDS[0]
async def test_ban_blocks_every_action_type_for_that_ip(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
for timeout_seconds in TIMEOUT_LADDER_SECONDS:
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
clock.advance(timeout_seconds)
await _exhaust_stages_into_timeout(db_session, clock, ip, action) # permanent ban
# A totally different action from the same IP is also blocked - bans are
# global per-IP, not scoped to the action that triggered them.
login_result = await peek_throttle(db_session, ip, ThrottleAction.FAILED_LOGIN)
assert login_result.banned is True
async def test_reset_on_success_clears_stage_but_not_offense_count(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.FAILED_LOGIN
for _ in range(6):
await record_attempt(db_session, ip, action, LOGIN_BACKOFF_SECONDS)
repo = IpThrottleRepository(db_session)
state = await repo.get_state(ip, action)
assert state.attempt_count == 6
await reset_on_success(db_session, ip, action)
state = await repo.get_state(ip, action)
assert state.attempt_count == 0
assert state.next_allowed_at is None
result = await peek_throttle(db_session, ip, action)
assert result.allowed is True
async def test_admin_unban_gives_a_clean_slate(db_session, clock):
ip = _unique_ip()
action = ThrottleAction.RESEND_RESET
for timeout_seconds in TIMEOUT_LADDER_SECONDS:
await _exhaust_stages_into_timeout(db_session, clock, ip, action)
clock.advance(timeout_seconds)
await _exhaust_stages_into_timeout(db_session, clock, ip, action) # permanent ban
result = await peek_throttle(db_session, ip, action)
assert result.banned is True
repo = IpThrottleRepository(db_session)
cleared = await repo.clear_ban_and_state(ip)
assert cleared is True
result = await peek_throttle(db_session, ip, action)
assert result.allowed is True
assert result.banned is False
state = await repo.get_state(ip, action)
assert state.offense_count == 0 # a real pardon, not just lifting the ban