Files
iistwin/attached_assets/Pasted--1-JWT-server-utils-jwt-ts-fallback--1777879950650_1777879950651.txt
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

114 lines
9.6 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 1. Безопасность JWT — критично
**Файл:** `/server/utils/jwt.ts`
- Убрать ВСЕ fallback-значения для секретов (`'access-secret-key'`, `'refresh-secret-key'` и т.д.).
- При старте приложения, если `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET` или `JWT_SUPERADMIN_SECRET` не заданы в `process.env` — вызывать `console.error('FATAL: JWT secrets not configured')` и `process.exit(1)`.
- Разделить секреты: access, refresh, superadmin, bot — каждый свой ключ из env.
- Сроки жизни токенов вынести в env с дефолтами: access=15m, refresh=7d, superadmin=1h, bot=1y.
## 2. SSE токен — перенести из query в заголовок
**Файл:** `/server/routes.ts` (SSE endpoint) + фронт (`/client/src/...` где подключается EventSource)
- На бэкенде: endpoint `/api/events` должен читать токен из заголовка `Authorization: Bearer <token>`, а не из `req.query.token`.
- На фронте: стандартный `EventSource` не умеет заголовки. Используй нативный `fetch` с `ReadableStream` для SSE или обёртку, которая передаёт токен в заголовке. **Не используй query-параметр для токена.**
- Если фронт использует TanStack Query — создай кастомный хук `useSSE` на `fetch` + `ReadableStream.getReader()`.
## 3. Разбить routes.ts на модули
**Файлы:** создать `/server/routes/` с файлами:
- `auth.routes.ts` — логин, регистрация, refresh, logout, invite
- `task.routes.ts` — задачи, статусы, поля, kanban, фильтры
- `chat.routes.ts` — сообщения, комментарии, @mentions
- `bot.routes.ts` — Telegram/Discord боты, вебхуки
- `admin.routes.ts` — superadmin, биллинг, настройки
- `automation.routes.ts` — автоматизации, VM sandbox (если есть)
- `index.ts` — сборка всех роутов через `router.use('/api/auth', authRoutes)` и т.д.
**Важно:**
- Старый `/server/routes.ts` оставь как основной файл, но перенеси логику в модули и импортируй их.
- Все существующие endpoint'ы (`/api/login`, `/api/tasks` и т.д.) должны продолжать работать.
- Не меняй логику внутри handler'ов — только структурируй.
## 4. Транзакции для bulk-операций
**Файлы:** `/server/routes/` (где сохраняются формы, статусы, поля)
- Найди места, где в цикле `for` делаются множественные `db.insert()` или `db.update()` (например, сохранение полей формы, статусов kanban).
- Обёртывай их в `await db.transaction(async (trx) => { ... })`.
- Если одна операция в цикле падает — откатывай всю транзакцию.
## 5. Устранить N+1 в legacy endpoint'ах
**Файл:** `/server/routes/task.routes.ts` (или в старом routes.ts если ещё не разбит)
- Найди endpoint `/api/forms/:id/tasks` (legacy) и `/api/forms/:id/tasks/with-fields` (optimized).
- Сделай так, чтобы legacy endpoint использовал тот же JOIN-подход, что и optimized, **или** пометь его deprecated и перенаправь на optimized.
- Для `buildSystemFieldValues()` — замени отдельные запросы на `db.query.tasks.findMany({ with: { ... } })` с Drizzle relations.
## 6. Улучшить in-memory кэш (без Redis)
**Файл:** `/server/utils/cache.ts`
- Добавь TTL: `set(key, value, ttlMs)`. При `get` проверяй `Date.now() > createdAt + ttl`.
- Добавь инвалидацию по префиксу: `invalidatePrefix('forms:')` удалит все ключи, начинающиеся с `forms:`.
- Добавь `maxSize` (например, 1000 записей). При превышении — удаляй самые старые (LRU).
- Добавь метод `clear()` для полной очистки.
## 7. Загрузка файлов — защита и потоки
**Файл:** `/server/routes.ts` (multer конфигурация)
- **Path Traversal:** замени `key.includes('..') || key.includes('/')` на строгую валидацию: только `a-zA-Z0-9_-`, расширение из белого списка (`jpg`, `jpeg`, `png`, `pdf`, `docx`, `xlsx`, `zip`).
- **Mime-type:** проверяй `file.mimetype` против белого списка. Запрещай `application/octet-stream` без явного расширения.
- **Размер:** лимит 10 МБ для изображений, 50 МБ для документов.
- **Хранение:** для файлов > 5 МБ используй `diskStorage` в `/tmp/uploads`, а не `memoryStorage`. После обработки (S3 upload) — удаляй файл. Для маленьких файлов (< 5 МБ) можно оставить `memoryStorage`.
- **Сканирование:** добавь базовую проверку magic bytes для изображений (чтобы `.exe` переименованный в `.jpg` не прошёл).
## 8. Вебхуки — retry логика (без Redis)
**Файл:** `/server/routes/bot.routes.ts` (или где вызываются `fetch()` для вебхуков)
- Замени голый `fetch().catch()` на функцию `sendWebhook(url, payload, retries = 3)`.
- При неудаче (не 2xx или network error) — жди `2^attempt * 1000` мс (exponential backoff) и повторяй.
- Если все 3 попытки провалились — логируй ошибку в БД (новая таблица `webhook_failures` или в `task_audit_log`).
- **Ограничение:** не более 5 одновременных вебхуков — используй `p-limit` или самописный семафор (массив pending promises, max 5).
## 9. Исправить IDOR в сообщениях
**Файл:** `/server/routes/chat.routes.ts`
- Endpoint `/api/messages/:id` должен проверять, что сообщение принадлежит задаче, которая принадлежит организации текущего пользователя.
- Добавь `AND eq(tasks.organization_id, req.user.organization_id)` в запрос.
## 10. Общий аудит (входы, экспорты, ошибки)
**Файл:** `/server/utils/audit.ts` (создать)
- Создай единую функцию `logAudit({ action, userId, organizationId, taskId, details, ip })`.
- Добавь таблицу `system_audit_log` (id, action, user_id, org_id, task_id, details JSONB, ip, user_agent, created_at).
- Логируй:
- Успешный/неуспешный вход
- Выход
- Экспорт данных (Excel, CSV)
- Изменение прав пользователя
- Неудачные попытки аутентификации (rate limit сработал — тоже логируй)
- Используй `db.insert()` без `await` (fire-and-forget), но оборачивай в `.catch()` чтобы не падало основное действие.
## 11. Индексы PostgreSQL
**Файл:** `/shared/schema.ts` (Drizzle) или миграция `/server/migrations/`
- Добавь через Drizzle индексы:
- `tasks`: `organization_id` + `form_id`, `organization_id` + `current_status_id`, `organization_id` + `assigned_to`
- `task_field_values`: `task_id` + `field_id`
- `task_messages`: `task_id` + `created_at DESC`
- `task_audit_log`: `task_id` + `created_at DESC`
- `users`: `organization_id` + `role`
- Сгенерируй SQL миграцию через `drizzle-kit generate` (или напиши raw SQL файл).
## 12. Улучшить rate limiting
**Файл:** `/server/routes.ts` или `/server/index.ts`
- API лимит: снизь с 5000/15min до **600/15min** для обычных пользователей.
- SSE: отдельный лимит — **30 соединений/IP**.
- Auth endpoints (`/api/login`, `/api/register`): **5 попыток/15min/IP** (строгий лимит против brute-force).
- Используй `express-rate-limit` с `standardHeaders: true`, `legacyHeaders: false`.
## 13. VM Автоматизации — изоляция (без отдельного микросервиса)
**Файл:** `/server/routes/automation.routes.ts`
- Если в коде есть `vm.createContext` + `vm.Script` — оберни в `try/catch/finally`.
- Добавь `cpuUsage` лимит через `process.cpuUsage()` перед и после. Если скрипт съел > 100ms CPU time — убивай.
- Ограничь память: перед запуском проверяй `process.memoryUsage().heapUsed`. Если > 80% от лимита — отказывай в запуске.
- **Запускай VM в `setImmediate` или `process.nextTick`** чтобы не блокировать текущий HTTP-ответ, если это возможно. Если нет — добавь комментарий `// TODO: вынести в Worker при масштабировании`.