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:
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 self.cors_origins
|
||||
|
||||
# ============================================
|
||||
# N8N WEBHOOKS (скрыты от фронтенда)
|
||||
# ============================================
|
||||
n8n_policy_check_webhook: str = ""
|
||||
n8n_file_upload_webhook: str = ""
|
||||
|
||||
# ============================================
|
||||
# LOGGING
|
||||
# ============================================
|
||||
|
||||
@@ -12,7 +12,7 @@ from .services.redis_service import redis_service
|
||||
from .services.rabbitmq_service import rabbitmq_service
|
||||
from .services.policy_service import policy_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(
|
||||
@@ -99,6 +99,7 @@ app.include_router(policy.router)
|
||||
app.include_router(upload.router)
|
||||
app.include_router(draft.router)
|
||||
app.include_router(events.router)
|
||||
app.include_router(n8n_proxy.router) # 🔒 Безопасный proxy к n8n webhooks
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
Reference in New Issue
Block a user