Enhance security and validation across multiple routes:
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m25s

- Escape XML and HTML special characters to prevent injection attacks.
- Implement rate limiting for various endpoints to mitigate abuse.
- Add validation for email formats, password lengths, and field limits.
- Ensure proper access control for recordings and room management.
This commit is contained in:
2026-02-28 19:49:29 +01:00
parent 616442a82a
commit 7466f3513d
10 changed files with 398 additions and 47 deletions

View File

@@ -3,6 +3,7 @@ import { authenticateToken } from '../middleware/auth.js';
import { getDb } from '../config/database.js';
import {
getRecordings,
getRecordingByRecordId,
deleteRecording,
publishRecording,
} from '../config/bbb.js';
@@ -13,6 +14,25 @@ const router = Router();
router.get('/', authenticateToken, async (req, res) => {
try {
const { meetingID } = req.query;
// M11: verify user has access to the room if a meetingID is specified
if (meetingID) {
const db = getDb();
const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [meetingID]);
if (!room) {
return res.status(404).json({ error: 'Room not found' });
}
if (room.user_id !== req.user.id && req.user.role !== 'admin') {
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
if (!share) {
return res.status(403).json({ error: 'No permission to view recordings for this room' });
}
}
} else if (req.user.role !== 'admin') {
// Non-admins must specify a meetingID
return res.status(400).json({ error: 'meetingID query parameter is required' });
}
const recordings = await getRecordings(meetingID || undefined);
// Format recordings
@@ -60,6 +80,14 @@ router.get('/room/:uid', authenticateToken, async (req, res) => {
return res.status(404).json({ error: 'Room not found' });
}
// H9: verify requesting user has access to this room
if (room.user_id !== req.user.id && req.user.role !== 'admin') {
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
if (!share) {
return res.status(403).json({ error: 'No permission to view recordings for this room' });
}
}
const recordings = await getRecordings(room.uid);
const formatted = recordings.map(rec => {
const playback = rec.playback?.format;
@@ -97,6 +125,25 @@ router.get('/room/:uid', authenticateToken, async (req, res) => {
// DELETE /api/recordings/:recordID
router.delete('/:recordID', authenticateToken, async (req, res) => {
try {
// M14 fix: look up the recording from BBB to find its meetingID (room UID),
// then verify the user owns or shares that room.
if (req.user.role !== 'admin') {
const rec = await getRecordingByRecordId(req.params.recordID);
if (!rec) {
return res.status(404).json({ error: 'Recording not found' });
}
const db = getDb();
const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [rec.meetingID]);
if (!room) {
return res.status(404).json({ error: 'Room not found' });
}
if (room.user_id !== req.user.id) {
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
if (!share) {
return res.status(403).json({ error: 'No permission to delete this recording' });
}
}
}
await deleteRecording(req.params.recordID);
res.json({ message: 'Recording deleted' });
} catch (err) {
@@ -108,6 +155,25 @@ router.delete('/:recordID', authenticateToken, async (req, res) => {
// PUT /api/recordings/:recordID/publish
router.put('/:recordID/publish', authenticateToken, async (req, res) => {
try {
// M14 fix: look up the recording from BBB to find its meetingID (room UID),
// then verify the user owns or shares that room.
if (req.user.role !== 'admin') {
const rec = await getRecordingByRecordId(req.params.recordID);
if (!rec) {
return res.status(404).json({ error: 'Recording not found' });
}
const db = getDb();
const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [rec.meetingID]);
if (!room) {
return res.status(404).json({ error: 'Room not found' });
}
if (room.user_id !== req.user.id) {
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
if (!share) {
return res.status(403).json({ error: 'No permission to update this recording' });
}
}
}
const { publish } = req.body;
await publishRecording(req.params.recordID, publish);
res.json({ message: publish ? 'Recording published' : 'Recording unpublished' });