From 6e3a363899049317be3f52fcb8f327857b8e0801 Mon Sep 17 00:00:00 2001 From: oe6dxd-automation Date: Thu, 2 Apr 2026 23:22:44 +0200 Subject: [PATCH] allow secure OpenWebRX follow-up requests after ticket auth --- server/index.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/server/index.js b/server/index.js index f2a6a0f..af92d18 100644 --- a/server/index.js +++ b/server/index.js @@ -5547,6 +5547,12 @@ async function handleOpenWebRxAuthorize(req, res, url) { tickets.push(refererTicket); } if (tickets.length === 0) { + if (canAuthorizeOpenWebRxFollowupWithoutTicket(req, originalUriHeader, refererHeader)) { + await ensureOpenWebRxSdrPath(null, { force: false, minIntervalMs: 3000 }); + res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); + res.end("ok"); + return; + } res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" }); res.end("forbidden"); return; @@ -5574,7 +5580,7 @@ async function handleOpenWebRxAuthorize(req, res, url) { result.ok && runtime.station.isInUse && ownerUserId && - result.userId === ownerUserId + String(result.userId) === String(ownerUserId) ); if (!allowed) { res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" }); @@ -5582,6 +5588,9 @@ async function handleOpenWebRxAuthorize(req, res, url) { return; } markOpenWebRxSession({ id: result.userId }, result); + const fingerprint = buildOpenWebRxRequesterFingerprint(req); + runtime.openWebRxSession.lastAuthorizedIp = fingerprint.ip; + runtime.openWebRxSession.lastAuthorizedUserAgent = fingerprint.userAgent; await ensureOpenWebRxSdrPath(null, { force: false, minIntervalMs: 3000 }); res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", @@ -5594,6 +5603,46 @@ async function handleOpenWebRxAuthorize(req, res, url) { } } +function canAuthorizeOpenWebRxFollowupWithoutTicket(req, originalUriHeader, refererHeader) { + if (!runtime.station || !runtime.station.isInUse || !runtime.station.activeByUserId) { + return false; + } + if (!isOpenWebRxSessionActive()) { + return false; + } + const sessionOwner = String(runtime.openWebRxSession && runtime.openWebRxSession.activeOwnerUserId ? runtime.openWebRxSession.activeOwnerUserId : ""); + const stationOwner = String(runtime.station.activeByUserId || ""); + if (!sessionOwner || !stationOwner || sessionOwner !== stationOwner) { + return false; + } + + const fingerprint = buildOpenWebRxRequesterFingerprint(req); + const expectedIp = String(runtime.openWebRxSession && runtime.openWebRxSession.lastAuthorizedIp ? runtime.openWebRxSession.lastAuthorizedIp : ""); + const expectedUserAgent = String(runtime.openWebRxSession && runtime.openWebRxSession.lastAuthorizedUserAgent ? runtime.openWebRxSession.lastAuthorizedUserAgent : ""); + if (!expectedIp || !expectedUserAgent) { + return false; + } + if (!fingerprint.ip || !fingerprint.userAgent) { + return false; + } + if (fingerprint.ip !== expectedIp || fingerprint.userAgent !== expectedUserAgent) { + return false; + } + + const openWebRxPath = String(config.openWebRxPath || "/sdr/").trim() || "/sdr/"; + const originalUri = String(originalUriHeader || ""); + const referer = String(refererHeader || ""); + return originalUri.includes(openWebRxPath) || referer.includes(openWebRxPath); +} + +function buildOpenWebRxRequesterFingerprint(req) { + const headers = req && req.headers ? req.headers : {}; + const xForwardedFor = String(headers["x-forwarded-for"] || "").trim(); + const ip = xForwardedFor ? xForwardedFor.split(",")[0].trim() : String(req && req.socket ? req.socket.remoteAddress || "" : "").trim(); + const userAgent = String(headers["user-agent"] || "").trim(); + return { ip, userAgent }; +} + function readCookie(cookieHeader, name) { const target = String(name || "").trim(); if (!target) { @@ -6997,6 +7046,8 @@ function markOpenWebRxSession(user, sessionResult) { runtime.openWebRxSession = { activeOwnerUserId: ownerUserId || null, expiresAtMs: Number.isFinite(expiresAtMs) ? expiresAtMs : (Date.now() + 120000), + lastAuthorizedIp: runtime.openWebRxSession && runtime.openWebRxSession.lastAuthorizedIp ? runtime.openWebRxSession.lastAuthorizedIp : null, + lastAuthorizedUserAgent: runtime.openWebRxSession && runtime.openWebRxSession.lastAuthorizedUserAgent ? runtime.openWebRxSession.lastAuthorizedUserAgent : null, lastEnsureSdrAtMs: runtime.openWebRxSession && Number.isFinite(runtime.openWebRxSession.lastEnsureSdrAtMs) ? runtime.openWebRxSession.lastEnsureSdrAtMs : 0 @@ -7010,6 +7061,8 @@ function clearOpenWebRxSession() { runtime.openWebRxSession = { activeOwnerUserId: null, expiresAtMs: 0, + lastAuthorizedIp: null, + lastAuthorizedUserAgent: null, lastEnsureSdrAtMs: 0 }; runtime.openWebRxAntennaRoute = null;