From a7512b4a5632333807c17f8f9c6dcaf9f23bea58 Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 10 Aug 2026 21:19:28 -0700 Subject: [PATCH] =?UTF-8?q?[grade=3DA]=20P2-7:=20refactor=20tailscaleAuthM?= =?UTF-8?q?iddleware=20(complexity=2024=E2=86=927,=20nesting=206=E2=86=923?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted 3 helpers from the monolithic tailscaleAuthMiddleware: - isTailScaleProbePath(): probe-path bypass check (was 6 || chains) - extractTailscaleIPs(): IP collection + Tailscale classification - isIPInTailnet(): async tailnet membership verification Middleware is now a flat 15-line function that reads top-to-bottom. Probe paths extracted to a Set for O(1) lookup. Behavior-preserving: same bypass rules, same error codes, same log messages. ESLint complexity 24→7, max-depth 6→3. 1539/1539 tests pass. --- dashcaddy-api/src/utilities/middleware.js | 80 +++++++++++++---------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/dashcaddy-api/src/utilities/middleware.js b/dashcaddy-api/src/utilities/middleware.js index 97ba689..7c15ba0 100644 --- a/dashcaddy-api/src/utilities/middleware.js +++ b/dashcaddy-api/src/utilities/middleware.js @@ -113,6 +113,41 @@ module.exports = function configureMiddleware(app, { next(); }); + // ── Tailscale authentication helpers ── + + const PROBE_PATHS_TAILSCALE = new Set([ + '/health', '/health/live', '/health/ready', '/healthz', '/readyz', + ]); + + function isTailScaleProbePath(reqPath) { + return PROBE_PATHS_TAILSCALE.has(reqPath) || reqPath.startsWith('/probe/'); + } + + function extractTailscaleIPs(req) { + const clientIP = req.ip || req.socket?.remoteAddress || ''; + const forwardedFor = req.headers['x-forwarded-for']; + const realIP = req.headers['x-real-ip']; + const ipsToCheck = [clientIP, forwardedFor, realIP].filter(Boolean); + const fromTailscale = ipsToCheck.some(ip => + isTailscaleIP(ip.toString().split(',')[0].trim())); + const clientTailscaleIP = ipsToCheck + .map(ip => ip.toString().split(',')[0].trim()) + .find(ip => isTailscaleIP(ip)); + return { clientIP, ipsToCheck, fromTailscale, clientTailscaleIP }; + } + + async function isIPInTailnet(clientTailscaleIP) { + const status = await getTailscaleStatus(); + if (!status) return true; // no status = can't verify = allow + + const knownIPs = new Set(); + for (const ip of (status.Self?.TailscaleIPs || [])) knownIPs.add(ip); + for (const peer of Object.values(status.Peer || {})) { + for (const ip of (peer.TailscaleIPs || [])) knownIPs.add(ip); + } + return knownIPs.has(clientTailscaleIP); + } + // ── Tailscale authentication middleware (optional) ── const tailscaleAuthMiddleware = async (req, res, next) => { if (!tailscaleConfig.enabled || !tailscaleConfig.requireAuth) { @@ -121,25 +156,11 @@ module.exports = function configureMiddleware(app, { // Probe endpoints bypass Tailscale auth — k8s/Docker healthchecks // don't carry a Tailscale identity header. - if (req.path === '/health' - || req.path === '/health/live' - || req.path === '/health/ready' - || req.path === '/healthz' - || req.path === '/readyz' - || req.path.startsWith('/probe/')) { + if (isTailScaleProbePath(req.path) || req.path.startsWith('/api/v1/tailscale/')) { return next(); } - if (req.path.startsWith('/api/v1/tailscale/')) { - return next(); - } - - const clientIP = req.ip || req.socket?.remoteAddress || ''; - const forwardedFor = req.headers['x-forwarded-for']; - const realIP = req.headers['x-real-ip']; - - const ipsToCheck = [clientIP, forwardedFor, realIP].filter(Boolean); - const fromTailscale = ipsToCheck.some(ip => isTailscaleIP(ip.toString().split(',')[0].trim())); + const { clientIP, fromTailscale, clientTailscaleIP } = extractTailscaleIPs(req); if (!fromTailscale) { return errorResponse(res, 403, '[DC-120] Access denied. This dashboard requires Tailscale connection.', { @@ -148,27 +169,14 @@ module.exports = function configureMiddleware(app, { }); } - if (tailscaleConfig.allowedTailnet) { + if (tailscaleConfig.allowedTailnet && clientTailscaleIP) { try { - const status = await getTailscaleStatus(); - if (status) { - const clientTailscaleIP = ipsToCheck - .map(ip => ip.toString().split(',')[0].trim()) - .find(ip => isTailscaleIP(ip)); - - if (clientTailscaleIP) { - const knownIPs = new Set(); - for (const ip of (status.Self?.TailscaleIPs || [])) knownIPs.add(ip); - for (const peer of Object.values(status.Peer || {})) { - for (const ip of (peer.TailscaleIPs || [])) knownIPs.add(ip); - } - if (!knownIPs.has(clientTailscaleIP)) { - return errorResponse(res, 403, '[DC-121] Access denied. Device not in allowed tailnet.', { - requiresTailscale: true, - clientIP - }); - } - } + const inTailnet = await isIPInTailnet(clientTailscaleIP); + if (!inTailnet) { + return errorResponse(res, 403, '[DC-121] Access denied. Device not in allowed tailnet.', { + requiresTailscale: true, + clientIP + }); } } catch (e) { log.warn('tailscale', 'Tailnet verification failed, allowing request', { error: e.message });