Visitor sign in kiosk: multi-site, badge printing, WWCC expiry warnings, admin accounts with 2FA

This commit is contained in:
2026-08-31 14:33:30 +10:00
parent ed77493817
commit b23ad422d0
13 changed files with 843 additions and 119 deletions
+44
View File
@@ -7,6 +7,7 @@ import config from '../config.js';
import { decryptPin, encryptPin, generatePin } from '../pins.js';
import { photoAbsolutePath, deletePhoto, purgeOldPhotos } from '../photos.js';
import * as sheets from '../sheets.js';
import * as tls from '../tls.js';
import * as users from '../users.js';
import { badgeHtml, listSites, shapeSite, uniqueSlug, escapeHtml as esc } from '../sites.js';
import {
@@ -915,10 +916,24 @@ router.get('/status', (req, res) => {
queued: sheets.queueDepth(),
lastOk: sheets.status.lastOk,
lastError: sheets.status.lastError,
logTab: config.sheets.logTab,
onSiteTab: config.sheets.onSiteTab,
lastOnSiteSync: sheets.status.lastOnSiteSync,
onSiteCount: sheets.status.onSiteCount,
onSiteError: sheets.status.onSiteError,
},
tls: tls.describe(),
});
});
router.post('/sheets/resync', async (req, res) => {
try {
res.json({ ok: true, ...(await sheets.syncOnSite()) });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
router.post('/sheets/test', async (req, res) => {
try {
res.json({ ok: true, ...(await sheets.testConnection()) });
@@ -935,6 +950,35 @@ router.post('/sheets/flush', async (req, res) => {
}
});
/* ---------------------------------------------------------------- tls */
router.get('/tls', (req, res) => {
res.json(tls.describe());
});
/** The CA certificate is public by design — it is what tablets need to trust. */
router.get('/tls/ca.crt', (req, res) => {
const ca = tls.caCertificate();
if (!ca) return res.status(404).send('No certificate authority has been generated.');
res.setHeader('Content-Type', 'application/x-x509-ca-cert');
res.setHeader('Content-Disposition', 'attachment; filename="visitor-signin-ca.crt"');
res.send(ca);
});
router.post('/tls/renew', requireOwner, (req, res) => {
try {
// A brand new CA means every kiosk device has to trust it again, so it is
// deliberately a separate, explicit choice.
const newCa = Boolean(req.body?.newCa);
tls.ensureCertificates({ force: newCa });
const reload = req.app.get('reloadTls');
const reloaded = reload ? reload() : false;
res.json({ ok: true, reloaded, newCa, info: tls.describe() });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
router.post('/photos/purge', (req, res) => {
res.json({ purged: purgeOldPhotos() });
});