From f96a9ccaa9f762a350a7fff3792ea61c22beabd0 Mon Sep 17 00:00:00 2001 From: yuyx <237899745@qq.com> Date: Wed, 18 Feb 2026 21:59:14 +0800 Subject: [PATCH] feat(security): shorten download signed URLs to 30s and remove update polling --- backend/server.js | 24 +++++++---- desktop-client/src/App.vue | 82 ++------------------------------------ 2 files changed, 21 insertions(+), 85 deletions(-) diff --git a/backend/server.js b/backend/server.js index 8cb3b43..f651047 100644 --- a/backend/server.js +++ b/backend/server.js @@ -97,6 +97,10 @@ const DOWNLOAD_RESERVATION_TTL_MS = Number(process.env.DOWNLOAD_RESERVATION_TTL_ const DOWNLOAD_LOG_RECONCILE_INTERVAL_MS = Number(process.env.DOWNLOAD_LOG_RECONCILE_INTERVAL_MS || (5 * 60 * 1000)); // 5分钟 const DOWNLOAD_LOG_MAX_FILES_PER_SWEEP = Number(process.env.DOWNLOAD_LOG_MAX_FILES_PER_SWEEP || 40); const DOWNLOAD_LOG_LIST_MAX_KEYS = Number(process.env.DOWNLOAD_LOG_LIST_MAX_KEYS || 200); +const DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS = Math.max( + 10, + Math.min(3600, Number(process.env.DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS || 30)) +); const DEFAULT_DESKTOP_VERSION = process.env.DESKTOP_LATEST_VERSION || '0.1.1'; const DEFAULT_DESKTOP_INSTALLER_URL = process.env.DESKTOP_INSTALLER_URL || ''; const DEFAULT_DESKTOP_RELEASE_NOTES = process.env.DESKTOP_RELEASE_NOTES || ''; @@ -6433,8 +6437,10 @@ app.get('/api/files/download-url', authMiddleware, async (req, res) => { }; const command = new GetObjectCommand(commandInput); - // 生成签名 URL(1小时有效) - const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 }); + // 生成签名 URL(短时有效,默认30秒) + const signedUrl = await getSignedUrl(client, command, { + expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS + }); // 直连模式:先预扣保留额度(不写入已用),实际用量由 OSS 日志异步确认入账 if (!trafficState.isUnlimited && fileSize > 0) { @@ -6454,7 +6460,7 @@ app.get('/api/files/download-url', authMiddleware, async (req, res) => { res.json({ success: true, downloadUrl: signedUrl, - expiresIn: 3600, + expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS, direct: true, quotaLimited: !trafficState.isUnlimited }); @@ -8262,8 +8268,10 @@ app.post('/api/share/:code/download-url', shareRateLimitMiddleware, async (req, ResponseContentDisposition: `attachment; filename="${encodeURIComponent(normalizedFilePath.split('/').pop())}"` }); - // 生成签名 URL(1小时有效) - const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 }); + // 生成签名 URL(短时有效,默认30秒) + const signedUrl = await getSignedUrl(client, command, { + expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS + }); if (!ownerTrafficState.isUnlimited && fileSize > 0) { const reserveResult = reserveDirectDownloadTraffic(shareOwner.id, fileSize, { @@ -8284,7 +8292,7 @@ app.post('/api/share/:code/download-url', shareRateLimitMiddleware, async (req, downloadUrl: signedUrl, direct: true, quotaLimited: !ownerTrafficState.isUnlimited, - expiresIn: 3600 + expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS }); } catch (error) { @@ -10588,7 +10596,9 @@ app.get('/d/:code', async (req, res) => { ResponseContentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(directFileName)}` }); - const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 }); + const signedUrl = await getSignedUrl(client, command, { + expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS + }); DirectLinkDB.incrementDownloadCount(code); logShare( diff --git a/desktop-client/src/App.vue b/desktop-client/src/App.vue index eb13af5..f96c52d 100644 --- a/desktop-client/src/App.vue +++ b/desktop-client/src/App.vue @@ -132,11 +132,8 @@ const updateState = reactive({ lastCheckedAt: "", message: "", }); -const AUTO_UPDATE_CHECK_INTERVAL_MS = 30 * 60 * 1000; const updateRuntime = reactive({ downloading: false, - promptedVersion: "", - mandatoryInstalledVersion: "", }); const contextMenu = reactive({ visible: false, @@ -153,7 +150,6 @@ const dropState = reactive({ }); let unlistenDragDrop: UnlistenFn | null = null; let syncTimer: ReturnType | null = null; -let updateCheckTimer: ReturnType | null = null; const toast = reactive({ visible: false, @@ -564,13 +560,6 @@ function clearSyncScheduler() { syncState.nextRunAt = ""; } -function clearUpdateScheduler() { - if (updateCheckTimer) { - clearInterval(updateCheckTimer); - updateCheckTimer = null; - } -} - function syncFingerprint(item: LocalSyncFileItem) { return `${Number(item.size || 0)}:${Number(item.modifiedMs || 0)}`; } @@ -656,18 +645,14 @@ async function checkClientUpdate(showResultToast = true): Promise { return false; } -async function installLatestUpdate(trigger: "manual" | "auto" = "manual"): Promise { +async function installLatestUpdate(): Promise { if (updateRuntime.downloading) { - if (trigger === "manual") { - showToast("更新包正在下载,请稍候", "info"); - } + showToast("更新包正在下载,请稍候", "info"); return false; } if (!updateState.downloadUrl) { - if (trigger === "manual") { - showToast("当前没有可用的更新下载地址", "info"); - } + showToast("当前没有可用的更新下载地址", "info"); return false; } @@ -725,46 +710,6 @@ async function installLatestUpdate(trigger: "manual" | "auto" = "manual"): Promi } } -async function runAutoUpdateCycle(trigger: "startup" | "login" | "timer" = "startup") { - const checked = await checkClientUpdate(false); - if (!checked) return; - - if (!updateState.available || !updateState.downloadUrl) { - return; - } - - const latestVersion = String(updateState.latestVersion || "").trim(); - if (!latestVersion) return; - - if (updateState.mandatory) { - if (updateRuntime.mandatoryInstalledVersion === latestVersion) { - return; - } - - showToast(`检测到强制更新 v${latestVersion},正在下载升级包`, "info"); - const installed = await installLatestUpdate("auto"); - if (installed) { - updateRuntime.mandatoryInstalledVersion = latestVersion; - } - return; - } - - if (updateRuntime.promptedVersion === latestVersion) { - return; - } - - updateRuntime.promptedVersion = latestVersion; - if (trigger === "timer") { - showToast(`发现新版本 v${latestVersion},可在“版本更新”中安装`, "info"); - return; - } - - const confirmed = window.confirm(`发现新版本 v${latestVersion},是否立即下载并安装?`); - if (confirmed) { - await installLatestUpdate("auto"); - } -} - async function chooseSyncDirectory() { try { const result = await openDialog({ @@ -822,16 +767,6 @@ function rebuildSyncScheduler() { }, intervalMinutes * 60 * 1000); } -function rebuildUpdateScheduler() { - clearUpdateScheduler(); - if (!authenticated.value) { - return; - } - updateCheckTimer = setInterval(() => { - void runAutoUpdateCycle("timer"); - }, AUTO_UPDATE_CHECK_INTERVAL_MS); -} - async function clearSyncSnapshot() { if (!syncState.localDir.trim()) { showToast("请先配置本地同步目录", "info"); @@ -1119,10 +1054,8 @@ async function restoreSession() { authenticated.value = true; loadSyncConfig(); rebuildSyncScheduler(); - rebuildUpdateScheduler(); await loadFiles("/"); await loadShares(true); - await runAutoUpdateCycle("startup"); } function buildItemPath(item: FileItem) { @@ -1187,12 +1120,10 @@ async function handleLogin() { showToast("登录成功,正在同步文件目录", "success"); loadSyncConfig(); rebuildSyncScheduler(); - rebuildUpdateScheduler(); await loadFiles("/"); if (!user.value) { await loadProfile(); } - await runAutoUpdateCycle("login"); return; } @@ -1204,7 +1135,6 @@ async function handleLogin() { async function handleLogout() { await invokeBridge("api_logout", { baseUrl: appConfig.baseUrl }); clearSyncScheduler(); - clearUpdateScheduler(); authenticated.value = false; user.value = null; files.value = []; @@ -1226,8 +1156,6 @@ async function handleLogout() { syncState.lastSummary = ""; syncState.nextRunAt = ""; updateRuntime.downloading = false; - updateRuntime.promptedVersion = ""; - updateRuntime.mandatoryInstalledVersion = ""; showToast("已退出客户端", "info"); } @@ -1702,7 +1630,6 @@ onBeforeUnmount(() => { window.removeEventListener("click", handleGlobalClick); window.removeEventListener("keydown", handleGlobalKey); clearSyncScheduler(); - clearUpdateScheduler(); if (unlistenDragDrop) { unlistenDragDrop(); unlistenDragDrop = null; @@ -1848,7 +1775,7 @@ onBeforeUnmount(() => { - @@ -2053,7 +1980,6 @@ onBeforeUnmount(() => {
- 自动检查:已开启(每 30 分钟) 上次检查:{{ updateState.lastCheckedAt ? formatDate(updateState.lastCheckedAt) : "-" }} 提示:{{ updateState.message || "可手动点击“检查更新”获取最新信息" }}