Spaces:
Sleeping
Sleeping
v4.2: Update api/main.py
Browse files- api/main.py +18 -11
api/main.py
CHANGED
|
@@ -58,8 +58,9 @@ HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "")
|
|
| 58 |
SAULLM_ENDPOINT = os.environ.get("SAULLM_ENDPOINT", "")
|
| 59 |
MAX_TEXT_LENGTH = int(os.environ.get("MAX_TEXT_LENGTH", "200000"))
|
| 60 |
|
| 61 |
-
# βββ FIX v4.
|
| 62 |
_rate_limits: dict[str, list[float]] = {}
|
|
|
|
| 63 |
RATE_LIMIT_REQUESTS = 30
|
| 64 |
RATE_LIMIT_WINDOW = 60 # seconds
|
| 65 |
|
|
@@ -71,8 +72,17 @@ def _get_client_ip(request: Request) -> str:
|
|
| 71 |
return request.client.host if request.client else "unknown"
|
| 72 |
|
| 73 |
def _check_rate_limit(client_ip: str) -> bool:
|
| 74 |
-
"""Sliding window rate limiter."""
|
|
|
|
| 75 |
now = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
if client_ip not in _rate_limits:
|
| 77 |
_rate_limits[client_ip] = []
|
| 78 |
|
|
@@ -85,13 +95,6 @@ def _check_rate_limit(client_ip: str) -> bool:
|
|
| 85 |
return False
|
| 86 |
|
| 87 |
_rate_limits[client_ip].append(now)
|
| 88 |
-
|
| 89 |
-
# Periodic cleanup of stale IPs (every 100 requests)
|
| 90 |
-
if len(_rate_limits) > 1000:
|
| 91 |
-
stale = [ip for ip, ts in _rate_limits.items() if not ts or now - ts[-1] > RATE_LIMIT_WINDOW * 2]
|
| 92 |
-
for ip in stale:
|
| 93 |
-
del _rate_limits[ip]
|
| 94 |
-
|
| 95 |
return True
|
| 96 |
|
| 97 |
# βββ Supabase helper βββ
|
|
@@ -193,11 +196,15 @@ async def lifespan(app: FastAPI):
|
|
| 193 |
|
| 194 |
app = FastAPI(title="ClauseGuard API", version="4.1.0", lifespan=lifespan)
|
| 195 |
|
|
|
|
|
|
|
| 196 |
ALLOWED_ORIGINS = [
|
| 197 |
"https://clauseguardweb.netlify.app",
|
| 198 |
-
"http://localhost:3000",
|
| 199 |
-
"http://localhost:3001",
|
| 200 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
app.add_middleware(
|
| 202 |
CORSMiddleware,
|
| 203 |
allow_origins=ALLOWED_ORIGINS,
|
|
|
|
| 58 |
SAULLM_ENDPOINT = os.environ.get("SAULLM_ENDPOINT", "")
|
| 59 |
MAX_TEXT_LENGTH = int(os.environ.get("MAX_TEXT_LENGTH", "200000"))
|
| 60 |
|
| 61 |
+
# βββ FIX v4.2: Improved sliding window rate limiter with periodic cleanup βββ
|
| 62 |
_rate_limits: dict[str, list[float]] = {}
|
| 63 |
+
_rate_limits_last_cleanup: float = 0.0
|
| 64 |
RATE_LIMIT_REQUESTS = 30
|
| 65 |
RATE_LIMIT_WINDOW = 60 # seconds
|
| 66 |
|
|
|
|
| 72 |
return request.client.host if request.client else "unknown"
|
| 73 |
|
| 74 |
def _check_rate_limit(client_ip: str) -> bool:
|
| 75 |
+
"""Sliding window rate limiter with periodic stale-IP cleanup."""
|
| 76 |
+
global _rate_limits_last_cleanup
|
| 77 |
now = time.time()
|
| 78 |
+
|
| 79 |
+
# FIX v4.2: Periodic cleanup every 60s regardless of dict size
|
| 80 |
+
if now - _rate_limits_last_cleanup > 60:
|
| 81 |
+
stale = [ip for ip, ts in _rate_limits.items() if not ts or now - ts[-1] > RATE_LIMIT_WINDOW * 2]
|
| 82 |
+
for ip in stale:
|
| 83 |
+
del _rate_limits[ip]
|
| 84 |
+
_rate_limits_last_cleanup = now
|
| 85 |
+
|
| 86 |
if client_ip not in _rate_limits:
|
| 87 |
_rate_limits[client_ip] = []
|
| 88 |
|
|
|
|
| 95 |
return False
|
| 96 |
|
| 97 |
_rate_limits[client_ip].append(now)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
return True
|
| 99 |
|
| 100 |
# βββ Supabase helper βββ
|
|
|
|
| 196 |
|
| 197 |
app = FastAPI(title="ClauseGuard API", version="4.1.0", lifespan=lifespan)
|
| 198 |
|
| 199 |
+
# FIX v4.2: CORS origins configurable via env var; localhost only in dev
|
| 200 |
+
_extra_origins = os.environ.get("CORS_EXTRA_ORIGINS", "").split(",")
|
| 201 |
ALLOWED_ORIGINS = [
|
| 202 |
"https://clauseguardweb.netlify.app",
|
|
|
|
|
|
|
| 203 |
]
|
| 204 |
+
# Only add localhost origins if explicitly enabled via env
|
| 205 |
+
if os.environ.get("CORS_ALLOW_LOCALHOST", "").lower() == "true":
|
| 206 |
+
ALLOWED_ORIGINS.extend(["http://localhost:3000", "http://localhost:3001"])
|
| 207 |
+
ALLOWED_ORIGINS.extend([o.strip() for o in _extra_origins if o.strip()])
|
| 208 |
app.add_middleware(
|
| 209 |
CORSMiddleware,
|
| 210 |
allow_origins=ALLOWED_ORIGINS,
|