@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/README.en.md +46 -9
- package/README.md +46 -9
- package/client/client.js +821 -28
- package/client/index.js +711 -35
- package/docs/platform-abstraction-design.md +473 -0
- package/docs/screenshots/qq-chat.jpg +0 -0
- package/docs/screenshots/qq-group.jpg +0 -0
- package/docs/screenshots/qr-scan.jpg +0 -0
- package/docs/screenshots/wechat-chat.jpg +0 -0
- package/docs/wechat-bot-plan.md +294 -0
- package/lib/auth/login-template.js +378 -0
- package/lib/auth/manager.js +470 -0
- package/lib/bridge-rpc-constants.js +6 -0
- package/lib/bridge-rpc.js +126 -6
- package/lib/feishu/node.js +4 -4
- package/lib/index.js +258 -32
- package/lib/qq/node.js +9 -2
- package/lib/telegram/node.js +4 -4
- package/lib/tunnel-client.mjs +11 -2
- package/lib/wechat/media.js +8 -2
- package/lib/wechat/node.js +2 -2
- package/package.json +6 -9
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
|
-
|
|
15
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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);
|
package/lib/feishu/node.js
CHANGED
|
@@ -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) {
|