Files
aiform_dev/SECURITY_N8N_PROXY.md
AI Assistant ef6a4160a4 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 без пересборки фронтенда
2025-10-29 16:49:03 +03:00

11 KiB
Raw Blame History

🔒 Безопасность: N8N Webhook Proxy

Проблема

Раньше: Webhook URLs n8n были захардкожены в коде фронтенда:

// ❌ ПЛОХО - 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

@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 (в корне проекта)

# 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

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

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
// ✅ ХОРОШО - используем 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

Мониторинг

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

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

cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform

# Rebuild frontend с новым кодом
docker-compose build frontend
docker-compose up -d frontend

Тестирование

1. Проверка полиса через proxy

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"
  }'

Ожидаемый ответ:

{
  "success": true,
  "policy": {
    "found": true,
    "voucher": "E1000-302372730",
    "insured_persons": [...]
  }
}

2. Загрузка файла через proxy

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"

Ожидаемый ответ:

{
  "success": true,
  "file_id": "uuid",
  "s3_url": "https://..."
}

3. Проверка что прямой доступ к n8n теперь не работает

# Этот запрос теперь НЕ используется фронтендом!
curl https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265

Дополнительные улучшения (опционально)

1. Rate Limiting

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

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

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)