${buttons.map(btn => `
${btn.icon ? `
`).join('')}
${escapeHtml(btn.icon)}
` : ''}
${escapeHtml(btn.label) || 'Button'}
${btn.sublabel ? `${escapeHtml(btn.sublabel)}
` : ''}
const express = require('express'); const router = express.Router(); const { v4: uuidv4 } = require('uuid'); const { db } = require('../db/database'); // Escape HTML to prevent XSS function escapeHtml(str) { if (typeof str !== 'string') return str; return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } // Validate CSS color values to prevent style injection function safeColor(val, fallback) { if (!val) return fallback; if (/^#[0-9a-fA-F]{3,8}$/.test(val) || /^[a-zA-Z]+$/.test(val)) return val; return fallback; } // Validate CSS numeric values function safeNumber(val, fallback) { const n = Number(val); return isFinite(n) ? n : fallback; } // List kiosk pages router.get('/', (req, res) => { const isAdmin = req.user.role === 'superadmin'; const pages = db.prepare( `SELECT * FROM kiosk_pages ${isAdmin ? '' : 'WHERE user_id = ?'} ORDER BY created_at DESC` ).all(...(isAdmin ? [] : [req.user.id])); res.json(pages); }); // Helper: check kiosk ownership function checkKioskAccess(req, res) { const page = db.prepare('SELECT * FROM kiosk_pages WHERE id = ?').get(req.params.id); if (!page) { res.status(404).json({ error: 'Page not found' }); return null; } if (req.user && !['admin','superadmin'].includes(req.user.role) && page.user_id !== req.user.id) { res.status(403).json({ error: 'Access denied' }); return null; } return page; } // Get kiosk page router.get('/:id', (req, res) => { const page = checkKioskAccess(req, res); if (!page) return; res.json(page); }); // Render kiosk page (public - accessed by devices) router.get('/:id/render', (req, res) => { const page = db.prepare('SELECT * FROM kiosk_pages WHERE id = ?').get(req.params.id); if (!page) return res.status(404).send('Page not found'); const config = JSON.parse(page.config || '{}'); const buttons = config.buttons || []; const style = config.style || {}; const html = `
${escapeHtml(config.subtitle)}
` : ''}