Files
eSIM-Tools/netlify/functions/qrcode-generate.js
Abner 51e50b62ff 🐛 fix: 修复 QR 码生成中的标签解构与错误处理问题,增强安全性
- 修复 `createQRCodeContainer` 函数中 `labels` 参数为 `undefined` 时导致解构崩溃的问题,添加默认空对象
- 简化 QR 码库加载逻辑,移除脚本已存在且加载完成时直接返回的冗余检查
- 移除 `showQRResult` 方法中冗余的 `.catch()` 处理,错误已由 `generateQRCode` 内部统一处理
- 在 Netlify 函数中创建 sanitizedError 对象,避免包含 LPA 字符串的错误信息泄露到 Sentry 日志
- 更新测试用例以匹配重构后的函数名和错误消息,并移除无效的 jest mock 配置
2026-06-13 20:15:23 +08:00

87 lines
2.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const QRCode = require('qrcode');
const { withAuth, validateInput, AuthError } = require('./_shared/middleware');
const DEFAULT_QR_SIZE = 300;
const MIN_QR_SIZE = 200;
const MAX_QR_SIZE = 600;
const QR_TIMEOUT_MS = 10000;
const qrcodeSchema = {
data: {
required: true,
type: 'string',
minLength: 1,
maxLength: 2048
},
size: {
required: false,
type: 'number'
}
};
function normalizeSize(size = DEFAULT_QR_SIZE) {
const normalizedSize = Number(size);
if (!Number.isInteger(normalizedSize)) {
throw new AuthError('size must be an integer', 400);
}
if (normalizedSize < MIN_QR_SIZE || normalizedSize > MAX_QR_SIZE) {
throw new AuthError(`size must be between ${MIN_QR_SIZE} and ${MAX_QR_SIZE}`, 400);
}
return normalizedSize;
}
function withTimeout(promise, timeoutMs) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`QR code generation timeout after ${timeoutMs}ms`));
}, timeoutMs);
});
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
}
exports.handler = withAuth(async (event, context, { body }) => {
// 405 优先于 Schema 验证(确保 HTTP 方法错误优先返回)
if (event.httpMethod !== 'POST') {
throw new AuthError('Method Not Allowed', 405);
}
// 手动调用 Schema 验证,确保 405 已经检查过
validateInput(qrcodeSchema, body);
const size = normalizeSize(body.size);
try {
const qrcode = await withTimeout(QRCode.toDataURL(body.data, {
errorCorrectionLevel: 'M',
margin: 2,
type: 'image/png',
width: size
}), QR_TIMEOUT_MS);
return {
statusCode: 200,
body: JSON.stringify({
success: true,
qrcode
})
};
} catch (error) {
// 不记录 data避免 LPA 激活信息进入日志或 Sentry。
console.error('[qrcode-generate] QR code generation failed:', error.message);
// 创建一个不含敏感数据的错误对象,防止 LPA 字符串通过 Sentry 泄露
const sanitizedError = new Error(error.message);
sanitizedError.name = error.name;
sanitizedError.stack = error.stack;
// 不复制可能包含 body.data 的属性
throw sanitizedError;
}
}, { requireAuth: true });