mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-15 02:33:15 -06:00
Five low-risk, high-value fixes surfaced by the security review: #3 Branding lockdown — `custom_domain`/`custom_css` (which feed the PUBLIC, pre-auth branding resolver and the login-page <style>) are now settable only by platform admins; a workspace_admin can no longer hijack the platform login page by claiming its domain. The public /api/branding (+ /domain) now return only presentational fields via publicBranding() (no id/user_id/workspace_id/ custom_domain/timestamps leak). #6 Strip device_token — the device WS auth secret (validated with timingSafeEqual) was returned in device list/get/update + pairing responses (SELECT d.* / *). New lib/device-sanitize.js strips it everywhere; prevents device impersonation by any workspace user. #7 must_change_password enforced server-side — was a frontend-only redirect, so a provisioned temp password worked indefinitely via the API. requireAuth now 403s every route except GET/PUT /api/auth/me (the password change, which clears the flag) and logout while the flag is set. #8 XSS — escape user data interpolated into innerHTML in teams.js, kiosk.js, layout-editor.js (team/page/layout/zone names, member name/email, kiosk config fields). scriptSrcAttr 'unsafe-inline' made these exploitable via injected event handlers, not just markup. #9 Thumbnail IDOR — /api/content/:id/thumbnail had no auth/scope gate (any UUID served any tenant's thumbnail). Now mirrors the /file route's playlist/widget workspace-scoped reference check. Tests: new test/security-fixes.test.js (device strip, publicBranding field allowlist, must_change_password gate). Full suite 41/41. Verified live against a prod-data copy: device_token absent from /api/devices, /api/branding trimmed. Not addressed here (tracked for follow-up): Android OTA signature verification (Critical), public widget-render XSS, token revocation/logout, pairing-code strength, validateRemoteUrl hardening, import quota. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24 lines
898 B
JavaScript
24 lines
898 B
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { db } = require('../db/database');
|
|
|
|
// Provision (pair) a device by entering its pairing code
|
|
router.post('/', (req, res) => {
|
|
const { pairing_code } = req.body;
|
|
if (!pairing_code) return res.status(400).json({ error: 'pairing_code required' });
|
|
|
|
const device = db.prepare('SELECT * FROM devices WHERE pairing_code = ?').get(pairing_code);
|
|
if (!device) return res.status(404).json({ error: 'No device found with that pairing code' });
|
|
|
|
// Clear pairing code and set online
|
|
db.prepare(`
|
|
UPDATE devices SET pairing_code = NULL, status = 'online', updated_at = strftime('%s','now')
|
|
WHERE id = ?
|
|
`).run(device.id);
|
|
|
|
const updated = db.prepare('SELECT * FROM devices WHERE id = ?').get(device.id);
|
|
res.json(require('../lib/device-sanitize').stripDeviceSecrets(updated));
|
|
});
|
|
|
|
module.exports = router;
|