|
15 | 15 | from backend.types import BasicAnalysis |
16 | 16 | from bleach.sanitizer import Cleaner |
17 | 17 | from backend.errors import register_error_handlers, ValidationError |
| 18 | +import time |
18 | 19 |
|
19 | 20 | # Настраиваем логирование |
20 | 21 | logging.basicConfig(level=logging.DEBUG) |
@@ -65,10 +66,39 @@ def _block_external_url_fetcher(url): |
65 | 66 | r"/api/*": { |
66 | 67 | "origins": ["http://localhost:3000"], |
67 | 68 | "methods": ["GET", "POST", "OPTIONS"], |
68 | | - "allow_headers": ["Content-Type"] |
| 69 | + "allow_headers": ["Content-Type", "X-API-Key"] |
69 | 70 | } |
70 | 71 | }) |
71 | 72 |
|
| 73 | +# Опциональная API-авторизация и наивный rate limiting |
| 74 | +API_KEY = os.getenv("API_KEY") # если не задан, проверка отключена |
| 75 | +RATE_LIMIT_WINDOW_SEC = int(os.getenv("RATE_LIMIT_WINDOW_SEC", "60")) |
| 76 | +RATE_LIMIT_MAX_REQ = int(os.getenv("RATE_LIMIT_MAX_REQ", "60")) |
| 77 | +_rate_limit_store: dict[str, list[float]] = {} |
| 78 | + |
| 79 | +def _client_id() -> str: |
| 80 | + return request.headers.get("X-Forwarded-For", request.remote_addr or "unknown") |
| 81 | + |
| 82 | +@app.before_request |
| 83 | +def _security_and_rate_limit(): |
| 84 | + if not request.path.startswith("/api/"): |
| 85 | + return |
| 86 | + if API_KEY: |
| 87 | + provided = request.headers.get("X-API-Key") |
| 88 | + if provided != API_KEY: |
| 89 | + return jsonify({"error": "Unauthorized"}), 401 |
| 90 | + now = time.time() |
| 91 | + cid = _client_id() |
| 92 | + bucket = _rate_limit_store.get(cid, []) |
| 93 | + cutoff = now - RATE_LIMIT_WINDOW_SEC |
| 94 | + bucket = [ts for ts in bucket if ts > cutoff] |
| 95 | + if len(bucket) >= RATE_LIMIT_MAX_REQ: |
| 96 | + retry_after = int(bucket[0] + RATE_LIMIT_WINDOW_SEC - now) + 1 |
| 97 | + return jsonify({"error": "Too Many Requests", "retry_after": retry_after}), 429 |
| 98 | + bucket.append(now) |
| 99 | + _rate_limit_store[cid] = bucket |
| 100 | + |
| 101 | + |
72 | 102 | def perform_basic_analysis(df: pd.DataFrame) -> BasicAnalysis: |
73 | 103 | """Выполняет базовый анализ данных DataFrame.""" |
74 | 104 | logger.debug(f"Starting basic analysis. DataFrame shape: {df.shape}") |
|
0 commit comments