@wenbin_wb/dsh-bridge 2.4.0 → 2.5.1

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/lib/bridge-rpc.js CHANGED
@@ -11,13 +11,14 @@ function ok(value) {
11
11
  }
12
12
 
13
13
  function fail(code, message, details = {}) {
14
- if (code === 'cancelled') {
15
- return { ok: false, error: { code: 'cancelled', message, details: {} } };
16
- }
14
+ const allowedCodes = new Set([
15
+ 'bad-request', 'cancelled', 'internal', 'settings-rejected', 'command-error'
16
+ ]);
17
+ const safeCode = allowedCodes.has(code) ? code : 'bad-request';
17
18
  return {
18
19
  ok: false,
19
20
  error: {
20
- code: 'bad-request',
21
+ code: safeCode,
21
22
  message,
22
23
  details: { issues: [{ message }], ...details },
23
24
  },
@@ -54,7 +55,22 @@ async function wechatStatusValue(wechatService, logger) {
54
55
  return { ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } };
55
56
  }
56
57
 
57
- export function installBridgeRpc(ctx, { service, wechat, platformManager, logger, saveCustomTunnelConfig }) {
58
+ function checkAdminAuth(authManager, payload) {
59
+ if (!authManager) return null;
60
+ if (authManager.adminPolicy === 'open') return null;
61
+ // 若系统尚未设置任何管理密码或访客密码,允许免密管理
62
+ const hasAnyPassword = authManager.hasAdminPassword || authManager.hasPassword;
63
+ if (!hasAnyPassword) {
64
+ return null;
65
+ }
66
+ // 已设置密码时,必须提供经服务端校验有效的 adminToken(绝不依赖客户端自称的 isLocalhost)
67
+ if (payload?.adminToken && authManager.validateAdminSession(payload.adminToken)) {
68
+ return null;
69
+ }
70
+ return fail('bad-request', '操作已被拦截:需要管理员权限,请先在控制台输入管理密码解锁');
71
+ }
72
+
73
+ export function installBridgeRpc(ctx, { service, authManager, wechat, platformManager, logger, saveCustomTunnelConfig }) {
58
74
  if (!ctx?.connection?.rpc?.handle) {
59
75
  logger.warn('dsh-bridge: Connection RPC unavailable — UI will not work');
60
76
  return () => {};
@@ -67,11 +83,61 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
67
83
 
68
84
  try {
69
85
  if (endpoint === BRIDGE_ENDPOINTS.getStatus) {
70
- const status = await service.getStatus();
86
+ const isAdmin = checkAdminAuth(authManager, payload) === null;
87
+ const status = await service.getStatus({ adminAuthValid: isAdmin });
71
88
  return ok(status);
72
89
  }
73
90
 
91
+ // ---- 访问安全认证 ----
92
+
93
+ if (endpoint === BRIDGE_ENDPOINTS.authGetStatus) {
94
+ if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
95
+ const isAdmin = checkAdminAuth(authManager, payload) === null;
96
+ return ok(authManager.getStatus({ masked: !isAdmin }));
97
+ }
98
+
99
+ if (endpoint === BRIDGE_ENDPOINTS.authUpdateConfig) {
100
+ if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
101
+ const adminErr = checkAdminAuth(authManager, payload);
102
+ if (adminErr) return adminErr;
103
+
104
+ const { enabled, mode, scope, adminPolicy, password, adminPassword } = payload;
105
+ if (enabled != null) await authManager.setEnabled(enabled);
106
+ if (mode != null) await authManager.setMode(mode);
107
+ if (scope != null) await authManager.setScope(scope);
108
+ if (adminPolicy != null) await authManager.setAdminPolicy(adminPolicy);
109
+ if (password !== undefined) await authManager.setPassword(password);
110
+ if (adminPassword !== undefined) await authManager.setAdminPassword(adminPassword);
111
+ return ok(authManager.getStatus({ masked: false }));
112
+ }
113
+
114
+ if (endpoint === BRIDGE_ENDPOINTS.authRegenerateToken) {
115
+ if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
116
+ const adminErr = checkAdminAuth(authManager, payload);
117
+ if (adminErr) return adminErr;
118
+
119
+ await authManager.regenerateSecretToken();
120
+ return ok(authManager.getStatus({ masked: false }));
121
+ }
122
+
123
+ if (endpoint === BRIDGE_ENDPOINTS.authAdminUnlock) {
124
+ if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
125
+ const { password } = payload;
126
+ const res = authManager.unlockAdmin(password);
127
+ if (res.ok) return ok({ adminToken: res.adminToken });
128
+ return fail('bad-request', res.error || '管理员密码错误');
129
+ }
130
+
131
+ if (endpoint === BRIDGE_ENDPOINTS.authAdminLock) {
132
+ if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
133
+ if (payload?.adminToken) authManager.revokeAdminSession(payload.adminToken);
134
+ return ok({ locked: true });
135
+ }
136
+
74
137
  if (endpoint === BRIDGE_ENDPOINTS.saveCustomTunnelConfig) {
138
+ const adminErr = checkAdminAuth(authManager, payload);
139
+ if (adminErr) return adminErr;
140
+
75
141
  const { serverUrl = '', accessToken = '' } = payload;
76
142
  await saveCustomTunnelConfig(serverUrl.trim(), accessToken.trim());
77
143
  const status = await service.getStatus();
@@ -79,6 +145,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
79
145
  }
80
146
 
81
147
  if (endpoint === BRIDGE_ENDPOINTS.startCustomTunnel) {
148
+ const adminErr = checkAdminAuth(authManager, payload);
149
+ if (adminErr) return adminErr;
150
+
82
151
  try {
83
152
  await service.startCustomTunnel();
84
153
  const status = await service.getStatus();
@@ -90,12 +159,18 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
90
159
  }
91
160
 
92
161
  if (endpoint === BRIDGE_ENDPOINTS.stopCustomTunnel) {
162
+ const adminErr = checkAdminAuth(authManager, payload);
163
+ if (adminErr) return adminErr;
164
+
93
165
  service.stopCustomTunnel();
94
166
  const status = await service.getStatus();
95
167
  return ok(status);
96
168
  }
97
169
 
98
170
  if (endpoint === BRIDGE_ENDPOINTS.startCloudflared) {
171
+ const adminErr = checkAdminAuth(authManager, payload);
172
+ if (adminErr) return adminErr;
173
+
99
174
  try {
100
175
  await service.startCloudflared();
101
176
  const status = await service.getStatus();
@@ -107,12 +182,18 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
107
182
  }
108
183
 
109
184
  if (endpoint === BRIDGE_ENDPOINTS.stopCloudflared) {
185
+ const adminErr = checkAdminAuth(authManager, payload);
186
+ if (adminErr) return adminErr;
187
+
110
188
  service.stopCloudflared();
111
189
  const status = await service.getStatus();
112
190
  return ok(status);
113
191
  }
114
192
 
115
193
  if (endpoint === BRIDGE_ENDPOINTS.resetCloudflared) {
194
+ const adminErr = checkAdminAuth(authManager, payload);
195
+ if (adminErr) return adminErr;
196
+
116
197
  await service.resetCloudflared();
117
198
  const status = await service.getStatus();
118
199
  return ok(status);
@@ -124,6 +205,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
124
205
  }
125
206
 
126
207
  if (endpoint === BRIDGE_ENDPOINTS.upgradePlugin) {
208
+ const adminErr = checkAdminAuth(authManager, payload);
209
+ if (adminErr) return adminErr;
210
+
127
211
  const result = await service.upgradePlugin(payload);
128
212
  return ok(result);
129
213
  }
@@ -147,6 +231,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
147
231
 
148
232
  if (endpoint === BRIDGE_ENDPOINTS.platformLogin) {
149
233
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
234
+ const adminErr = checkAdminAuth(authManager, payload);
235
+ if (adminErr) return adminErr;
236
+
150
237
  const { platformId, qrType } = payload;
151
238
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
152
239
  const platform = platformManager.get(platformId);
@@ -160,6 +247,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
160
247
 
161
248
  if (endpoint === BRIDGE_ENDPOINTS.platformSetAllowFrom) {
162
249
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
250
+ const adminErr = checkAdminAuth(authManager, payload);
251
+ if (adminErr) return adminErr;
252
+
163
253
  const { platformId, allowFrom } = payload;
164
254
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
165
255
  const platform = platformManager.get(platformId);
@@ -172,6 +262,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
172
262
 
173
263
  if (endpoint === BRIDGE_ENDPOINTS.platformSetConfig) {
174
264
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
265
+ const adminErr = checkAdminAuth(authManager, payload);
266
+ if (adminErr) return adminErr;
267
+
175
268
  const { platformId, ...config } = payload;
176
269
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
177
270
  const platform = platformManager.get(platformId);
@@ -184,6 +277,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
184
277
 
185
278
  if (endpoint === BRIDGE_ENDPOINTS.platformStop) {
186
279
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
280
+ const adminErr = checkAdminAuth(authManager, payload);
281
+ if (adminErr) return adminErr;
282
+
187
283
  const { platformId } = payload;
188
284
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
189
285
  const platform = platformManager.get(platformId);
@@ -196,6 +292,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
196
292
 
197
293
  if (endpoint === BRIDGE_ENDPOINTS.platformStart) {
198
294
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
295
+ const adminErr = checkAdminAuth(authManager, payload);
296
+ if (adminErr) return adminErr;
297
+
199
298
  const { platformId } = payload;
200
299
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
201
300
  const platform = platformManager.get(platformId);
@@ -208,6 +307,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
208
307
 
209
308
  if (endpoint === BRIDGE_ENDPOINTS.platformUnbind) {
210
309
  if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
310
+ const adminErr = checkAdminAuth(authManager, payload);
311
+ if (adminErr) return adminErr;
312
+
211
313
  const { platformId } = payload;
212
314
  if (!platformId) return fail('bad-request', '缺少 platformId 参数');
213
315
  const platform = platformManager.get(platformId);
@@ -228,6 +330,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
228
330
 
229
331
  if (endpoint === BRIDGE_ENDPOINTS.wechatLogin) {
230
332
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
333
+ const adminErr = checkAdminAuth(authManager, payload);
334
+ if (adminErr) return adminErr;
335
+
231
336
  const { qrType } = payload;
232
337
  const result = await wechat.login({ qrType });
233
338
  if (!result.ok) return fail('bad-request', result.error ?? '登录启动失败');
@@ -237,6 +342,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
237
342
 
238
343
  if (endpoint === BRIDGE_ENDPOINTS.wechatSetAllowFrom) {
239
344
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
345
+ const adminErr = checkAdminAuth(authManager, payload);
346
+ if (adminErr) return adminErr;
347
+
240
348
  await wechat.setAllowFrom(payload.allowFrom);
241
349
  const value = await wechatStatusValue(wechat, logger);
242
350
  return ok(value);
@@ -244,6 +352,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
244
352
 
245
353
  if (endpoint === BRIDGE_ENDPOINTS.wechatSetConfig) {
246
354
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
355
+ const adminErr = checkAdminAuth(authManager, payload);
356
+ if (adminErr) return adminErr;
357
+
247
358
  await wechat.setConfig(payload);
248
359
  const value = await wechatStatusValue(wechat, logger);
249
360
  return ok(value);
@@ -251,6 +362,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
251
362
 
252
363
  if (endpoint === BRIDGE_ENDPOINTS.wechatStop) {
253
364
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
365
+ const adminErr = checkAdminAuth(authManager, payload);
366
+ if (adminErr) return adminErr;
367
+
254
368
  await wechat.stop();
255
369
  const value = await wechatStatusValue(wechat, logger);
256
370
  return ok(value);
@@ -258,6 +372,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
258
372
 
259
373
  if (endpoint === BRIDGE_ENDPOINTS.wechatStart) {
260
374
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
375
+ const adminErr = checkAdminAuth(authManager, payload);
376
+ if (adminErr) return adminErr;
377
+
261
378
  await wechat.gateway.start().catch((err) => {
262
379
  logger.error('wechat start enabled: %s', err?.message ?? err);
263
380
  });
@@ -267,6 +384,9 @@ export function installBridgeRpc(ctx, { service, wechat, platformManager, logger
267
384
 
268
385
  if (endpoint === BRIDGE_ENDPOINTS.wechatUnbind) {
269
386
  if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
387
+ const adminErr = checkAdminAuth(authManager, payload);
388
+ if (adminErr) return adminErr;
389
+
270
390
  await wechat.unbind();
271
391
  const value = await wechatStatusValue(wechat, logger);
272
392
  return ok(value);
@@ -75,13 +75,13 @@ export class FeishuConversationNode extends ConversationBridge {
75
75
  this._inTurn = false
76
76
 
77
77
  // 订阅网关入站消息事件
78
- this.ctx.on('feishu/message', (event) => this._handleInbound(event))
78
+ this.disposers.push(this.ctx.on('feishu/message', (event) => this._handleInbound(event)))
79
79
 
80
80
  // 订阅卡片交互事件(审批按钮点击)
81
- this.ctx.on('feishu/action', (event) => this._handleAction(event))
81
+ this.disposers.push(this.ctx.on('feishu/action', (event) => this._handleAction(event)))
82
82
 
83
83
  // 监听轮次事件:turn/start 开启流式会话,turn/end 最终刷新并重置
84
- this.ctx.on('session/event', (session, event) => {
84
+ this.disposers.push(this.ctx.on('session/event', (session, event) => {
85
85
  if (session.id !== this.activeSessionId) return
86
86
  if (event.type === 'turn/start') {
87
87
  this._inTurn = true
@@ -103,7 +103,7 @@ export class FeishuConversationNode extends ConversationBridge {
103
103
  this._streamCardId = null
104
104
  this._streamContent = ''
105
105
  }
106
- })
106
+ }))
107
107
  }
108
108
 
109
109
  async _handleInbound(event) {