From 3ea150bad1c246abfa211878fab36d83518d62d0 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 9 Jun 2026 09:10:16 +1000 Subject: [PATCH] fix: extract softAuth to shared module and apply to icon_sets router Move the softAuth middleware from devices.js into a new shared lib/api/soft_auth.js module. Apply router.use(softAuth) and router.use(errorMiddleware) to icon_sets.js so that POST/DELETE owner-only routes return 401 (not 500) when no auth is present. Co-Authored-By: Claude Opus 4.8 --- lib/api/routes/devices.js | 25 +------------------------ lib/api/routes/icon_sets.js | 6 +++++- lib/api/soft_auth.js | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 lib/api/soft_auth.js diff --git a/lib/api/routes/devices.js b/lib/api/routes/devices.js index a5b1989..95ff854 100644 --- a/lib/api/routes/devices.js +++ b/lib/api/routes/devices.js @@ -5,32 +5,9 @@ import { requireOwner } from '../cap.js'; import { validate } from '../validate.js'; import * as devices from '../../db/repos/lan_devices.js'; import { isRandomizedMac } from '../../infra/scan.js'; -import * as agents from '../../db/repos/agents.js'; -import { timingSafeStrEqual } from '../../auth/safe_compare.js'; -import { accessOwnerEmail } from '../../auth/cf_access.js'; +import { softAuth } from '../soft_auth.js'; export const router = Router(); - -// Soft auth: identifies the actor if auth is present but never blocks the request. -// Owner-only sub-routes enforce 401/403 via requireOwner. -async function softAuth(req, _res, next) { - try { - const cfEmail = await accessOwnerEmail(req); - if (cfEmail) { req.actor = { kind: 'user', id: null }; return next(); } - const auth = req.headers.authorization || ''; - const [scheme, token] = auth.split(' '); - if (scheme === 'Bearer' && token) { - if (process.env.OWNER_TOKEN && timingSafeStrEqual(token, process.env.OWNER_TOKEN)) { - req.actor = { kind: 'user', id: null }; return next(); - } - try { - const agent = await agents.verifyToken(token); - if (agent) req.actor = { kind: 'agent', id: agent.id, capabilities: agent.capabilities || {}, scopes: agent.scopes || {} }; - } catch { /* ignore */ } - } - } catch { /* ignore */ } - next(); -} const GROUP_ORDER = ['Smart Home', 'Entertainment', 'Personal', 'Network', 'Flagged']; router.use(softAuth); diff --git a/lib/api/routes/icon_sets.js b/lib/api/routes/icon_sets.js index a7c7364..88c0935 100644 --- a/lib/api/routes/icon_sets.js +++ b/lib/api/routes/icon_sets.js @@ -2,11 +2,13 @@ import { Router } from 'express'; import multer from 'multer'; import { requireOwner } from '../cap.js'; -import { asyncWrap } from '../errors.js'; +import { asyncWrap, errorMiddleware } from '../errors.js'; +import { softAuth } from '../soft_auth.js'; import * as sets from '../../icons/sets.js'; import { processFile, unpackZip, fetchUrl, isZip } from '../../icons/ingest.js'; export const router = Router(); +router.use(softAuth); const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 6 * 1024 * 1024, files: 50 } }); // GET /api/icon-sets — list sets + their icons (open; can't send bearer). @@ -49,3 +51,5 @@ router.delete('/:set', requireOwner, asyncWrap(async (req, res) => { catch (e) { return res.status(e.message === 'reserved_set' ? 409 : 400).json({ error: { code: e.message } }); } res.json({ ok: true }); })); + +router.use(errorMiddleware); diff --git a/lib/api/soft_auth.js b/lib/api/soft_auth.js new file mode 100644 index 0000000..f4139ce --- /dev/null +++ b/lib/api/soft_auth.js @@ -0,0 +1,25 @@ +// lib/api/soft_auth.js — shared middleware +// Soft auth: identifies the actor if auth is present but never blocks the request. +// Owner-only sub-routes enforce 401/403 via requireOwner. +import * as agents from '../db/repos/agents.js'; +import { timingSafeStrEqual } from '../auth/safe_compare.js'; +import { accessOwnerEmail } from '../auth/cf_access.js'; + +export async function softAuth(req, _res, next) { + try { + const cfEmail = await accessOwnerEmail(req); + if (cfEmail) { req.actor = { kind: 'user', id: null }; return next(); } + const auth = req.headers.authorization || ''; + const [scheme, token] = auth.split(' '); + if (scheme === 'Bearer' && token) { + if (process.env.OWNER_TOKEN && timingSafeStrEqual(token, process.env.OWNER_TOKEN)) { + req.actor = { kind: 'user', id: null }; return next(); + } + try { + const agent = await agents.verifyToken(token); + if (agent) req.actor = { kind: 'agent', id: agent.id, capabilities: agent.capabilities || {}, scopes: agent.scopes || {} }; + } catch { /* ignore */ } + } + } catch { /* ignore */ } + next(); +}