From 10ec62cf95b0a983e1e15822a35aa3aaaf9e870e Mon Sep 17 00:00:00 2001 From: KingKDot Date: Thu, 16 Jul 2026 23:51:20 -0500 Subject: [PATCH] add restrictions on file handler --- Overlord-Server/public/assets/settings.js | 86 +++++++ Overlord-Server/public/settings.html | 50 ++++ Overlord-Server/src/config.ts | 129 +++++++++++ .../src/file-transfer-settings.test.ts | 51 ++++ .../src/server/file-transfer-state.ts | 24 +- .../src/server/http-download-consumer.test.ts | 29 +++ .../src/server/http-download-consumer.ts | 33 ++- .../routes/file-download-routes.test.ts | 155 +++++++++++++ .../src/server/routes/file-download-routes.ts | 105 +++++++-- .../src/server/routes/misc-routes.ts | 36 ++- .../ws-file-process-proxy-keylogger.test.ts | 32 +++ .../server/ws-file-process-proxy-keylogger.ts | 16 +- .../test/e2e/file-transfer-api.spec.ts | 218 ++++++++++++++++++ 13 files changed, 919 insertions(+), 45 deletions(-) create mode 100644 Overlord-Server/src/file-transfer-settings.test.ts create mode 100644 Overlord-Server/src/server/ws-file-process-proxy-keylogger.test.ts create mode 100644 Overlord-Server/test/e2e/file-transfer-api.spec.ts diff --git a/Overlord-Server/public/assets/settings.js b/Overlord-Server/public/assets/settings.js index 4bd0d63..99811fd 100644 --- a/Overlord-Server/public/assets/settings.js +++ b/Overlord-Server/public/assets/settings.js @@ -2118,6 +2118,88 @@ function initRegistrationHandlers() { }); } +// ── File Transfer Limit Settings ────────────────────────────────────────── + +async function loadFileTransferLimits() { + if (!userHas("system:security")) return; + try { + const res = await fetch("/api/settings/file-transfers", { credentials: "include" }); + if (!res.ok) return; + const data = await res.json(); + const limits = data.fileTransfers || {}; + const mb = 1024 * 1024; + document.getElementById("ft-max-file-mb").value = Math.round((limits.maxFileBytes || 1024 * mb) / mb); + document.getElementById("ft-max-staged-mb").value = Math.round((limits.maxStagedBytes || 4096 * mb) / mb); + document.getElementById("ft-max-active-global").value = limits.maxActiveGlobal ?? 16; + document.getElementById("ft-max-active-user").value = limits.maxActivePerUser ?? 4; + document.getElementById("ft-intent-ttl-minutes").value = Math.round((limits.uploadIntentTtlMs || 30 * 60_000) / 60_000); + document.getElementById("ft-pull-ttl-minutes").value = Math.round((limits.uploadPullTtlMs || 30 * 60_000) / 60_000); + } catch (error) { + console.error("Failed to load file transfer limits", error); + } +} + +function showFileTransferLimitsMsg(text, type) { + const el = document.getElementById("file-transfer-limits-msg"); + if (!el) return; + el.textContent = text; + el.className = `text-sm rounded-lg px-3 py-2 border ${type === "error" ? "border-rose-800 bg-rose-900/20 text-rose-200" : "border-emerald-800 bg-emerald-900/20 text-emerald-200"}`; + el.classList.remove("hidden"); + setTimeout(() => el.classList.add("hidden"), 5000); +} + +async function saveFileTransferLimits(event) { + event.preventDefault(); + if (!requireUiPermission("system:security", "Security settings permission required.")) return; + const mb = 1024 * 1024; + const maxFileMb = Number(document.getElementById("ft-max-file-mb")?.value); + const maxStagedMb = Number(document.getElementById("ft-max-staged-mb")?.value); + const maxActiveGlobal = Number(document.getElementById("ft-max-active-global")?.value); + const maxActivePerUser = Number(document.getElementById("ft-max-active-user")?.value); + const intentMinutes = Number(document.getElementById("ft-intent-ttl-minutes")?.value); + const pullMinutes = Number(document.getElementById("ft-pull-ttl-minutes")?.value); + if (![maxFileMb, maxStagedMb, maxActiveGlobal, maxActivePerUser, intentMinutes, pullMinutes].every(Number.isFinite)) { + showFileTransferLimitsMsg("All limits must be valid numbers.", "error"); + return; + } + if (maxStagedMb < maxFileMb) { + showFileTransferLimitsMsg("Total staging quota must be at least the maximum file size.", "error"); + return; + } + if (maxActivePerUser > maxActiveGlobal) { + showFileTransferLimitsMsg("Per-user active transfers cannot exceed the global limit.", "error"); + return; + } + try { + const res = await fetch("/api/settings/file-transfers", { + method: "PUT", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + maxFileBytes: Math.round(maxFileMb * mb), + maxStagedBytes: Math.round(maxStagedMb * mb), + maxActiveGlobal: Math.round(maxActiveGlobal), + maxActivePerUser: Math.round(maxActivePerUser), + uploadIntentTtlMs: Math.round(intentMinutes * 60_000), + uploadPullTtlMs: Math.round(pullMinutes * 60_000), + }), + }); + const data = await res.json().catch(() => ({})); + if (!res.ok) { + showFileTransferLimitsMsg(data.error || "Failed to save transfer limits.", "error"); + return; + } + await loadFileTransferLimits(); + showFileTransferLimitsMsg("File transfer limits saved and active.", "success"); + } catch { + showFileTransferLimitsMsg("Network error.", "error"); + } +} + +function initFileTransferLimitHandlers() { + document.getElementById("file-transfer-limits-form")?.addEventListener("submit", saveFileTransferLimits); +} + // ── Build Rate Limit Settings ────────────────────────────────────────────── async function loadBuildRateLimitSettings() { @@ -2599,6 +2681,10 @@ async function init() { await loadBuildRateLimitSettings(); initBuildRateLimitHandlers(); } + if (userHas("system:security")) { + await loadFileTransferLimits(); + initFileTransferLimitHandlers(); + } if (userHas("system:thumbnails")) { await loadThumbnailSettings(); initThumbnailHandlers(); diff --git a/Overlord-Server/public/settings.html b/Overlord-Server/public/settings.html index 3183642..a010f64 100644 --- a/Overlord-Server/public/settings.html +++ b/Overlord-Server/public/settings.html @@ -120,6 +120,7 @@ Admin Access Overview Security Policy + File Transfer Limits TLS Certificates OIDC / SSO Team Chat @@ -721,6 +722,55 @@ + +