"""RFC 7591 dynamic client registration helpers (G3X, 2026-10-19).""" from __future__ import annotations import re import time from collections import defaultdict, deque from urllib.parse import urlparse from fastapi import Request _SCHEME_RE = re.compile(r"^[a-z][a-z0-9+.\-]*$") _BAD_SCHEMES = {"javascript", "data", "file", "vbscript", "about", "blob", "ftp", "ws", "wss"} _LOOPBACK = {"localhost", "127.0.0.1", "::1"} SUPPORTED_GRANTS = {"authorization_code", "refresh_token"} def valid_redirect_uri(uri: object) -> bool: """https URLs, http loopback URLs, or private app schemes (cursor://...).""" if not isinstance(uri, str) or not uri or len(uri) > 2000 or "*" in uri: return False if any(c.isspace() or ord(c) < 32 for c in uri): return False p = urlparse(uri) if p.fragment or not p.scheme: return False if p.scheme == "https": return bool(p.hostname) if p.scheme == "http": return p.hostname in _LOOPBACK return bool(_SCHEME_RE.match(p.scheme)) and p.scheme not in _BAD_SCHEMES class RateLimiter: def __init__(self, max_events: int, window_seconds: float) -> None: self.max_events = max_events self.window = window_seconds self._events: dict[str, deque[float]] = defaultdict(deque) def hit(self, key: str) -> bool: """Record an event; return True when the limit is exceeded.""" now = time.time() q = self._events[key] while q and q[0] < now - self.window: q.popleft() if len(q) >= self.max_events: return True q.append(now) return False def client_ip(request: Request) -> str: fwd = request.headers.get("x-forwarded-for", "") if fwd: return fwd.split(",")[0].strip() return request.client.host if request.client else "unknown"