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>
This commit is contained in:
Maxim Dolgolyov
2026-04-16 10:59:19 +03:00
parent 6cd0cf34d4
commit 3a4623a60a
12 changed files with 55 additions and 19 deletions
+13 -2
View File
@@ -59,6 +59,15 @@ function getOne(req, res) {
options_json: undefined,
}));
// Hide is_correct from students — only teachers/admins see correct answers
const isPrivileged = req.user.role === 'teacher' || req.user.role === 'admin';
if (!isPrivileged) {
questions.forEach(q => {
q.options.forEach(o => { delete o.is_correct; });
delete q.explanation;
});
}
res.json({ ...t, questions });
}
@@ -99,7 +108,8 @@ function addQuestions(req, res) {
try {
db.transaction(() => { question_ids.forEach(qid => ins.run(testId, qid, idx++)); })();
} catch (e) {
return res.status(500).json({ error: e.message });
console.error('[testController] addQuestions error:', e.message);
return res.status(500).json({ error: 'Ошибка добавления вопросов' });
}
res.json({ ok: true });
}
@@ -126,7 +136,8 @@ function reorderQuestions(req, res) {
ids.forEach((qid, i) => upd.run(i, testId, qid));
})();
} catch (e) {
return res.status(500).json({ error: e.message });
console.error('[testController] reorderQuestions error:', e.message);
return res.status(500).json({ error: 'Ошибка сортировки' });
}
res.json({ ok: true });
}