From 0152277b4dd85e13a8f7bfbcec737a7aedfb695a Mon Sep 17 00:00:00 2001
From: Abner <22141172+Silentely@users.noreply.github.com>
Date: Sat, 13 Jun 2026 19:43:50 +0800
Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=94=9F=E6=88=90=20Promise=20?=
=?UTF-8?q?=E6=8C=82=E8=B5=B7=E3=80=81XSS=20=E9=A3=8E=E9=99=A9=E5=92=8C=20?=
=?UTF-8?q?i18n=20=E7=A1=AC=E7=BC=96=E7=A0=81=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 loadQRCodeLibrary Promise 永久挂起(Issue #75 根因)
- 检测已存在脚本标签,移除失效脚本并重新加载
- 添加 5 秒超时保护,防止 Session 恢复后二维码冻结
- 统一 cleanup 函数清理监听器和定时器
- 修复 XSS 风险
- 使用 DOM API 创建错误提示,避免 innerHTML 注入
- 移除模板字符串 ${t(...)} 直接注入
- 修复 i18n 硬编码
- createQRCodeContainer 接受 labels 参数支持多语言
- generateQRCode* 函数支持 labels 透传
- UIController 传入 tl() 翻译后的可访问性标签
- Schema 验证优化
- qrcode-generate 保留手动验证,确保 405 优先级
- 添加注释说明设计意图
- 代码规范
- tests/security/qrcode-generate.test.js 添加 'use strict'
✅ 测试: 8 passed, 0 failed
---
netlify/functions/qrcode-generate.js | 5 +-
src/giffgaff/js/modules/ui-controller.js | 28 ++++++--
src/js/modules/qrcode-generator.js | 89 +++++++++++++++++++-----
src/simyo/js/modules/ui-controller.js | 28 ++++++--
tests/security/qrcode-generate.test.js | 2 +
5 files changed, 118 insertions(+), 34 deletions(-)
diff --git a/netlify/functions/qrcode-generate.js b/netlify/functions/qrcode-generate.js
index ba7c9a6..79aafbe 100644
--- a/netlify/functions/qrcode-generate.js
+++ b/netlify/functions/qrcode-generate.js
@@ -47,11 +47,12 @@ function withTimeout(promise, timeoutMs) {
}
exports.handler = withAuth(async (event, context, { body }) => {
+ // 405 优先于 Schema 验证(确保 HTTP 方法错误优先返回)
if (event.httpMethod !== 'POST') {
throw new AuthError('Method Not Allowed', 405);
}
- // withAuth 不在这里使用 validateSchema,确保非 POST 请求先返回 405。
+ // 手动调用 Schema 验证,确保 405 已经检查过
validateInput(qrcodeSchema, body);
const size = normalizeSize(body.size);
@@ -76,4 +77,4 @@ exports.handler = withAuth(async (event, context, { body }) => {
console.error('[qrcode-generate] QR code generation failed:', error.message);
throw error;
}
-});
+}, { requireAuth: true });
diff --git a/src/giffgaff/js/modules/ui-controller.js b/src/giffgaff/js/modules/ui-controller.js
index 7c67cfd..8f645b2 100644
--- a/src/giffgaff/js/modules/ui-controller.js
+++ b/src/giffgaff/js/modules/ui-controller.js
@@ -431,7 +431,13 @@ export class UIController {
const gen = ++this._qrGeneration;
try {
- const result = await generateQRCodeWithFallback(data, size);
+ const labels = {
+ alt: tl('eSIM 二维码'),
+ ariaLabel: tl('eSIM 安装二维码'),
+ tooltipAlt: tl('eSIM 二维码放大预览')
+ };
+
+ const result = await generateQRCodeWithFallback(data, size, labels);
if (gen !== this._qrGeneration) return; // 防止并发调用干扰
if (result.tooltip && typeof this.showTooltipElement === 'function' && typeof this.hideTooltipElement === 'function') {
@@ -444,12 +450,20 @@ export class UIController {
} catch (error) {
if (gen !== this._qrGeneration) return;
console.error('[Giffgaff] QR code generation failed:', error);
- this.elements.qrcode.innerHTML = `
-
-
- ${t('giffgaff.app.qr.failed')}
-
- `;
+
+ // 使用 DOM API 创建元素,避免 innerHTML XSS 风险
+ const alertDiv = document.createElement('div');
+ alertDiv.className = 'alert alert-danger';
+
+ const icon = document.createElement('i');
+ icon.className = 'fas fa-exclamation-circle me-2';
+ alertDiv.appendChild(icon);
+
+ const message = document.createTextNode(t('giffgaff.app.qr.failed'));
+ alertDiv.appendChild(message);
+
+ this.elements.qrcode.innerHTML = '';
+ this.elements.qrcode.appendChild(alertDiv);
}
}
diff --git a/src/js/modules/qrcode-generator.js b/src/js/modules/qrcode-generator.js
index b19035d..8f93b84 100644
--- a/src/js/modules/qrcode-generator.js
+++ b/src/js/modules/qrcode-generator.js
@@ -100,6 +100,7 @@ function validateQRCodeData(data) {
/**
* 懒加载浏览器端 qrcode.js 库。
* 首次调用会插入 CDN script,后续调用复用同一个 Promise,避免重复加载。
+ * 修复 Promise 挂起问题:移除失效脚本、添加超时保护(Issue #75 根因修复)。
* @returns {Promise