From 1492ba0009ff6477227acb551a9cde3d8474513b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:57:22 +0000 Subject: [PATCH 1/2] fix: remove clickable links from emails, add public auth page with verification code input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - email-hub.js: Remove all links from bandwidth-auth and V3 subscription emails - Emails now contain only verification code + plain text URL guidance - Guide users to manually type guanghulab.online/auth in Chrome browser - Remove authPageUrl parameter from sendBandwidthAuthEmail() - Remove bwAuthUrl from generateSubscriptionV3Email() - Simplify CLI bandwidth-auth and batch sendBandwidthAuthAllEmail() - subscription-server-v3.js: Add public bandwidth-auth-open endpoint - GET /bandwidth-auth-open: Public auth page with email + code input + consent checkbox + "我同意授权" button - POST /bandwidth-auth-open: Validate email + code, register contributor, save operation snapshot - Add saveAuthSnapshot() function for user operation audit trail - Update existing token-based auth page button text to "我同意授权" - Add snapshot saving to existing token-based auth POST handler - Remove authPageUrl from /bandwidth-send-code/{token} endpoint - zhuyuan-sovereign.conf: Add /auth short path proxy in preview domain - guanghulab.online/auth → V3 subscription service /bandwidth-auth-open - Users type short URL instead of full API path Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/1de8212f-1deb-462f-8d19-03f82a7ec9fe Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/nginx/zhuyuan-sovereign.conf | 18 + server/proxy/service/email-hub.js | 79 ++- .../proxy/service/subscription-server-v3.js | 466 +++++++++++++++++- 3 files changed, 514 insertions(+), 49 deletions(-) diff --git a/server/nginx/zhuyuan-sovereign.conf b/server/nginx/zhuyuan-sovereign.conf index e77bbbbf..cbbfea31 100644 --- a/server/nginx/zhuyuan-sovereign.conf +++ b/server/nginx/zhuyuan-sovereign.conf @@ -153,6 +153,24 @@ server { try_files $uri $uri/ /index.html; } + # ─── 带宽授权短路径 · 邮件引导用户手动输入此地址 ─── + # 邮件不含可点击链接 (QQ邮箱反拦截),引导用户在浏览器中输入 guanghulab.online/auth + # 自动代理到V3订阅服务的公开授权页面 + location = /auth { + proxy_pass http://ZY_BRAIN_HOST_PLACEHOLDER/api/proxy-v3/bandwidth-auth-open; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 10s; + proxy_read_timeout 30s; + proxy_send_timeout 30s; + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + } + # ─── 光湖语言世界V3 · 带宽授权桥接 (大脑服务器反向代理) ─── # QQ邮箱拦截大脑服务器域名链接,此桥接通过guanghulab.online域名 # 将V3授权/订阅请求透传到大脑服务器的V3订阅服务 (端口3805) diff --git a/server/proxy/service/email-hub.js b/server/proxy/service/email-hub.js index 8a0a8dc9..1a8bcd33 100644 --- a/server/proxy/service/email-hub.js +++ b/server/proxy/service/email-hub.js @@ -53,8 +53,9 @@ const EMAIL_LOG_FILE = path.join(DATA_DIR, 'email-hub-log.json'); const RELEASE_NOTES_FILE = path.join(__dirname, '../config/release-notes.json'); // ── 带宽授权页面域名 ───────────────────────── -// QQ邮箱拦截guanghulab.com域名链接,使用guanghulab.online桥接 -// guanghulab.online通过Nginx反向代理桥接到大脑服务器V3订阅服务 +// QQ邮箱拦截所有可点击链接,邮件中仅含验证码+纯文本网址引导 +// 用户在浏览器中手动输入 guanghulab.online/auth 访问公开授权页面 +// guanghulab.online/auth 通过Nginx代理到大脑服务器V3订阅服务 /bandwidth-auth-open const BW_AUTH_HOST = process.env.ZY_BW_AUTH_HOST || 'guanghulab.online'; // ── 加载版本更新说明 ──────────────────────────── @@ -601,10 +602,13 @@ function generateFeedbackAckEmail(config) { // ═══════════════════════════════════════════════ // 📧 邮件类型 6: 带宽共享验证码 (∞+1) // ═══════════════════════════════════════════════ -function generateBandwidthAuthEmail(code, authPageUrl, config) { +function generateBandwidthAuthEmail(code, config) { const releaseNotes = loadReleaseNotesObj(); const vpnIntro = getVpnSystemIntroHtml(releaseNotes); + // 授权页面网址 (纯文本,不插入可点击链接,防止QQ邮箱拦截) + const authSiteUrl = `https://${BW_AUTH_HOST}/auth`; + const content = `
🌊 带宽共享加速 · 授权验证 @@ -616,7 +620,7 @@ function generateBandwidthAuthEmail(code, authPageUrl, config) {

本邮件为光湖语言世界带宽共享加速计划的授权验证通知。
带宽共享为自愿参与机制,参与后将提升全网连接速度与稳定性。
- 如您确认授权,请复制以下验证码并在授权页面提交: + 如您确认授权,请复制以下验证码并按照下方步骤操作:

@@ -625,13 +629,21 @@ function generateBandwidthAuthEmail(code, authPageUrl, config) {

${code}

- ${authPageUrl ? ` - -
- - 🔗 前往授权页面输入验证码 - -
` : ''} + +
+

📋 授权操作步骤

+
    +
  1. 打开谷歌浏览器(Chrome)
  2. +
  3. 在地址栏中手动输入以下网址,按回车访问:
  4. +
+
+

${escapeHtml(authSiteUrl)}

+
+
    +
  1. 在页面中输入您的邮箱地址和上方验证码
  2. +
  3. 点击「我同意授权」按钮完成授权
  4. +
+
@@ -732,7 +744,7 @@ function generateThreatClearedEmail(config) { // ═══════════════════════════════════════════════ // 📧 邮件类型 9: V3订阅链接 (含系统介绍 + 带宽共享邀请) // ═══════════════════════════════════════════════ -function generateSubscriptionV3Email(subUrl, dashboardUrl, bwAuthUrl, config) { +function generateSubscriptionV3Email(subUrl, dashboardUrl, config) { const releaseNotes = loadReleaseNotesObj(); const vpnIntro = getVpnSystemIntroHtml(releaseNotes); @@ -801,7 +813,7 @@ function generateSubscriptionV3Email(subUrl, dashboardUrl, bwAuthUrl, config) { - +

🌊 带宽共享加速计划 · 自愿参与

@@ -809,14 +821,12 @@ function generateSubscriptionV3Email(subUrl, dashboardUrl, bwAuthUrl, config) { 提升所有用户的连接速度与稳定性。此功能为自愿参与机制, 不参与不影响正常服务使用。

- ${bwAuthUrl ? ` -
- - 🌊 前往参与带宽共享 - -
` : ''} +
+

请在谷歌浏览器中手动输入以下网址访问授权页面:

+

https://${escapeHtml(BW_AUTH_HOST)}/auth

+

- 点击上方按钮进入授权页面,在页面中申请验证码完成授权。
+ 在授权页面中输入您的邮箱和系统发送的验证码,点击「我同意授权」完成操作。
如暂不参与,无需任何操作。此邮件仅为系统通知,不参与不产生任何影响。

@@ -1000,13 +1010,13 @@ function listUserEmails() { /** * 发送带宽共享验证码给单个用户 (∞+1) + * 邮件仅含验证码 + 纯文本网址引导,不含可点击链接 (QQ邮箱反拦截) * @param {string} email 目标邮箱 * @param {string} code 6位验证码 - * @param {string} [authPageUrl] 授权页面URL */ -async function sendBandwidthAuthEmail(email, code, authPageUrl) { +async function sendBandwidthAuthEmail(email, code) { const config = loadConfig(); - const html = generateBandwidthAuthEmail(code, authPageUrl, config); + const html = generateBandwidthAuthEmail(code, config); try { await sendEmail(email, '🌊 光湖语言世界 · 带宽共享授权验证码', html); @@ -1063,9 +1073,8 @@ async function sendSubscriptionV3Email(email) { const host = config.server_host || 'guanghulab.com'; const subUrl = `https://${host}/api/proxy-v3/sub/${user.token}`; const dashboardUrl = `https://${host}/api/proxy-v3/dashboard/${user.token}`; - const bwAuthUrl = `https://${BW_AUTH_HOST}/api/proxy-v3/bandwidth-auth/${user.token}`; - const html = generateSubscriptionV3Email(subUrl, dashboardUrl, bwAuthUrl, config); + const html = generateSubscriptionV3Email(subUrl, dashboardUrl, config); try { await sendEmail(email, '🌐 光湖语言世界 · V3专属订阅链接', html); @@ -1111,13 +1120,8 @@ async function sendBandwidthAuthAllEmail() { // 为每位用户生成独立验证码 const authCode = bwPool.createAuthCode(user.email); - // 构建用户专属授权页面URL (使用guanghulab.online桥接域名) - let authPageUrl; - if (user.token) { - authPageUrl = `https://${BW_AUTH_HOST}/api/proxy-v3/bandwidth-auth/${user.token}`; - } - - const html = generateBandwidthAuthEmail(authCode, authPageUrl, config); + // 邮件仅含验证码 + 纯文本网址引导,不含可点击链接 (QQ邮箱反拦截) + const html = generateBandwidthAuthEmail(authCode, config); await sendEmail(user.email, '🌊 光湖语言世界 · 带宽共享授权验证码', html); sent++; console.log(` ✅ ${user.email}`); @@ -1251,15 +1255,8 @@ async function main() { const bwPool = require('./bandwidth-pool-agent'); const authCode = bwPool.createAuthCode(arg1); - // 查找用户token以构建授权页面URL (使用guanghulab.online桥接域名) - let bwAuthPageUrl; - const bwUsers = getEnabledUsers(); - const bwUser = bwUsers.find(u => u.email === arg1); - if (bwUser && bwUser.token) { - bwAuthPageUrl = `https://${BW_AUTH_HOST}/api/proxy-v3/bandwidth-auth/${bwUser.token}`; - } - - const result = await sendBandwidthAuthEmail(arg1, authCode, bwAuthPageUrl); + // 邮件仅含验证码 + 纯文本网址引导,不含可点击链接 (QQ邮箱反拦截) + const result = await sendBandwidthAuthEmail(arg1, authCode); console.log(`📧 带宽验证码: ${result.sent}成功 / ${result.failed}失败`); break; } diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 26a0ff2d..1d55ff91 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -36,6 +36,42 @@ const LIVE_NODES_FRESHNESS_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '6 // 引入用户管理器 const userManager = require('./user-manager'); +// ── 操作快照文件 ────────────────────────────── +const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json'); +const MAX_SNAPSHOTS = 500; // 最多保留500条快照记录 + +/** + * 保存用户操作快照 + * 记录用户授权操作详情,用于审计追溯 + * @param {Object} snapshot 快照数据 + */ +function saveAuthSnapshot(snapshot) { + try { + let snapshots = []; + try { + snapshots = JSON.parse(fs.readFileSync(AUTH_SNAPSHOT_FILE, 'utf8')); + } catch { /* 文件不存在或格式错误 */ } + + if (!Array.isArray(snapshots)) snapshots = []; + + snapshots.push({ + ...snapshot, + timestamp: new Date().toISOString(), + timestamp_ms: Date.now() + }); + + // 保留最近MAX_SNAPSHOTS条记录 + if (snapshots.length > MAX_SNAPSHOTS) { + snapshots = snapshots.slice(-MAX_SNAPSHOTS); + } + + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(AUTH_SNAPSHOT_FILE, JSON.stringify(snapshots, null, 2)); + } catch (err) { + console.error('[操作快照] 保存失败:', err.message); + } +} + // ── 加载密钥 ──────────────────────────────── function loadKeys() { const keys = {}; @@ -809,6 +845,412 @@ mode: direct return; } + // ── ∞+1 公开带宽共享授权页面: /bandwidth-auth-open ── + // 用户通过邮件中纯文本网址引导访问,输入邮箱+验证码完成授权 + // 不需要token,通过邮箱+验证码双重验证身份 + if (pathname === '/bandwidth-auth-open') { + + // POST: 提交邮箱+验证码 + if (req.method === 'POST') { + let body = ''; + req.on('data', chunk => { body += chunk; }); + req.on('end', () => { + try { + let code = ''; + let email = ''; + // 支持 JSON 和 form-urlencoded + if (req.headers['content-type']?.includes('application/json')) { + const json = JSON.parse(body); + code = String(json.code || '').trim(); + email = String(json.email || '').trim().toLowerCase(); + } else { + const params = new URLSearchParams(body); + code = String(params.get('code') || '').trim(); + email = String(params.get('email') || '').trim().toLowerCase(); + } + + if (!email || !email.includes('@')) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请输入有效的邮箱地址' })); + return; + } + + if (!code || code.length !== 6) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请输入6位验证码' })); + return; + } + + // 验证码校验 (邮箱+验证码交叉验证) + const bwPool = require('./bandwidth-pool-agent'); + const verifyResult = bwPool.verifyAuthCode(code, email); + + if (!verifyResult.valid) { + // 保存失败操作快照 + saveAuthSnapshot({ + email, + action: 'bandwidth-auth-consent', + result: 'failed', + error: verifyResult.error, + ip: req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || req.socket.remoteAddress || '0.0.0.0', + user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'public-open' + }); + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: verifyResult.error })); + return; + } + + // 采集用户IP (加密存储) + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + + // 注册为带宽贡献者 + const regResult = bwPool.registerContributor(email, userIP); + + // 保存用户操作快照 (授权记录) + saveAuthSnapshot({ + email, + action: 'bandwidth-auth-consent', + result: 'authorized', + ip: userIP, + user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'public-open', + contributor_id: regResult.contributor_id + }); + + // 激活用户守护Agent + try { + const guardian = require('./user-guardian-agent'); + guardian.activateGuardian(email); + } catch { /* guardian not loaded */ } + + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + success: true, + message: '授权成功!您的带宽已加入加速池,感谢支持。', + contributor_id: regResult.contributor_id + })); + } catch (err) { + res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '系统异常,请稍后重试' })); + } + }); + return; + } + + // GET: 渲染公开授权页面 (邮箱+验证码输入) + const esc2 = (s) => String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + + // 读取带宽池状态 + let poolInfo2 = { active_contributors: 0, total_contributed_gb: 0 }; + try { + poolInfo2 = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'bandwidth-pool-status.json'), 'utf8')); + } catch { /* ignore */ } + + const openAuthHtml = ` + + + + + +光湖语言世界 · 带宽共享授权 + + + +
+
+

🌊 带宽共享加速

+

光湖语言世界 · 授权验证

+
+ +
+

📊 加速池状态

+
+
${poolInfo2.active_contributors || 0}
活跃贡献者
+
${poolInfo2.total_contributed_gb || 0}
总贡献 (GB)
+
+
+ +
+

🔑 授权验证

+

请输入您的邮箱地址和邮件中收到的6位验证码。如尚未收到验证码,请联系管理员申请。

+ +
+
+ + +
+
+ + +
+ + + + +
+ +
+
+ +
+ ✅ 授权确认 · 提交验证码 + 您的闲置带宽将纳入光湖语言世界加速网络。参与带宽共享的用户越多,全网加速效能越高,您的连接速度将同步提升。 +
+ +
+ ❌ 暂不参与 · 关闭此页面 + 本功能为自愿参与机制,不影响您的正常服务使用。未参与带宽共享的用户将通过系统默认带宽通道连接,服务质量不受影响。 +
+ +
+ 🔒 安全机制说明 + 本系统为内部授权用户专用,所有参与者均为受邀成员。您的IP地址仅用于带宽加速调度,系统采用 SHA256 + 盐值 加密存储,外部无法访问。当系统检测到安全风险时,将自动切断所有共享通道并格式化全部共享记录,确保无痕清除。风险解除后,系统将自动重新分配订阅链接。您的隐私安全由铸渊守护体系全程保障。 +
+ + +
+ + + +`; + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(openAuthHtml); + return; + } + // ── ∞+1 带宽共享授权页面: /bandwidth-auth/{token} ── // 用户输入验证码授权带宽共享 · 安全加密 const bwAuthMatch = pathname.match(/^\/bandwidth-auth\/([a-f0-9]+)$/); @@ -867,6 +1309,17 @@ mode: direct // 注册为带宽贡献者 const regResult = bwPool.registerContributor(user.email, userIP); + // 保存用户操作快照 (授权记录) + saveAuthSnapshot({ + email: user.email, + action: 'bandwidth-auth-consent', + result: 'authorized', + ip: userIP, + user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'token-based', + contributor_id: regResult.contributor_id + }); + // 激活用户守护Agent try { const guardian = require('./user-guardian-agent'); @@ -1052,7 +1505,7 @@ mode: direct
- +
@@ -1160,14 +1613,14 @@ async function submitCode(e) { result.className = 'result error'; result.textContent = '❌ ' + data.message; btn.disabled = false; - btn.textContent = '🔓 提交授权'; + btn.textContent = '✅ 我同意授权'; } } catch (err) { result.className = 'result error'; result.style.display = 'block'; result.textContent = '❌ 网络异常,请稍后重试'; btn.disabled = false; - btn.textContent = '🔓 提交授权'; + btn.textContent = '✅ 我同意授权'; } } @@ -1197,11 +1650,8 @@ async function submitCode(e) { const emailHub = require('./email-hub'); const code = bwPool.createAuthCode(user.email); - // 使用guanghulab.online桥接域名 (QQ邮箱反拦截) - const bwAuthHost = process.env.ZY_BW_AUTH_HOST || 'guanghulab.online'; - const authPageUrl = `https://${bwAuthHost}/api/proxy-v3/bandwidth-auth/${token}`; - - emailHub.sendBandwidthAuthEmail(user.email, code, authPageUrl).then(() => { + // 邮件仅含验证码 + 纯文本网址引导,不含可点击链接 (QQ邮箱反拦截) + emailHub.sendBandwidthAuthEmail(user.email, code).then(() => { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify({ success: true, From e7214e21b7609f6bce8d2743641e1ac7c609cc92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:59:38 +0000 Subject: [PATCH 2/2] refactor: address code review - simplify snapshot init, deduplicate IP extraction Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/1de8212f-1deb-462f-8d19-03f82a7ec9fe Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .../proxy/service/subscription-server-v3.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 1d55ff91..1bdcc930 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -49,10 +49,9 @@ function saveAuthSnapshot(snapshot) { try { let snapshots = []; try { - snapshots = JSON.parse(fs.readFileSync(AUTH_SNAPSHOT_FILE, 'utf8')); - } catch { /* 文件不存在或格式错误 */ } - - if (!Array.isArray(snapshots)) snapshots = []; + const raw = JSON.parse(fs.readFileSync(AUTH_SNAPSHOT_FILE, 'utf8')); + if (Array.isArray(raw)) snapshots = raw; + } catch { /* 文件不存在或格式错误,使用空数组 */ } snapshots.push({ ...snapshot, @@ -881,6 +880,12 @@ mode: direct return; } + // 采集用户IP + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + // 验证码校验 (邮箱+验证码交叉验证) const bwPool = require('./bandwidth-pool-agent'); const verifyResult = bwPool.verifyAuthCode(code, email); @@ -892,7 +897,7 @@ mode: direct action: 'bandwidth-auth-consent', result: 'failed', error: verifyResult.error, - ip: req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || req.socket.remoteAddress || '0.0.0.0', + ip: userIP, user_agent: req.headers['user-agent'] || 'unknown', auth_type: 'public-open' }); @@ -901,12 +906,6 @@ mode: direct return; } - // 采集用户IP (加密存储) - const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() - || req.headers['x-real-ip'] - || req.socket.remoteAddress - || '0.0.0.0'; - // 注册为带宽贡献者 const regResult = bwPool.registerContributor(email, userIP);