security: 🔒 N8N webhook URLs спрятаны через backend proxy
- Создан n8n_proxy.py для безопасного проксирования запросов - Webhook URLs перенесены в .env (скрыты от фронтенда) - Frontend теперь использует /api/n8n/* endpoints - Добавлена документация SECURITY_N8N_PROXY.md Преимущества: - Webhook URLs не видны в DevTools - Централизованное логирование - Возможность добавить rate limiting и auth - Легко менять URLs без пересборки фронтенда
This commit is contained in:
345
SECURITY_N8N_PROXY.md
Normal file
345
SECURITY_N8N_PROXY.md
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
# 🔒 Безопасность: 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)
|
||||||
|
|
||||||
128
backend/app/api/n8n_proxy.py
Normal file
128
backend/app/api/n8n_proxy.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""
|
||||||
|
N8N Webhook Proxy Router
|
||||||
|
Безопасное проксирование запросов к n8n webhooks.
|
||||||
|
Frontend не знает прямых URL webhooks!
|
||||||
|
"""
|
||||||
|
import httpx
|
||||||
|
import logging
|
||||||
|
from fastapi import APIRouter, HTTPException, File, UploadFile, Form, Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
router = APIRouter(prefix="/api/n8n", tags=["n8n-proxy"])
|
||||||
|
|
||||||
|
|
||||||
|
# URL webhooks из .env (будут добавлены)
|
||||||
|
N8N_POLICY_CHECK_WEBHOOK = getattr(settings, 'n8n_policy_check_webhook', None)
|
||||||
|
N8N_FILE_UPLOAD_WEBHOOK = getattr(settings, 'n8n_file_upload_webhook', None)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/policy/check")
|
||||||
|
async def proxy_policy_check(request: Request):
|
||||||
|
"""
|
||||||
|
Проксирует проверку полиса к n8n webhook
|
||||||
|
|
||||||
|
Frontend отправляет: POST /api/n8n/policy/check
|
||||||
|
Backend проксирует к: https://n8n.clientright.pro/webhook/{uuid}
|
||||||
|
"""
|
||||||
|
if not N8N_POLICY_CHECK_WEBHOOK:
|
||||||
|
raise HTTPException(status_code=500, detail="N8N webhook не настроен")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Получаем JSON body от фронтенда
|
||||||
|
body = await request.json()
|
||||||
|
|
||||||
|
logger.info(f"🔄 Proxy policy check: {body.get('policy_number', 'unknown')}")
|
||||||
|
|
||||||
|
# Проксируем запрос к n8n
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
response = await client.post(
|
||||||
|
N8N_POLICY_CHECK_WEBHOOK,
|
||||||
|
json=body,
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info(f"✅ Policy check success")
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"❌ N8N returned {response.status_code}: {response.text}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=response.status_code,
|
||||||
|
detail=f"N8N error: {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
logger.error("⏱️ N8N webhook timeout")
|
||||||
|
raise HTTPException(status_code=504, detail="Таймаут подключения к n8n")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error proxying to n8n: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка проверки полиса: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload/file")
|
||||||
|
async def proxy_file_upload(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
claim_id: Optional[str] = Form(None),
|
||||||
|
voucher: Optional[str] = Form(None),
|
||||||
|
session_id: Optional[str] = Form(None),
|
||||||
|
file_type: Optional[str] = Form(None)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Проксирует загрузку файла к n8n webhook
|
||||||
|
|
||||||
|
Frontend отправляет: POST /api/n8n/upload/file (multipart/form-data)
|
||||||
|
Backend проксирует к: https://n8n.clientright.pro/webhook/{uuid}
|
||||||
|
"""
|
||||||
|
if not N8N_FILE_UPLOAD_WEBHOOK:
|
||||||
|
raise HTTPException(status_code=500, detail="N8N upload webhook не настроен")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"🔄 Proxy file upload: {file.filename} for claim {claim_id}")
|
||||||
|
|
||||||
|
# Читаем файл
|
||||||
|
file_content = await file.read()
|
||||||
|
|
||||||
|
# Формируем multipart/form-data для n8n
|
||||||
|
files = {
|
||||||
|
'file': (file.filename, file_content, file.content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if claim_id:
|
||||||
|
data['claim_id'] = claim_id
|
||||||
|
if voucher:
|
||||||
|
data['voucher'] = voucher
|
||||||
|
if session_id:
|
||||||
|
data['session_id'] = session_id
|
||||||
|
if file_type:
|
||||||
|
data['file_type'] = file_type
|
||||||
|
|
||||||
|
# Проксируем запрос к n8n
|
||||||
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
|
response = await client.post(
|
||||||
|
N8N_FILE_UPLOAD_WEBHOOK,
|
||||||
|
files=files,
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info(f"✅ File upload success")
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"❌ N8N returned {response.status_code}: {response.text}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=response.status_code,
|
||||||
|
detail=f"N8N error: {response.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
logger.error("⏱️ N8N webhook timeout")
|
||||||
|
raise HTTPException(status_code=504, detail="Таймаут загрузки файла")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Error proxying file to n8n: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка загрузки файла: {str(e)}")
|
||||||
|
|
||||||
@@ -156,6 +156,12 @@ class Settings(BaseSettings):
|
|||||||
return [origin.strip() for origin in self.cors_origins.split(",")]
|
return [origin.strip() for origin in self.cors_origins.split(",")]
|
||||||
return self.cors_origins
|
return self.cors_origins
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# N8N WEBHOOKS (скрыты от фронтенда)
|
||||||
|
# ============================================
|
||||||
|
n8n_policy_check_webhook: str = ""
|
||||||
|
n8n_file_upload_webhook: str = ""
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# LOGGING
|
# LOGGING
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from .services.redis_service import redis_service
|
|||||||
from .services.rabbitmq_service import rabbitmq_service
|
from .services.rabbitmq_service import rabbitmq_service
|
||||||
from .services.policy_service import policy_service
|
from .services.policy_service import policy_service
|
||||||
from .services.s3_service import s3_service
|
from .services.s3_service import s3_service
|
||||||
from .api import sms, claims, policy, upload, draft, events
|
from .api import sms, claims, policy, upload, draft, events, n8n_proxy
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -99,6 +99,7 @@ app.include_router(policy.router)
|
|||||||
app.include_router(upload.router)
|
app.include_router(upload.router)
|
||||||
app.include_router(draft.router)
|
app.include_router(draft.router)
|
||||||
app.include_router(events.router)
|
app.include_router(events.router)
|
||||||
|
app.include_router(n8n_proxy.router) # 🔒 Безопасный proxy к n8n webhooks
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -196,8 +196,9 @@ export default function Step1Policy({ formData, updateFormData, onNext, addDebug
|
|||||||
|
|
||||||
addDebugEvent?.('policy_check', 'pending', `Проверяю полис: ${values.voucher}`, { voucher: values.voucher });
|
addDebugEvent?.('policy_check', 'pending', `Проверяю полис: ${values.voucher}`, { voucher: values.voucher });
|
||||||
|
|
||||||
// Проверка полиса через n8n вебхук + создание записи в БД
|
// Проверка полиса через backend API (proxy к n8n)
|
||||||
const response = await fetch('https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265', {
|
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',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -317,7 +318,8 @@ export default function Step1Policy({ formData, updateFormData, onNext, addDebug
|
|||||||
uploadFormData.append('file', pdfFile); // PDF файл!
|
uploadFormData.append('file', pdfFile); // PDF файл!
|
||||||
|
|
||||||
setUploadProgress(`📡 Загружаем ${pdfFile.name} в облако...`);
|
setUploadProgress(`📡 Загружаем ${pdfFile.name} в облако...`);
|
||||||
const uploadResponse = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', {
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8100';
|
||||||
|
const uploadResponse = await fetch(`${API_BASE_URL}/api/n8n/upload/file`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: uploadFormData,
|
body: uploadFormData,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -151,8 +151,9 @@ const StepDocumentUpload: React.FC<Props> = ({
|
|||||||
eventSource.close();
|
eventSource.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Отправляем файл на сервер (n8n webhook)
|
// Отправляем файл на сервер через backend API (proxy к n8n)
|
||||||
const response = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', {
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8100';
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/n8n/upload/file`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formDataToSend,
|
body: formDataToSend,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user