fix: ревью онлайн-урока — 8 исправлений багов, уязвимостей и улучшений
- draw_permitted: emit→emitToUser (WS доставка вместо SSE-only) - raised hands: убран in-memory Map, единый источник — таблица classroom_hands - endSession: очистка classroom_hands при завершении сессии - VALID_THEMES: исправлен список (добавлен corkboard, убраны dark/grid/dots) - XSS: crLoadOnlineStudents — inline onclick заменён на data-* + addEventListener - signal(): проверка что target_user_id является участником сессии - WS rate-limit: 120 msg/sec per connection - invalidateSession при join/leave для мгновенной видимости новых участников Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const { emit, emitToClass, getOnlineUserIds, emitToGuests } = require('../sse');
|
||||
const { emitToUser } = require('../ws-server');
|
||||
const { emitToUser, invalidateSession } = require('../ws-server');
|
||||
|
||||
/* ── chat attachment uploads dir ─────────────────────────────────────── */
|
||||
const CHAT_UPLOADS_DIR = path.join(__dirname, '../../uploads/chat');
|
||||
@@ -136,7 +136,7 @@ function endSession(req, res) {
|
||||
db.prepare(`UPDATE classroom_sessions SET status='ended', ended_at=datetime('now') WHERE id=?`)
|
||||
.run(sessionId);
|
||||
|
||||
_raisedHands.delete(sessionId);
|
||||
db.prepare('DELETE FROM classroom_hands WHERE session_id=?').run(sessionId);
|
||||
db.prepare('DELETE FROM classroom_draw_permissions WHERE session_id=?').run(sessionId);
|
||||
emitToSession(sessionId, { type: 'classroom_ended', sessionId });
|
||||
res.json({ ok: true });
|
||||
@@ -189,6 +189,8 @@ function joinSession(req, res) {
|
||||
ON CONFLICT(session_id, user_id) DO UPDATE SET joined_at=datetime('now'), left_at=NULL
|
||||
`).run(sessionId, req.user.id);
|
||||
|
||||
invalidateSession(sessionId);
|
||||
|
||||
emitToSession(sessionId, {
|
||||
type: 'classroom_user_joined',
|
||||
sessionId,
|
||||
@@ -199,7 +201,7 @@ function joinSession(req, res) {
|
||||
// If this user already has draw permission (e.g. they rejoined after a page refresh), notify them
|
||||
const drawAllowed = canDraw(sessionId, req.user.id, session) && session.teacher_id !== req.user.id;
|
||||
if (drawAllowed) {
|
||||
emit(req.user.id, { type: 'classroom_draw_permitted', sessionId });
|
||||
emitToUser(req.user.id, { type: 'classroom_draw_permitted', sessionId });
|
||||
}
|
||||
|
||||
res.json({ ok: true, canDraw: drawAllowed });
|
||||
@@ -212,6 +214,8 @@ function leaveSession(req, res) {
|
||||
WHERE session_id=? AND user_id=? AND left_at IS NULL`)
|
||||
.run(sessionId, req.user.id);
|
||||
|
||||
invalidateSession(sessionId);
|
||||
|
||||
const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (session) {
|
||||
emitToSession(sessionId, {
|
||||
@@ -360,6 +364,10 @@ function signal(req, res) {
|
||||
if (!hasAccess(session, req.user.id, req.user.role))
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
|
||||
// Verify target is also a participant of this session
|
||||
if (!hasAccess(session, target_user_id, 'student'))
|
||||
return res.status(403).json({ error: 'Цель не является участником сессии' });
|
||||
|
||||
emitToUser(target_user_id, {
|
||||
type: 'classroom_signal',
|
||||
sessionId,
|
||||
@@ -434,9 +442,6 @@ function getOnlineStudents(req, res) {
|
||||
res.json({ students });
|
||||
}
|
||||
|
||||
/* ── In-memory raised hands: sessionId -> Set<userId> ─────────────────── */
|
||||
const _raisedHands = new Map();
|
||||
|
||||
/* POST /api/classroom/:id/pages — add a page */
|
||||
function addPage(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
@@ -495,7 +500,7 @@ function updatePageTemplate(req, res) {
|
||||
/* PATCH /api/classroom/:id/board-theme — change board theme and broadcast to all */
|
||||
function updateBoardTheme(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const VALID_THEMES = new Set(['chalkboard', 'blackboard', 'whiteboard', 'dark', 'grid', 'dots']);
|
||||
const VALID_THEMES = new Set(['chalkboard', 'blackboard', 'corkboard', 'whiteboard']);
|
||||
const { theme } = req.body;
|
||||
if (!theme || !VALID_THEMES.has(theme)) return res.status(400).json({ error: 'invalid theme' });
|
||||
const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=? AND status='active'`).get(sessionId);
|
||||
@@ -515,8 +520,7 @@ function raiseHand(req, res) {
|
||||
if (!hasAccess(session, req.user.id, req.user.role))
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
|
||||
if (!_raisedHands.has(sessionId)) _raisedHands.set(sessionId, new Map());
|
||||
_raisedHands.get(sessionId).set(req.user.id, req.user.name);
|
||||
db.prepare('INSERT OR IGNORE INTO classroom_hands (session_id, user_id) VALUES (?,?)').run(sessionId, req.user.id);
|
||||
|
||||
emitToSession(sessionId, {
|
||||
type: 'classroom_hand_raised',
|
||||
@@ -531,8 +535,7 @@ function raiseHand(req, res) {
|
||||
function lowerHand(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=?`).get(sessionId);
|
||||
const map = _raisedHands.get(sessionId);
|
||||
if (map) map.delete(req.user.id);
|
||||
db.prepare('DELETE FROM classroom_hands WHERE session_id=? AND user_id=?').run(sessionId, req.user.id);
|
||||
|
||||
if (session) {
|
||||
emitToSession(sessionId, { type: 'classroom_hand_lowered', sessionId, userId: req.user.id });
|
||||
@@ -543,8 +546,12 @@ function lowerHand(req, res) {
|
||||
/* GET /api/classroom/:id/hands — get current raised hands */
|
||||
function getHands(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const map = _raisedHands.get(sessionId);
|
||||
const hands = map ? [...map.entries()].map(([userId, userName]) => ({ userId, userName })) : [];
|
||||
const hands = db.prepare(`
|
||||
SELECT h.user_id AS userId, u.name AS userName
|
||||
FROM classroom_hands h
|
||||
JOIN users u ON u.id = h.user_id
|
||||
WHERE h.session_id=?
|
||||
`).all(sessionId);
|
||||
res.json({ hands });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user