trustbase-seller-backend 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit-page-scripts.js +59 -0
- package/package.json +1 -1
- package/routes/sp-applyment.js +4 -3
- package/test-sp-applyment.js +23 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// 全页面 JS 体检: 抓取所有 HTML 页面的内联脚本并逐一编译 (抓 0.4.1 那类模板转义回归)
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const Database = require("better-sqlite3");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
function compileScripts(name, html) {
|
|
8
|
+
const blocks = [...html.matchAll(/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi)];
|
|
9
|
+
if (!blocks.length) { console.log(`-- ${name}: 无内联脚本`); return 0; }
|
|
10
|
+
let bad = 0;
|
|
11
|
+
blocks.forEach((b, i) => {
|
|
12
|
+
try { new Function(b[1]); console.log(`OK ${name} script#${i + 1} (${b[1].length} chars)`); }
|
|
13
|
+
catch (e) {
|
|
14
|
+
bad++;
|
|
15
|
+
console.log(`FAIL ${name} script#${i + 1}: ${e.message}`);
|
|
16
|
+
const lines = b[1].split("\n");
|
|
17
|
+
// 用 node --check 精确定位
|
|
18
|
+
fs.writeFileSync(path.join(__dirname, `audit-${name}-${i + 1}.js`), b[1]);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return bad;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
(async () => {
|
|
25
|
+
let fails = 0;
|
|
26
|
+
const db = new Database(":memory:");
|
|
27
|
+
process.env.SP_ADMIN_API_KEY = process.env.SP_ADMIN_API_KEY || "audit-key";
|
|
28
|
+
|
|
29
|
+
// 1) onboard.html (磁盘直读)
|
|
30
|
+
const onboardHtml = fs.readFileSync(path.join(__dirname, "routes", "onboard.html"), "utf8");
|
|
31
|
+
fails += compileScripts("onboard.html", onboardHtml);
|
|
32
|
+
|
|
33
|
+
// 2) 起 mock server 抓动态页
|
|
34
|
+
const app = express();
|
|
35
|
+
app.use(express.json({ limit: "20mb" }));
|
|
36
|
+
app.use("/api/sp", require("./routes/sp-applyment").createSpApplymentRouter({ db }));
|
|
37
|
+
app.use("/m2m", require("./routes/m2m-viewer").createM2MViewerRouter({ db }));
|
|
38
|
+
const srv = await new Promise((r) => { const s = app.listen(0, () => r(s)); });
|
|
39
|
+
const base = `http://127.0.0.1:${srv.address().port}`;
|
|
40
|
+
const H = { Accept: "text/html" };
|
|
41
|
+
|
|
42
|
+
for (const [name, url] of [
|
|
43
|
+
["sp-config", base + "/api/sp/config"],
|
|
44
|
+
["sp-apply", base + "/api/sp/apply"],
|
|
45
|
+
["m2m-viewer", base + "/m2m"],
|
|
46
|
+
]) {
|
|
47
|
+
try {
|
|
48
|
+
const r = await fetch(url, { headers: H });
|
|
49
|
+
const html = await r.text();
|
|
50
|
+
console.log(`\n== ${name}: HTTP ${r.status} (${html.length} chars)`);
|
|
51
|
+
if (r.status === 200 && html.includes("<")) fails += compileScripts(name, html);
|
|
52
|
+
} catch (e) { console.log(`\n== ${name}: FETCH FAIL ${e.message}`); }
|
|
53
|
+
}
|
|
54
|
+
srv.close();
|
|
55
|
+
|
|
56
|
+
// 3) run-unified.js 里可能还有别的内联 HTML — 搜 res.send 模板
|
|
57
|
+
console.log(`\n== 总计: ${fails} 个脚本块编译失败`);
|
|
58
|
+
process.exit(fails ? 1 : 0);
|
|
59
|
+
})();
|
package/package.json
CHANGED
package/routes/sp-applyment.js
CHANGED
|
@@ -595,8 +595,8 @@ function renderSlots(){
|
|
|
595
595
|
return '<div class="slot"><div class="t">'+kk[1]+'</div>'
|
|
596
596
|
+(mid?'<div class="mid">✓ '+esc(maskMid(mid))+'</div>':'')
|
|
597
597
|
+'<img id="img-'+kk[0]+'" style="display:none">'
|
|
598
|
-
+'<label class="upbtn">'+(mid?'替换':'点击上传')+'<input type="file" accept="image/*" style="display:none" onchange="upFormAsset(event
|
|
599
|
-
+(mid?'<button class="upbtn del" onclick="formAssets[
|
|
598
|
+
+'<label class="upbtn">'+(mid?'替换':'点击上传')+'<input type="file" accept="image/*" style="display:none" onchange="upFormAsset(event,\\x27'+kk[0]+'\\x27)"></label>'
|
|
599
|
+
+(mid?'<button class="upbtn del" onclick="formAssets[\\x27'+kk[0]+'\\x27]=\\x27\\x27;renderSlots()">删除</button>':'')
|
|
600
600
|
+'</div>';
|
|
601
601
|
}).join('');
|
|
602
602
|
}
|
|
@@ -1064,7 +1064,8 @@ adminKey(); // 打开即要 key (后续 API 都要带)
|
|
|
1064
1064
|
function createSpApplymentRouter(deps) {
|
|
1065
1065
|
const router = express.Router();
|
|
1066
1066
|
const { db } = deps;
|
|
1067
|
-
ensureApplymentTables(db);
|
|
1067
|
+
ensureApplymentTables(db);
|
|
1068
|
+
ensureSpApps(db); // 0.4.2: 路由创建即建表 — 修 POST /apps 在 GET 之前调用时 "no such table: sp_apps" (node-c 实测)
|
|
1068
1069
|
|
|
1069
1070
|
/** admin 鉴权: env 未配置 → 全部拒绝 (401); 配置了 → header 精确比对 (同 sp-commission 口径) */
|
|
1070
1071
|
function checkAdmin(req, res) {
|
package/test-sp-applyment.js
CHANGED
|
@@ -435,7 +435,13 @@ test("SP 配置页后端: config 读写 (表优先 env) + 配齐度 + info 立
|
|
|
435
435
|
// 页面: Accept text/html 无 key 也 200 (admin key 页面内输入)
|
|
436
436
|
r = await fetch(base + "/sp/config", { headers: { Accept: "text/html" } });
|
|
437
437
|
assert.strictEqual(r.status, 200);
|
|
438
|
-
|
|
438
|
+
const pageHtml = await r.text();
|
|
439
|
+
assert.ok(pageHtml.includes("服务商配置"));
|
|
440
|
+
// 回归 (0.4.2): 页面内联脚本必须可编译 — 模板里 \x27 会被服务端提前求值成双引号导致整页 JS 死掉 (保存按钮无反应),
|
|
441
|
+
// 必须用 \\x27 让浏览器拿到字面 \x27 再自行求值
|
|
442
|
+
const scriptMatch = pageHtml.match(/<script>([\s\S]*?)<\/script>/);
|
|
443
|
+
assert.ok(scriptMatch, "配置页应有内联脚本");
|
|
444
|
+
new Function(scriptMatch[1]); // 语法错误会抛
|
|
439
445
|
// 读默认 (env 兜底)
|
|
440
446
|
let j = await (await fetch(base + "/api/sp/config", { headers: { ...H, Accept: "application/json" } })).json();
|
|
441
447
|
assert.strictEqual(j.ok, true);
|
|
@@ -541,6 +547,22 @@ test("多应用: CRUD + 默认唯一 + 旧配置自动迁移 + /info apps 列表
|
|
|
541
547
|
} finally { srv.close(); }
|
|
542
548
|
});
|
|
543
549
|
|
|
550
|
+
test("回归 0.4.2: 全新库直接 POST /apps (不先 GET) 不能报 no such table — node-c 实测坑", async () => {
|
|
551
|
+
const { srv, base } = await startServer();
|
|
552
|
+
try {
|
|
553
|
+
// startServer 每次新起 :memory: 库; 直接写, 不允许依赖 GET 懒建表
|
|
554
|
+
const r = await fetch(base + "/api/sp/apps", {
|
|
555
|
+
method: "POST", headers: H,
|
|
556
|
+
body: JSON.stringify({ name: "首个应用", appid: "wx_fresh_001", type: "app" }),
|
|
557
|
+
});
|
|
558
|
+
const j = await r.json();
|
|
559
|
+
assert.strictEqual(r.status, 200, JSON.stringify(j));
|
|
560
|
+
assert.strictEqual(j.ok, true);
|
|
561
|
+
assert.strictEqual(j.apps.length, 1);
|
|
562
|
+
assert.strictEqual(j.apps[0].is_default, true, "首个应用自动默认");
|
|
563
|
+
} finally { srv.close(); }
|
|
564
|
+
});
|
|
565
|
+
|
|
544
566
|
test("配置页操作反馈: toast 组件 + 成功/失败文案 + 保存中禁用 (页面断言)", async () => {
|
|
545
567
|
const { srv, base } = await startServer();
|
|
546
568
|
try {
|