import time from collections import defaultdict from typing import Mapping def resolve_client_ip(headers: Mapping[str, str], direct_host: str | None) -> str: """Resolves the real visitor IP for per-IP rate limiting. The App CT sits behind a Cloudflare Tunnel that runs on a separate machine (see README Architecture) — every internet-facing connection's raw TCP peer is that tunnel machine, not the visitor, which would collapse per-IP limiting to a single shared bucket for all remote traffic. Cloudflare's edge sets `CF-Connecting-IP` itself, stripping any client-supplied value first, so it's safe to trust here. Direct LAN/local access (no Cloudflare in front, e.g. local dev) has no such header and falls back to the raw socket peer. """ forwarded = headers.get("cf-connecting-ip") if forwarded: return forwarded return direct_host or "unknown" class RateLimiter: """Fixed-window limiter keyed by an arbitrary string (user id or client IP).""" def __init__(self, max_requests: int, window_seconds: float): self.max_requests = max_requests self.window_seconds = window_seconds self._hits: dict[str, list[float]] = defaultdict(list) def allow(self, key: str) -> bool: now = time.monotonic() window_start = now - self.window_seconds hits = self._hits[key] while hits and hits[0] < window_start: hits.pop(0) if len(hits) >= self.max_requests: return False hits.append(now) return True