fix(widgets): make widget/kiosk render frameable (X-Frame-Options)

The web player embeds widget/kiosk renders in a sandboxed (allow-scripts, no
allow-same-origin) iframe = a null origin. The global helmet X-Frame-Options:
SAMEORIGIN refuses that (null != same-origin), so every widget rendered blank in
the web player (video worked since it isn't an iframe). Drop X-Frame-Options on
just the /render endpoints - the sandbox, not X-Frame-Options, is what isolates
the widget from the dashboard (it still can't read the JWT). Dashboard keeps its
clickjacking protection. Verified: directory board now renders in a sandboxed
iframe with no refusal.
This commit is contained in:
ScreenTinker 2026-06-08 23:36:53 -05:00
parent d13ac58e74
commit 827b1c4c87
2 changed files with 9 additions and 0 deletions

View file

@ -180,6 +180,9 @@ router.get('/:id/render', (req, res) => {
</script>
</body></html>`;
// Embedded by the player in a sandboxed (null-origin) iframe; the global
// X-Frame-Options: SAMEORIGIN would refuse that and leave it blank.
res.removeHeader('X-Frame-Options');
res.setHeader('Content-Type', 'text/html');
res.send(html);
});

View file

@ -183,6 +183,12 @@ router.get('/:id/render', (req, res) => {
const widget = db.prepare('SELECT * FROM widgets WHERE id = ?').get(req.params.id);
if (!widget) return res.status(404).send('Widget not found');
const config = JSON.parse(widget.config || '{}');
// This page is DESIGNED to be embedded by the player, which frames it in a
// sandboxed (allow-scripts, no allow-same-origin) iframe = a null origin. The
// global helmet X-Frame-Options: SAMEORIGIN refuses that (null != same), so
// widgets render blank in the web player. Drop it here; the sandbox - not
// X-Frame-Options - is what isolates the widget (it can't read the dashboard JWT).
res.removeHeader('X-Frame-Options');
res.setHeader('Content-Type', 'text/html');
res.send(renderWidgetHtml(widget.widget_type, config));
});