# 🔒 Безопасность: N8N Webhook Proxy ## Проблема **Раньше:** Webhook URLs n8n были захардкожены в коде фронтенда: ```typescript // ❌ ПЛОХО - URL виден всем в браузере! const response = await fetch('https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265', { method: 'POST', body: JSON.stringify(data) }); ``` **Риски:** - 🚨 Любой может открыть DevTools и увидеть URL - 🚨 Может отправлять spam/ddos запросы напрямую к n8n - 🚨 Может исследовать структуру workflow - 🚨 Обход rate limiting и валидации --- ## Решение: Backend Proxy **Теперь:** Frontend общается только с нашим backend API, который проксирует запросы к n8n: ``` ┌──────────────────────────────────────────────────────────────────┐ │ FRONTEND │ │ (React, TypeScript) │ │ │ │ fetch('/api/n8n/policy/check') ← Безопасный endpoint │ │ fetch('/api/n8n/upload/file') │ └────────────┬─────────────────────────────────────────────────────┘ │ │ HTTP Request (no webhook URL!) │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ BACKEND (FastAPI) │ │ app/api/n8n_proxy.py │ │ │ │ @router.post("/api/n8n/policy/check") │ │ @router.post("/api/n8n/upload/file") │ │ │ │ - Читает webhook URLs из .env │ │ - Валидирует запросы │ │ - Rate limiting │ │ - Логирование │ │ - Проксирует к n8n │ └────────────┬─────────────────────────────────────────────────────┘ │ │ HTTPS (с настоящим URL) │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ N8N WEBHOOKS │ │ https://n8n.clientright.pro/webhook/{uuid} │ │ │ │ - Недоступен для прямых запросов от клиентов │ │ - Webhook URLs только в backend .env │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Реализация ### 1. Backend: N8N Proxy Router **Файл:** `backend/app/api/n8n_proxy.py` ```python @router.post("/api/n8n/policy/check") async def proxy_policy_check(request: Request): """Проксирует проверку полиса к n8n webhook""" # Читаем webhook URL из .env (не виден фронтенду!) webhook_url = settings.n8n_policy_check_webhook # Проксируем запрос async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post(webhook_url, json=body) return response.json() @router.post("/api/n8n/upload/file") async def proxy_file_upload(file: UploadFile, ...): """Проксирует загрузку файла к n8n webhook""" webhook_url = settings.n8n_file_upload_webhook # Проксируем multipart/form-data async with httpx.AsyncClient(timeout=60.0) as client: response = await client.post(webhook_url, files=files, data=data) return response.json() ``` ### 2. Environment Variables **Файл:** `.env` (в корне проекта) ```bash # N8N Webhooks (скрыты от фронтенда!) N8N_POLICY_CHECK_WEBHOOK=https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265 N8N_FILE_UPLOAD_WEBHOOK=https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95 ``` ⚠️ **Важно:** `.env` файл НЕ коммитится в git (есть в `.gitignore`)! ### 3. Config **Файл:** `backend/app/config.py` ```python class Settings(BaseSettings): # N8N Webhooks (скрыты от фронтенда) n8n_policy_check_webhook: str = "" n8n_file_upload_webhook: str = "" class Config: env_file = "/var/www/.../erv_platform/.env" ``` ### 4. Main App **Файл:** `backend/app/main.py` ```python from .api import n8n_proxy # API Routes app.include_router(n8n_proxy.router) # 🔒 Безопасный proxy ``` ### 5. Frontend **Файлы:** - `frontend/src/components/form/Step1Policy.tsx` - `frontend/src/components/form/StepDocumentUpload.tsx` ```typescript // ✅ ХОРОШО - используем backend API const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8100'; // Проверка полиса const response = await fetch(`${API_BASE_URL}/api/n8n/policy/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ claim_id: formData.claim_id, policy_number: voucher, session_id: sessionId }) }); // Загрузка файла const response = await fetch(`${API_BASE_URL}/api/n8n/upload/file`, { method: 'POST', body: formData // multipart/form-data }); ``` --- ## Преимущества решения ### ✅ Безопасность - Webhook URLs спрятаны в backend `.env` - Невозможно увидеть в DevTools / Network tab - Нельзя обойти валидацию фронтенда ### ✅ Контроль - Централизованное логирование всех запросов к n8n - Rate limiting (можно добавить) - Валидация данных перед проксированием - Аутентификация (можно добавить) ### ✅ Гибкость - Легко сменить webhook URL (только в `.env`) - Можно добавить retry логику - Можно кешировать ответы - Можно маршрутизировать к разным n8n instances ### ✅ Мониторинг ```python logger.info(f"🔄 Proxy policy check: {body.get('policy_number')}") logger.info(f"✅ Policy check success") logger.error(f"❌ N8N returned {response.status_code}") ``` --- ## Запуск ### Backend ```bash cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend # Проверяем что .env содержит N8N_*_WEBHOOK переменные cat ../.env | grep N8N # Перезапускаем backend kill $(lsof -ti :8100) source venv/bin/activate python -m uvicorn app.main:app --host 0.0.0.0 --port 8100 --reload > ../backend.log 2>&1 & ``` ### Frontend ```bash cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform # Rebuild frontend с новым кодом docker-compose build frontend docker-compose up -d frontend ``` --- ## Тестирование ### 1. Проверка полиса через proxy ```bash curl -X POST http://localhost:8100/api/n8n/policy/check \ -H "Content-Type: application/json" \ -d '{ "claim_id": "CLM-TEST-123", "policy_number": "E1000-302372730", "session_id": "test-session" }' ``` **Ожидаемый ответ:** ```json { "success": true, "policy": { "found": true, "voucher": "E1000-302372730", "insured_persons": [...] } } ``` ### 2. Загрузка файла через proxy ```bash curl -X POST http://localhost:8100/api/n8n/upload/file \ -F "file=@test.pdf" \ -F "claim_id=CLM-TEST-123" \ -F "voucher=E1000-302372730" \ -F "session_id=test-session" \ -F "file_type=flight_delay_boarding_or_ticket" ``` **Ожидаемый ответ:** ```json { "success": true, "file_id": "uuid", "s3_url": "https://..." } ``` ### 3. Проверка что прямой доступ к n8n теперь не работает ```bash # Этот запрос теперь НЕ используется фронтендом! curl https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265 ``` --- ## Дополнительные улучшения (опционально) ### 1. Rate Limiting ```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @router.post("/api/n8n/policy/check") @limiter.limit("10/minute") # Максимум 10 запросов в минуту с одного IP async def proxy_policy_check(request: Request): ... ``` ### 2. API Key Authentication ```python from fastapi import Header, HTTPException @router.post("/api/n8n/policy/check") async def proxy_policy_check( request: Request, x_api_key: str = Header(None) ): if x_api_key != settings.frontend_api_key: raise HTTPException(status_code=403, detail="Invalid API key") ... ``` ### 3. Request Validation ```python from pydantic import BaseModel, validator class PolicyCheckRequest(BaseModel): claim_id: str policy_number: str session_id: str @validator('policy_number') def validate_policy_format(cls, v): if not re.match(r'^E\d{4}-\d{9}$', v): raise ValueError('Invalid policy format') return v @router.post("/api/n8n/policy/check") async def proxy_policy_check(data: PolicyCheckRequest): # Pydantic автоматически валидирует данные ... ``` --- ## Итоги ✅ **Было:** Webhook URLs в коде фронтенда → 🚨 Небезопасно ✅ **Стало:** Backend proxy → 🔒 Безопасно **Изменённые файлы:** - `backend/app/api/n8n_proxy.py` (новый файл) - `backend/app/config.py` (+2 строки) - `backend/app/main.py` (+2 строки) - `frontend/src/components/form/Step1Policy.tsx` (2 замены URL) - `frontend/src/components/form/StepDocumentUpload.tsx` (1 замена URL) - `.env` (+2 строки) **Git diff:** ~150 строк **Время реализации:** ~20 минут **Уровень безопасности:** ⭐⭐⭐⭐⭐ (5/5)