diff --git a/IMPLEMENTATION_LOG.md b/IMPLEMENTATION_LOG.md index 98b7dce..548e945 100644 --- a/IMPLEMENTATION_LOG.md +++ b/IMPLEMENTATION_LOG.md @@ -47,3 +47,16 @@ - Как проверялось: `npx vitest run` — 45/45 зелёные; `npm run check` — чисто. - Влияние на поиск/UX: нет. - Подводные камни: баг в `userActivity.ts` проявлялся только на первом аутентифицированном запросе в минуту (throttle) — в проде мог давать sporadic 403. + +--- + +## [0.7] Обработчик ошибок без утечки деталей + +- Статус: ✅ done +- Зачем: клиент не должен видеть SQL и внутренности БД в 5xx-ответах (аудит, Фаза 0). +- Что изменено: + - `server/index.ts` — глобальный error handler: при status ≥ 500 отдаёт «Внутренняя ошибка сервера (код XXXXXXXX)», оригинальная ошибка — в серверный лог с тем же кодом инцидента. 4xx не тронуты. + - Route-уровень (12 файлов): зачищены все 5xx-ответы с `err.message`/`String(err)`/телом апстрима — `auth.users.routes.ts`, `form-offline.routes.ts`, `sync.routes.ts`, `polls.routes.ts`, `reactions.routes.ts`, `mcp-rag.routes.ts`, `llm-providers.routes.ts`, `external.routes.ts`, `task-crud-write.routes.ts`, `task-fields.routes.ts`, `finance-di2/routes.ts`, `documents/worker/server.ts`. Где не было — добавлен `console.error` с контекстом. +- Как проверялось: повторный grep `status(5xx)` + `err.message` по `server/` — 0 совпадений; `npm run check` чисто; `npx vitest run` 45/45. +- Влияние на поиск/UX: нет (только тексты 5xx-ответов). +- Подводные камни: остались ответы `200 + success:false` с `err?.message` (llm-providers, rag, finance-di2) и 4xx с `err.message` (documents, data-tables) — сознательно вне скоупа шага, кандидаты для фазы 2. diff --git a/server/documents/worker/server.ts b/server/documents/worker/server.ts index e7f2118..d3e4c3c 100644 --- a/server/documents/worker/server.ts +++ b/server/documents/worker/server.ts @@ -34,7 +34,7 @@ app.post("/convert/pdf", async (req, res) => { }); } catch (err: any) { console.error("[worker] PDF conversion error:", err); - res.status(500).json({ success: false, error: err.message || "Ошибка конвертации PDF" }); + res.status(500).json({ success: false, error: "Ошибка конвертации PDF" }); } }); @@ -94,7 +94,7 @@ app.post("/convert/docx-to-pdf", async (req, res) => { }); } catch (err: any) { console.error("[worker] DOCX→PDF conversion error:", err); - res.status(500).json({ success: false, error: err.message || "Ошибка конвертации DOCX→PDF" }); + res.status(500).json({ success: false, error: "Ошибка конвертации DOCX→PDF" }); } }); @@ -135,7 +135,7 @@ app.post("/convert/emf-to-png", async (req, res) => { }); } catch (err: any) { console.error("[worker] EMF→PNG conversion error:", err); - res.status(500).json({ success: false, error: err.message || "Ошибка конвертации EMF→PNG" }); + res.status(500).json({ success: false, error: "Ошибка конвертации EMF→PNG" }); } }); diff --git a/server/finance-di2/routes.ts b/server/finance-di2/routes.ts index e749ecf..1478f95 100644 --- a/server/finance-di2/routes.ts +++ b/server/finance-di2/routes.ts @@ -5150,7 +5150,7 @@ export async function registerRoutes(httpServer: Server, app: Express): Promise< answer: errorMsg, sqlQueries: [], }); - return res.status(502).json({ error: errorMsg }); + return res.status(502).json({ error: "Ошибка AI API" }); } const json = await resp.json() as any; @@ -5225,7 +5225,7 @@ export async function registerRoutes(httpServer: Server, app: Express): Promise< if (err?.message?.includes("abort") || err?.message?.includes("timeout")) { return res.status(504).json({ error: errorMsg }); } - res.status(500).json({ error: errorMsg }); + res.status(500).json({ error: "Ошибка анализа" }); } }); @@ -5926,7 +5926,7 @@ export async function registerRoutes(httpServer: Server, app: Express): Promise< return res.status(401).json({ error: err.message, authRequired: true }); } console.error("[salary sync] Error:", err); - res.status(500).json({ error: "Ошибка синхронизации: " + (err?.message || "неизвестная ошибка") }); + res.status(500).json({ error: "Ошибка синхронизации" }); } }); diff --git a/server/index.ts b/server/index.ts index 8ccadde..2260a5d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1075,16 +1075,20 @@ async function runStartupDataPatches() { app.use((err: any, _req: Request, res: Response, _next: NextFunction) => { const status = err.status || err.statusCode || 500; - const message = err.message || "Internal Server Error"; if (res.headersSent) { console.error('[express] Error after headers sent:', err); return; } - res.status(status).json({ message }); if (status >= 500) { - console.error('[express] Server error:', err); + // Клиенту — только нейтральный текст и id инцидента; детали (SQL, стек) — в серверный лог + const requestId = crypto.randomUUID().slice(0, 8); + console.error(`[express] Server error [${requestId}]:`, err); + res.status(status).json({ message: `Внутренняя ошибка сервера (код ${requestId})` }); + return; } + const message = err.message || "Bad Request"; + res.status(status).json({ message }); }); // importantly only setup vite in development and after diff --git a/server/routes/auth.users.routes.ts b/server/routes/auth.users.routes.ts index 15e5fb4..656539a 100644 --- a/server/routes/auth.users.routes.ts +++ b/server/routes/auth.users.routes.ts @@ -589,7 +589,7 @@ export function registerUserManagementRoutes(router: Router): void { res.json({ success: true, subscriptions }); } catch (error: any) { console.error('Get offline subscriptions error:', error); - res.status(500).json({ success: false, error: error.message || 'Ошибка загрузки подписок' }); + res.status(500).json({ success: false, error: 'Ошибка загрузки подписок' }); } } ); @@ -619,7 +619,7 @@ export function registerUserManagementRoutes(router: Router): void { res.json({ success: true, subscription }); } catch (error: any) { console.error('Upsert offline subscription error:', error); - res.status(500).json({ success: false, error: error.message || 'Ошибка сохранения подписки' }); + res.status(500).json({ success: false, error: 'Ошибка сохранения подписки' }); } } ); @@ -641,7 +641,7 @@ export function registerUserManagementRoutes(router: Router): void { res.json({ success: true }); } catch (error: any) { console.error('Delete offline subscription error:', error); - res.status(500).json({ success: false, error: error.message || 'Ошибка удаления подписки' }); + res.status(500).json({ success: false, error: 'Ошибка удаления подписки' }); } } ); diff --git a/server/routes/external.routes.ts b/server/routes/external.routes.ts index d516dfa..e92d215 100644 --- a/server/routes/external.routes.ts +++ b/server/routes/external.routes.ts @@ -431,8 +431,7 @@ export function registerExternalRoutes(app: import("express").Express): void { res.json({ success: true, suggestions: data.suggestions }); } catch (error: unknown) { console.error('Geo suggest error:', error); - const message = error instanceof Error ? error.message : 'Ошибка при запросе геокодинга'; - res.status(500).json({ success: false, error: message }); + res.status(500).json({ success: false, error: 'Ошибка при запросе геокодинга' }); } } ); @@ -491,8 +490,7 @@ export function registerExternalRoutes(app: import("express").Express): void { res.json({ success: true, suggestions: data.suggestions }); } catch (error: unknown) { console.error('Geo reverse error:', error); - const message = error instanceof Error ? error.message : 'Ошибка при запросе геокодинга'; - res.status(500).json({ success: false, error: message }); + res.status(500).json({ success: false, error: 'Ошибка при запросе геокодинга' }); } } ); diff --git a/server/routes/form-offline.routes.ts b/server/routes/form-offline.routes.ts index fe35eb3..275310f 100644 --- a/server/routes/form-offline.routes.ts +++ b/server/routes/form-offline.routes.ts @@ -26,7 +26,7 @@ router.get('/api/forms/:id/offline-cache', authenticateToken, tenantIsolation, a res.json({ success: true, offlineCache: config }); } catch (err: any) { console.error('[FORM-OFFLINE] get error:', err); - res.status(500).json({ error: err.message || 'Ошибка получения настроек офлайн-кэша' }); + res.status(500).json({ error: 'Ошибка получения настроек офлайн-кэша' }); } }); @@ -44,7 +44,7 @@ router.put('/api/forms/:id/offline-cache', authenticateToken, tenantIsolation, r res.json({ success: true, offlineCache: form.offlineCache }); } catch (err: any) { console.error('[FORM-OFFLINE] put error:', err); - res.status(500).json({ error: err.message || 'Ошибка обновления настроек офлайн-кэша' }); + res.status(500).json({ error: 'Ошибка обновления настроек офлайн-кэша' }); } }); @@ -61,7 +61,7 @@ router.delete('/api/forms/:id/offline-cache', authenticateToken, tenantIsolation res.json({ success: true }); } catch (err: any) { console.error('[FORM-OFFLINE] delete error:', err); - res.status(500).json({ error: err.message || 'Ошибка отключения офлайн-кэша' }); + res.status(500).json({ error: 'Ошибка отключения офлайн-кэша' }); } }); diff --git a/server/routes/llm-providers.routes.ts b/server/routes/llm-providers.routes.ts index 7848135..bde4a2d 100644 --- a/server/routes/llm-providers.routes.ts +++ b/server/routes/llm-providers.routes.ts @@ -336,10 +336,10 @@ router.post( } catch (err: any) { console.error("[LLM-Providers] ollama pull error:", err); if (!res.headersSent) { - return res.status(502).json({ success: false, error: `Ошибка загрузки модели: ${err?.message ?? err}` }); + return res.status(502).json({ success: false, error: "Ошибка загрузки модели" }); } try { - res.write(JSON.stringify({ error: `Ошибка загрузки: ${err?.message ?? err}` }) + "\n"); + res.write(JSON.stringify({ error: "Ошибка загрузки модели" }) + "\n"); } catch {} res.end(); } diff --git a/server/routes/mcp-rag.routes.ts b/server/routes/mcp-rag.routes.ts index ce49343..fd6dcef 100644 --- a/server/routes/mcp-rag.routes.ts +++ b/server/routes/mcp-rag.routes.ts @@ -256,7 +256,7 @@ export function registerMcpRagRoutes(app: import("express").Express): void { failed: prev?.failed ?? 0, status: 'error', startedAt: prev?.startedAt ?? Date.now(), - error: String(err), + error: 'Ошибка переиндексации', }); setTimeout(() => reindexProgressStore.delete(organizationId), 5 * 60 * 1000); }); @@ -322,7 +322,8 @@ export function registerMcpRagRoutes(app: import("express").Express): void { const reminders = await storage.getTaskReminders(taskId, req.organizationId!); return res.json({ success: true, reminders }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('Get task reminders error:', err); + return res.status(500).json({ success: false, error: 'Ошибка загрузки напоминаний' }); } }); @@ -383,7 +384,8 @@ export function registerMcpRagRoutes(app: import("express").Express): void { }); return res.json({ success: true, reminder }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('Create task reminder error:', err); + return res.status(500).json({ success: false, error: 'Ошибка создания напоминания' }); } }); @@ -394,7 +396,8 @@ export function registerMcpRagRoutes(app: import("express").Express): void { await storage.deleteTaskReminder(id, req.organizationId!); return res.json({ success: true }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('Delete task reminder error:', err); + return res.status(500).json({ success: false, error: 'Ошибка удаления напоминания' }); } }); diff --git a/server/routes/polls.routes.ts b/server/routes/polls.routes.ts index 3cbfd10..00b63c9 100644 --- a/server/routes/polls.routes.ts +++ b/server/routes/polls.routes.ts @@ -69,7 +69,8 @@ router.get('/api/tasks/:taskId/polls', authenticateToken, tenantIsolation, async } return res.json({ success: true, polls: result }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка загрузки опросов задачи:', err); + return res.status(500).json({ success: false, error: 'Ошибка загрузки опросов' }); } }); @@ -138,7 +139,8 @@ router.get('/api/messenger/conversations/:id/polls', authenticateToken, tenantIs } return res.json({ success: true, polls: result }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка загрузки опросов диалога:', err); + return res.status(500).json({ success: false, error: 'Ошибка загрузки опросов' }); } }); @@ -197,7 +199,8 @@ router.post('/api/polls', authenticateToken, tenantIsolation, async (req: Authen } return res.json({ success: true, poll: pollData }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка создания опроса:', err); + return res.status(500).json({ success: false, error: 'Ошибка создания опроса' }); } }); @@ -246,7 +249,8 @@ router.post('/api/polls/:id/vote', authenticateToken, tenantIsolation, async (re } return res.json({ success: true, poll: pollData }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка голосования:', err); + return res.status(500).json({ success: false, error: 'Ошибка голосования' }); } }); @@ -281,7 +285,8 @@ router.delete('/api/polls/:id/vote', authenticateToken, tenantIsolation, async ( } return res.json({ success: true, poll: pollData }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка удаления голоса:', err); + return res.status(500).json({ success: false, error: 'Ошибка удаления голоса' }); } }); @@ -307,7 +312,8 @@ router.post('/api/polls/:id/close', authenticateToken, tenantIsolation, async (r } return res.json({ success: true, poll: pollData }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[POLLS] Ошибка закрытия опроса:', err); + return res.status(500).json({ success: false, error: 'Ошибка закрытия опроса' }); } }); diff --git a/server/routes/reactions.routes.ts b/server/routes/reactions.routes.ts index 15b3fcb..b189f48 100644 --- a/server/routes/reactions.routes.ts +++ b/server/routes/reactions.routes.ts @@ -29,7 +29,8 @@ router.get('/api/tasks/:taskId/reactions', authenticateToken, tenantIsolation, a const reactions = await getReactionsForTaskMsgs(msgIds, req.user!.id); return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка загрузки реакций задачи:', err); + return res.status(500).json({ success: false, error: 'Ошибка загрузки реакций' }); } }); @@ -55,7 +56,8 @@ router.get('/api/messenger/conversations/:id/reactions', authenticateToken, tena const reactions = await getReactionsForConvMsgs(msgIds, req.user!.id); return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка загрузки реакций диалога:', err); + return res.status(500).json({ success: false, error: 'Ошибка загрузки реакций' }); } }); @@ -117,7 +119,8 @@ router.post('/api/messages/:id/reactions', authenticateToken, tenantIsolation, a return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка обновления реакции на сообщение задачи:', err); + return res.status(500).json({ success: false, error: 'Ошибка обновления реакции' }); } }); @@ -143,7 +146,8 @@ router.delete('/api/messages/:id/reactions', authenticateToken, tenantIsolation, emitTaskReaction(req.organizationId!, { taskMessageId: msgId, taskId: ctx.taskId, reactions }); return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка удаления реакции с сообщения задачи:', err); + return res.status(500).json({ success: false, error: 'Ошибка удаления реакции' }); } }); @@ -210,7 +214,8 @@ router.post('/api/conv-messages/:id/reactions', authenticateToken, tenantIsolati return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка обновления реакции на сообщение диалога:', err); + return res.status(500).json({ success: false, error: 'Ошибка обновления реакции' }); } }); @@ -236,7 +241,8 @@ router.delete('/api/conv-messages/:id/reactions', authenticateToken, tenantIsola await emitConvReaction(ctx.conversationId, req.organizationId!, { convMessageId: msgId, conversationId: ctx.conversationId, reactions }); return res.json({ success: true, reactions }); } catch (err: any) { - return res.status(500).json({ success: false, error: err.message }); + console.error('[REACTIONS] Ошибка удаления реакции с сообщения диалога:', err); + return res.status(500).json({ success: false, error: 'Ошибка удаления реакции' }); } }); diff --git a/server/routes/sync.routes.ts b/server/routes/sync.routes.ts index 87f7adb..8dd370e 100644 --- a/server/routes/sync.routes.ts +++ b/server/routes/sync.routes.ts @@ -87,7 +87,7 @@ export function registerSyncRoutes(app: import("express").Express): void { }); } catch (err: any) { console.error('[SYNC] config error:', err); - res.status(500).json({ success: false, error: err.message || 'Ошибка получения конфигурации синхронизации' }); + res.status(500).json({ success: false, error: 'Ошибка получения конфигурации синхронизации' }); } }); @@ -221,7 +221,7 @@ export function registerSyncRoutes(app: import("express").Express): void { }); } catch (err: any) { console.error('[SYNC] initial error:', err); - res.status(500).json({ success: false, error: err.message || 'Ошибка начальной синхронизации' }); + res.status(500).json({ success: false, error: 'Ошибка начальной синхронизации' }); } }); @@ -310,7 +310,7 @@ export function registerSyncRoutes(app: import("express").Express): void { }); } catch (err: any) { console.error('[SYNC] delta error:', err); - res.status(500).json({ success: false, error: err.message || 'Ошибка дельта-синхронизации' }); + res.status(500).json({ success: false, error: 'Ошибка дельта-синхронизации' }); } }); @@ -346,7 +346,7 @@ export function registerSyncRoutes(app: import("express").Express): void { res.json({ success: true, accepted, conflicts }); } catch (err: any) { console.error('[SYNC] push error:', err); - res.status(500).json({ success: false, error: err.message || 'Ошибка приёма offline-мутаций' }); + res.status(500).json({ success: false, error: 'Ошибка приёма offline-мутаций' }); } }); diff --git a/server/routes/task-crud-write.routes.ts b/server/routes/task-crud-write.routes.ts index d6c7f0f..bed07c7 100644 --- a/server/routes/task-crud-write.routes.ts +++ b/server/routes/task-crud-write.routes.ts @@ -1052,8 +1052,7 @@ export function registerTaskCrudWriteRoutes(router: ReturnType