@seasonkoh/webaz 0.1.10 → 0.1.11

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.
@@ -4006,7 +4006,7 @@ export async function startMCPServer() {
4006
4006
  type: 'text',
4007
4007
  text: `用户需求:${args.user_intent || '(未提供)'}\n\n` +
4008
4008
  `请使用 webaz_search 工具(可粘贴外链精准匹配,详见工具描述)找到最匹配的商品,` +
4009
- `然后用 webaz_verify_price 锁价(返回 session_token,5 分钟有效),` +
4009
+ `然后用 webaz_verify_price 锁价(返回 session_token,10 分钟有效),` +
4010
4010
  `最后用 webaz_place_order(session_token=...) 下单。` +
4011
4011
  `下单时记得跟用户确认收货地址 + 是否需要使用推荐人 promoter_api_key(可选)。\n\n` +
4012
4012
  `重要:不要跳过 verify_price 这一步 — 它是防价格篡改的关键。`,
@@ -10687,7 +10687,18 @@ window.doRegister = async () => {
10687
10687
  // #1049 Turnstile token(若启用)
10688
10688
  if (window._turnstileToken) body.turnstile_token = window._turnstileToken
10689
10689
  const res = await POST('/register', body)
10690
- if (res.error) return showMsg('error', res.error)
10690
+ if (res.error) {
10691
+ // P0-3: token 单次消耗,任何注册失败(name 重 / invite 缺 / captcha 假)后都要 reset widget 拿新 challenge,
10692
+ // 否则用户 retry 时旧 token 已失效 → 后端 timeout-or-duplicate → 死循环
10693
+ try {
10694
+ window._turnstileToken = ''
10695
+ if (window.turnstile) {
10696
+ const slot = document.getElementById('reg-turnstile-slot')
10697
+ if (slot) window.turnstile.reset(slot)
10698
+ }
10699
+ } catch {}
10700
+ return showMsg('error', res.error)
10701
+ }
10691
10702
  // 注册成功后清掉 hint(已绑定)
10692
10703
  localStorage.removeItem('webaz_share_hint')
10693
10704
  localStorage.removeItem('webaz_ref')
@@ -11631,7 +11642,6 @@ async function renderDiscover(app) {
11631
11642
  position: idx + 1,
11632
11643
  item: {
11633
11644
  '@type': 'Product',
11634
- inLanguage: lang,
11635
11645
  name: nameField,
11636
11646
  url: location.origin + '/#order-product/' + pp.id,
11637
11647
  ...(img ? { image: img } : {}),
@@ -15723,7 +15733,6 @@ async function renderBuyPage(app, productId) {
15723
15733
  setJsonLd({
15724
15734
  '@context': 'https://schema.org',
15725
15735
  '@type': 'Product',
15726
- inLanguage: curLang,
15727
15736
  name: nameField,
15728
15737
  description: descField,
15729
15738
  sku: p.id,
@@ -1,5 +1,5 @@
1
1
  // Service Worker — 网络优先,离线降级缓存;API 请求不缓存
2
- const CACHE = 'webaz-v478'
2
+ const CACHE = 'webaz-v480'
3
3
 
4
4
  self.addEventListener('install', e => {
5
5
  self.skipWaiting()
@@ -113,9 +113,11 @@ export function buildPaymentMandate(input) {
113
113
  forSign.proof.signature = '';
114
114
  return { canonical: canonical(forSign), mandate };
115
115
  }
116
- /** 把 build*Mandate 输出的 canonical 经 signFn 签名后,塞回 mandate.proof.signature */
116
+ /** 把 build*Mandate 输出的 canonical 经 signFn 签名后,返回带 signature 的深 clone(不 mutate 入参) */
117
117
  export async function signMandate(out, signFn) {
118
118
  const sig = await signFn(out.canonical);
119
- out.mandate.proof.signature = sig;
120
- return out.mandate;
119
+ // P2-3:深 clone 后再塞 signature;入参 out.mandate 保持不变,防调用方持引用被旁路修改
120
+ const signed = JSON.parse(JSON.stringify(out.mandate));
121
+ signed.proof.signature = sig;
122
+ return signed;
121
123
  }
@@ -15,6 +15,10 @@ export function registerAuthRegisterRoutes(app, deps) {
15
15
  if (!turnstile_token || typeof turnstile_token !== 'string') {
16
16
  return void errorRes(res, 400, 'CAPTCHA_REQUIRED', '请完成人机校验');
17
17
  }
18
+ // P1-3:Cloudflare Turnstile token 通常 <2KB,卡 4KB 防大体积注入 siteverify 浪费带宽
19
+ if (turnstile_token.length > 4096) {
20
+ return void errorRes(res, 400, 'CAPTCHA_INVALID', '人机校验未通过,请刷新后重试');
21
+ }
18
22
  try {
19
23
  const remoteIp = req.ip || req.socket?.remoteAddress || '';
20
24
  const verifyRes = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
@@ -4995,8 +4995,9 @@ const CROSS_USER_READ_DAILY_CAP = {
4995
4995
  };
4996
4996
  const crossUserReadBuckets = new Map();
4997
4997
  function extractCrossUserTarget(path, currentUserId) {
4998
- // /api/users/:id/anything → :id;同 user 不算跨;sys_* 协议账号不算
4999
- const m = path.match(/^\/api\/users\/([^/]+)\//);
4998
+ // /api/users/:id/api/users/:id/anything → :id;同 user 不算跨;sys_* 协议账号不算
4999
+ // P1-4 修:允许 trailing path 缺失(/api/users/:user_id 是合法 endpoint,返回用户档案)
5000
+ const m = path.match(/^\/api\/users\/([^/?#]+)(?:[/?#]|$)/);
5000
5001
  if (!m)
5001
5002
  return null;
5002
5003
  const target = m[1];
@@ -5193,7 +5194,9 @@ app.use((req, res, next) => {
5193
5194
  });
5194
5195
  }
5195
5196
  // #1043(补 A) 跨用户读日 cap — 真人也罩;只对路径里显式带 other user id 的 GET 计数
5196
- if (req.method === 'GET') {
5197
+ // P0-2 优化:先 regex 快判 path 命中(零开销),命中才查 owner.id + level — 避免每 GET 跑 SQLite
5198
+ // P1-4 修:正则允许 /api/users/:id 无 trailing slash(GET /api/users/:user_id 是返回用户档案的合法端点)
5199
+ if (req.method === 'GET' && /^\/api\/users\/[^/?#]+/.test(req.path)) {
5197
5200
  const owner = db.prepare(`SELECT id FROM users WHERE api_key = ?`).get(apiKey);
5198
5201
  if (owner) {
5199
5202
  const target = extractCrossUserTarget(req.path, owner.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seasonkoh/webaz",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Agent-native decentralized commerce protocol. Humans and AI agents trade on the same protocol via MCP tools.",
5
5
  "main": "dist/mcp.js",
6
6
  "bin": {