fix: update presentation display to show filename instead of name
All checks were successful
Build & Push Docker Image / build (push) Successful in 4m21s

This commit is contained in:
2026-04-01 11:34:48 +02:00
parent c058ba3bf1
commit 9bf4228d04
2 changed files with 32 additions and 22 deletions

View File

@@ -40,10 +40,10 @@ if (!fs.existsSync(presentationsDir)) fs.mkdirSync(presentationsDir, { recursive
const PRESENTATION_TOKEN_SECRET = process.env.BBB_SECRET || crypto.randomBytes(32).toString('hex');
const PRESENTATION_TOKEN_TTL = 60 * 60 * 1000; // 1 hour
function signPresentationUrl(filename) {
function signPresentationUrl(roomUid, filename) {
const expires = Date.now() + PRESENTATION_TOKEN_TTL;
const token = crypto.createHmac('sha256', PRESENTATION_TOKEN_SECRET)
.update(`${filename}:${expires}`)
.update(`${roomUid}/${filename}:${expires}`)
.digest('hex');
return { token, expires };
}
@@ -497,8 +497,8 @@ router.post('/:uid/start', authenticateToken, async (req, res) => {
const loginURL = `${baseUrl}/join/${room.uid}`;
let presentationUrl = null;
if (room.presentation_file) {
const { token, expires } = signPresentationUrl(room.presentation_file);
presentationUrl = `${baseUrl}/api/rooms/presentations/${token}/${expires}/${room.presentation_file}`;
const { token, expires } = signPresentationUrl(room.uid, room.presentation_file);
presentationUrl = `${baseUrl}/api/rooms/presentations/${token}/${expires}/${room.uid}/${encodeURIComponent(room.presentation_file)}`;
}
const analyticsCallbackURL = room.learning_analytics
? `${baseUrl}/api/analytics/callback/${room.uid}?token=${getAnalyticsToken(room.uid)}`
@@ -702,11 +702,11 @@ router.get('/:uid/status', async (req, res) => {
}
});
// GET /api/rooms/presentations/:token/:expires/:filename - Serve presentation file (token-protected for BBB)
// Token and expires are path segments so the URL ends with the filename,
// GET /api/rooms/presentations/:token/:expires/:roomUid/:filename - Serve presentation file (token-protected for BBB)
// Token and expires are path segments so the URL ends with the original filename,
// allowing BBB to detect the file type from the extension.
router.get('/presentations/:token/:expires/:filename', (req, res) => {
const { token, expires, filename } = req.params;
router.get('/presentations/:token/:expires/:roomUid/:filename', (req, res) => {
const { token, expires, roomUid, filename } = req.params;
if (!token || !expires) {
return res.status(401).json({ error: 'Missing token' });
@@ -718,7 +718,7 @@ router.get('/presentations/:token/:expires/:filename', (req, res) => {
}
const expected = crypto.createHmac('sha256', PRESENTATION_TOKEN_SECRET)
.update(`${filename}:${expires}`)
.update(`${roomUid}/${filename}:${expires}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expected))) {
@@ -726,8 +726,9 @@ router.get('/presentations/:token/:expires/:filename', (req, res) => {
}
// S8: prevent path traversal
const filepath = path.resolve(presentationsDir, filename);
if (!filepath.startsWith(presentationsDir + path.sep)) {
const roomDir = path.resolve(presentationsDir, roomUid);
const filepath = path.resolve(roomDir, filename);
if (!filepath.startsWith(presentationsDir + path.sep) || !filepath.startsWith(roomDir + path.sep)) {
return res.status(400).json({ error: 'Invalid filename' });
}
@@ -792,22 +793,28 @@ router.post('/:uid/presentation', authenticateToken, async (req, res) => {
// Preserve original filename (sent as X-Filename header)
const rawName = req.headers['x-filename'];
const originalName = rawName
const filename = rawName
? decodeURIComponent(rawName).replace(/[^a-zA-Z0-9._\- ]/g, '_').slice(0, 200)
: `presentation.${ext}`;
const filename = `${room.uid}_${Date.now()}.${ext}`;
const filepath = path.join(presentationsDir, filename);
// Each room gets its own folder: uploads/presentations/{roomUID}/
const roomDir = path.join(presentationsDir, room.uid);
if (!fs.existsSync(roomDir)) fs.mkdirSync(roomDir, { recursive: true });
const filepath = path.join(roomDir, filename);
// S8: defense-in-depth path traversal check
if (!path.resolve(filepath).startsWith(roomDir + path.sep)) {
return res.status(400).json({ error: 'Invalid filename' });
}
// Remove old presentation file if exists
if (room.presentation_file) {
// S8: defense-in-depth path traversal check
const oldPath = path.resolve(presentationsDir, room.presentation_file);
if (oldPath.startsWith(presentationsDir + path.sep) && fs.existsSync(oldPath)) fs.unlinkSync(oldPath);
const oldPath = path.resolve(roomDir, room.presentation_file);
if (oldPath.startsWith(roomDir + path.sep) && fs.existsSync(oldPath)) fs.unlinkSync(oldPath);
}
fs.writeFileSync(filepath, buffer);
await db.run('UPDATE rooms SET presentation_file = ?, presentation_name = ?, updated_at = CURRENT_TIMESTAMP WHERE uid = ?', [filename, originalName, req.params.uid]);
await db.run('UPDATE rooms SET presentation_file = ?, updated_at = CURRENT_TIMESTAMP WHERE uid = ?', [filename, req.params.uid]);
const updated = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
res.json({ room: updated });
} catch (err) {
@@ -825,11 +832,14 @@ router.delete('/:uid/presentation', authenticateToken, async (req, res) => {
if (room.presentation_file) {
// S8: defense-in-depth path traversal check
const filepath = path.resolve(presentationsDir, room.presentation_file);
if (filepath.startsWith(presentationsDir + path.sep) && fs.existsSync(filepath)) fs.unlinkSync(filepath);
const roomDir = path.join(presentationsDir, room.uid);
const filepath = path.resolve(roomDir, room.presentation_file);
if (filepath.startsWith(roomDir + path.sep) && fs.existsSync(filepath)) fs.unlinkSync(filepath);
// Remove empty room folder
if (fs.existsSync(roomDir) && fs.readdirSync(roomDir).length === 0) fs.rmdirSync(roomDir);
}
await db.run('UPDATE rooms SET presentation_file = NULL, presentation_name = NULL, updated_at = CURRENT_TIMESTAMP WHERE uid = ?', [req.params.uid]);
await db.run('UPDATE rooms SET presentation_file = NULL, updated_at = CURRENT_TIMESTAMP WHERE uid = ?', [req.params.uid]);
const updated = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
res.json({ room: updated });
} catch (err) {