Spaces:
Running
Running
File size: 1,707 Bytes
b324c71 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | """
Shared checks for the public, no-sign-in forms (/bsa and /bff): a simple rate
limiter and the contact-detail validators. Rate limits are in-memory, per
process (same trade-off as routes/help.py).
"""
import re
import threading
import time
from collections import defaultdict
from fastapi import HTTPException
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
# Digits with an optional leading +, allowing the spaces, dashes, dots and brackets people type.
PHONE_RE = re.compile(r"^\+?[\d\s().-]+$")
class RateLimiter:
"""Allows `count` hits per `period` seconds for each key."""
def __init__(self):
self._lock = threading.Lock()
self._hits: dict[str, list[float]] = defaultdict(list)
def allow(self, key: str, count: int, period: int) -> bool:
now = time.time()
with self._lock:
recent = [t for t in self._hits[key] if now - t < period]
allowed = len(recent) < count
if allowed:
recent.append(now)
self._hits[key] = recent
return allowed
def reset(self):
with self._lock:
self._hits.clear()
def clean_email(raw: str) -> str:
email = raw.strip().lower()
if not EMAIL_RE.match(email):
raise HTTPException(status_code=400, detail="Enter a valid email address.")
return email
def clean_phone(raw: str) -> str:
"""Trimmed phone number, kept as typed apart from spacing. 7 to 15 digits."""
phone = " ".join(raw.split())
digits = re.sub(r"\D", "", phone)
if not PHONE_RE.match(phone) or not 7 <= len(digits) <= 15:
raise HTTPException(status_code=400, detail="Enter a valid phone number.")
return phone
|