Files
Learn_System/backend/src/middleware/errorHandler.js
T
Maxim Dolgolyov 3a4623a60a fix: полное ревью системы — 15 исправлений безопасности и надёжности
Безопасность:
- tests/🆔 скрыть is_correct и explanation для студентов (P0)
- SQL injection: limit/offset через placeholder вместо template literal
- Stored XSS: stripTags для lesson comments, flashcards, redBook sightings
- profile.html: escape e.message в showMsg (XSS через server error)
- attachment_url: валидация только /uploads/* путей
- requestId: генерировать UUID сервером, не доверять клиенту
- register: скрыть token_version из ответа

Надёжность:
- register: обработка UNIQUE constraint race condition
- pet buyBg: re-check баланса внутри транзакции
- DB errors: скрыть e.message в testController/questionController/courseController
- preferences: лимит 50KB на размер JSON

UX:
- board.html: debounce 250ms на search input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:59:19 +03:00

78 lines
2.6 KiB
JavaScript

'use strict';
/* ── Error handling middleware ─────────────────────────────────────────────
requestId — attach X-Request-Id to every request (use early in middleware chain)
errorHandler — 4-arg Express error handler (use as last app.use)
──────────────────────────────────────────────────────────────────────── */
const crypto = require('crypto');
const logger = require('../utils/logger');
let _errorLogStmt = null;
function getErrorLogStmt() {
if (!_errorLogStmt) {
try {
const db = require('../db/db');
_errorLogStmt = db.prepare(
'INSERT INTO error_log (level, message, stack, route, method, user_id) VALUES (?, ?, ?, ?, ?, ?)'
);
} catch {}
}
return _errorLogStmt;
}
/**
* Attaches a unique request ID to req.requestId and sets X-Request-Id header.
* Honour an incoming X-Request-Id from trusted proxies/gateways when present.
*/
function requestId(req, res, next) {
const id = crypto.randomUUID();
req.requestId = id;
res.setHeader('X-Request-Id', id);
next();
}
/**
* Global error handler — must be registered AFTER all routes.
*
* Classifies errors:
* operational (4xx) — expected client errors → warn level, message returned as-is
* programmer (5xx) — unexpected bugs → error level, stack logged, message hidden in prod
*/
function errorHandler(err, req, res, _next) {
const status = err.status || err.statusCode || 500;
const isOperational = status >= 400 && status < 500;
const isProd = process.env.NODE_ENV === 'production';
const meta = {
requestId: req.requestId,
method: req.method,
path: req.path,
status,
userId: req.user?.id,
role: req.user?.role,
};
if (isOperational) {
logger.warn(err.message || 'Client error', meta);
} else {
logger.error(err.message || 'Unhandled error', {
...meta,
stack: !isProd ? err.stack : undefined,
});
// Persist to error_log table for admin dashboard
try {
const s = getErrorLogStmt();
if (s) s.run('error', (err.message || 'Unknown').slice(0, 1000), (err.stack || '').slice(0, 4000), req.path, req.method, req.user?.id || null);
} catch {}
}
const message = isProd && !isOperational
? 'Internal server error'
: (err.message || 'Server error');
if (res.headersSent) return;
res.status(status).json({ error: message, requestId: req.requestId });
}
module.exports = { requestId, errorHandler };