cclawd 1.0.7 → 1.0.8
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/dist/{active-listener-DYmI7imH.js → active-listener-Dkhmfuwx.js} +2 -2
- package/dist/{api-key-rotation-DLU4jvSu.js → api-key-rotation-BOfI3cG3.js} +1 -1
- package/dist/{audio-preflight-C9TMbRb4.js → audio-preflight-CtkZ5SAs.js} +15 -15
- package/dist/{audio-transcription-runner-Q5zG_hYd.js → audio-transcription-runner-CbPqoiHX.js} +10 -10
- package/dist/{audit-membership-runtime-DhxSwFnF.js → audit-membership-runtime-hXUuer4x.js} +6 -6
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +35 -35
- package/dist/bundled/bootstrap-extra-files/handler.js +5 -5
- package/dist/bundled/command-logger/handler.js +2 -2
- package/dist/bundled/session-memory/handler.js +35 -35
- package/dist/{channel-activity-Bx08UTAg.js → channel-activity-DWAER4wd.js} +2 -2
- package/dist/{commands-registry-llLVCTH9.js → commands-registry-BUyiA7nE.js} +2 -2
- package/dist/compact.runtime-DpcZpcTl.js +39 -0
- package/dist/{deliver-DJf2ZBpe.js → deliver-aHOaRbkt.js} +19 -19
- package/dist/deliver-runtime-D4bCsr6d.js +19 -0
- package/dist/deps-send-discord.runtime-BziKU-pE.js +19 -0
- package/dist/deps-send-imessage.runtime-CFRnDTqp.js +18 -0
- package/dist/deps-send-signal.runtime-BuOtABJm.js +17 -0
- package/dist/deps-send-slack.runtime-BOLqvMxW.js +17 -0
- package/dist/deps-send-telegram.runtime-DeEoFLv5.js +20 -0
- package/dist/deps-send-whatsapp.runtime-CG1uXYLY.js +43 -0
- package/dist/{diagnostic-BCCMF3O_.js → diagnostic-BdcXX9iJ.js} +2 -2
- package/dist/{env-aYXLHjfZ.js → env-lw2hsIUY.js} +1 -1
- package/dist/{fetch-bvgIiupu.js → fetch-C0iyt-Iz.js} +3 -3
- package/dist/{fetch-DCTUdr1U.js → fetch-D9NUULbj.js} +5 -5
- package/dist/{fetch-guard-CqpEmMQ2.js → fetch-guard-B5ZMnGaN.js} +2 -2
- package/dist/{frontmatter-DjZuS525.js → frontmatter-C_obXuTp.js} +3 -3
- package/dist/{github-copilot-token-CQmATy5E.js → github-copilot-token-8N63GdbE.js} +7 -7
- package/dist/{image-Q8E1-lZn.js → image-4x07m4Jl.js} +4 -4
- package/dist/image-runtime-smkMrIol.js +12 -0
- package/dist/{ir-CzM3SxId.js → ir-CsgNUpOU.js} +6 -6
- package/dist/llm-slug-generator.js +35 -35
- package/dist/{logger-ChbX1G7s.js → logger-CbUVl62f.js} +7 -7
- package/dist/{login-B0mtU11X.js → login-p_O59TVQ.js} +4 -4
- package/dist/{login-qr-DY_i60f5.js → login-qr-BCJpDsAy.js} +10 -10
- package/dist/{manager-FAQPC0uO.js → manager-CwYv8O3T.js} +12 -12
- package/dist/manager-runtime-D_jEoBr9.js +15 -0
- package/dist/{model-selection-wf3OY5DX.js → model-selection-Cv2Puf5z.js} +122 -122
- package/dist/{outbound-Bw0dOVS7.js → outbound-Chpiwybe.js} +6 -6
- package/dist/{outbound-attachment-1R6r9Pg_.js → outbound-attachment-BnAVJDLe.js} +2 -2
- package/dist/{paths-C0HLtPu0.js → paths-CehYKFsO.js} +7 -7
- package/dist/{paths-hfkBoC7i.js → paths-DkxwiA8g.js} +5 -5
- package/dist/{pi-embedded-BAHaY-Oh.js → pi-embedded-CJVNBk0y.js} +150 -150
- package/dist/pi-model-discovery-7IzK0Uc3.js +131 -0
- package/dist/pi-model-discovery-runtime-DABef3qy.js +12 -0
- package/dist/{pi-tools.before-tool-call.runtime-D_mthvtC.js → pi-tools.before-tool-call.runtime-BP2UvGJb.js} +10 -10
- package/dist/plugin-sdk/active-listener-CN-tMEvN.js +35 -0
- package/dist/plugin-sdk/api-key-rotation-CimGYMBc.js +176 -0
- package/dist/plugin-sdk/audio-preflight-C-xXBoE2.js +51 -0
- package/dist/plugin-sdk/audio-transcription-runner-CTIHpebA.js +2173 -0
- package/dist/plugin-sdk/audit-membership-runtime-BFatB2LJ.js +58 -0
- package/dist/plugin-sdk/channel-activity-DO0FEzyj.js +95 -0
- package/dist/plugin-sdk/channel-web-Da-__nUF.js +2238 -0
- package/dist/plugin-sdk/commands-registry-6no2NNrY.js +1118 -0
- package/dist/plugin-sdk/compact.runtime-CCoclu5e.js +35 -0
- package/dist/plugin-sdk/compat.js +35 -35
- package/dist/plugin-sdk/config-B9ODwgpz.js +37426 -0
- package/dist/plugin-sdk/deliver-B1fFpKjV.js +1757 -0
- package/dist/plugin-sdk/deliver-runtime-DB-VRMe1.js +15 -0
- package/dist/plugin-sdk/deps-send-discord.runtime-DklqycYG.js +15 -0
- package/dist/plugin-sdk/deps-send-imessage.runtime-Chs8zeon.js +14 -0
- package/dist/plugin-sdk/deps-send-signal.runtime-clW9aSJP.js +13 -0
- package/dist/plugin-sdk/deps-send-slack.runtime-BUx0LYY1.js +13 -0
- package/dist/plugin-sdk/deps-send-telegram.runtime-LECSHgMG.js +16 -0
- package/dist/plugin-sdk/deps-send-whatsapp.runtime-D2d65fw0.js +40 -0
- package/dist/plugin-sdk/diagnostic-CxIvS-C2.js +315 -0
- package/dist/plugin-sdk/dispatch-BqlR4dPx.js +105863 -0
- package/dist/plugin-sdk/env-b9k1PHMI.js +34 -0
- package/dist/plugin-sdk/fetch-PoxzAANT.js +326 -0
- package/dist/plugin-sdk/fetch-guard-4UVSZ0uS.js +164 -0
- package/dist/plugin-sdk/image-Ch6M4tnJ.js +2420 -0
- package/dist/plugin-sdk/image-runtime-CSh2o5wY.js +8 -0
- package/dist/plugin-sdk/ir-CugsqGH8.js +1312 -0
- package/dist/plugin-sdk/local-roots-adnEg9zb.js +217 -0
- package/dist/plugin-sdk/logger-D6zRubj0.js +1164 -0
- package/dist/plugin-sdk/login-CYvkQ0At.js +54 -0
- package/dist/plugin-sdk/login-qr-ll4NtaT5.js +316 -0
- package/dist/plugin-sdk/manager-CHy8IclH.js +3959 -0
- package/dist/plugin-sdk/manager-runtime-C70EkEr7.js +11 -0
- package/dist/plugin-sdk/outbound-Wzs2iN7X.js +216 -0
- package/dist/plugin-sdk/outbound-attachment-khXJwucX.js +17 -0
- package/dist/plugin-sdk/paths-BtVqCdw4.js +3063 -0
- package/dist/{pi-model-discovery-ItS07aJB.js → plugin-sdk/pi-model-discovery-Dh4ziodY.js} +2 -2
- package/dist/plugin-sdk/pi-model-discovery-runtime-b83Xe-HT.js +8 -0
- package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-C1z5CDBF.js +349 -0
- package/dist/{proxy-fetch-c1ZUFFcO.js → plugin-sdk/proxy-fetch-CJEmoBxi.js} +1 -1
- package/dist/plugin-sdk/pw-ai-Dj3Cvlzl.js +1990 -0
- package/dist/plugin-sdk/qmd-manager-egHUAseQ.js +1581 -0
- package/dist/plugin-sdk/resolve-outbound-target-BiICvIKs.js +38 -0
- package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DNApufzW.js +9 -0
- package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CBmtfIQ8.js +13 -0
- package/dist/plugin-sdk/send-CScblaI4.js +532 -0
- package/dist/plugin-sdk/send-CeHhnld6.js +407 -0
- package/dist/plugin-sdk/send-DP_c8JfR.js +3277 -0
- package/dist/plugin-sdk/send-Dc5fI6e8.js +495 -0
- package/dist/plugin-sdk/send-l-77_s1_.js +2507 -0
- package/dist/plugin-sdk/session-CkOKZaqa.js +166 -0
- package/dist/plugin-sdk/signal.js +2 -2
- package/dist/plugin-sdk/skill-commands-BohYCgkq.js +336 -0
- package/dist/plugin-sdk/slash-commands.runtime-DpLfVTM6.js +8 -0
- package/dist/plugin-sdk/slash-dispatch.runtime-CASMHwpm.js +35 -0
- package/dist/plugin-sdk/slash-skill-commands.runtime-D7rrJEci.js +9 -0
- package/dist/plugin-sdk/sqlite-CJE3X7Mv.js +1005 -0
- package/dist/plugin-sdk/subagent-registry-runtime-B1oo5bih.js +35 -0
- package/dist/plugin-sdk/tables-D5VgpTmm.js +53 -0
- package/dist/plugin-sdk/target-errors-C6zZ_OpA.js +191 -0
- package/dist/{tokens-BWDIKewp.js → plugin-sdk/tokens-DUnJnpMS.js} +1 -1
- package/dist/plugin-sdk/web-TfUM1nSi.js +39 -0
- package/dist/plugin-sdk/whatsapp-actions-DuWJ0j1r.js +71 -0
- package/dist/proxy-fetch-BOh1PLOW.js +54 -0
- package/dist/{pw-ai-Ok6KGelf.js → pw-ai-DwH5GpEO.js} +9 -9
- package/dist/{qmd-manager-DhfEz4Ar.js → qmd-manager-DEscZz5_.js} +6 -6
- package/dist/{query-expansion-GqNV2iIE.js → query-expansion-BErUY8P2.js} +4 -4
- package/dist/runtime-whatsapp-login.runtime-BI3U306v.js +13 -0
- package/dist/runtime-whatsapp-outbound.runtime-Bsc2uD09.js +17 -0
- package/dist/{send-DwAoiT2p.js → send-BDnOgWIp.js} +25 -25
- package/dist/{send-CS0ocZHl.js → send-C-Q_WPMf.js} +3 -3
- package/dist/{send-6R8b9zsj.js → send-DUibfNQD.js} +5 -5
- package/dist/{send-DPflcjM5.js → send-DtBvCnPQ.js} +6 -6
- package/dist/{send-CEg4P96c.js → send-ORtn50qg.js} +5 -5
- package/dist/{session-BoIID5UR.js → session-B7imi6T5.js} +7 -7
- package/dist/{skill-commands-DhdiziMs.js → skill-commands-B9brPuiL.js} +9 -9
- package/dist/slash-commands.runtime-Cf6ygfBp.js +12 -0
- package/dist/slash-dispatch.runtime-CsmvhO5K.js +39 -0
- package/dist/slash-skill-commands.runtime-CX7stIEP.js +13 -0
- package/dist/subagent-registry-runtime-B_S1nf7y.js +39 -0
- package/dist/{subsystem-C8z6w6xC.js → subsystem-DfXy5gUB.js} +14 -14
- package/dist/{tables-DQusRhkD.js → tables-CjQqTOdD.js} +1 -1
- package/dist/{target-errors-CfavnC9U.js → target-errors-BZE1mc-W.js} +1 -1
- package/dist/tokens-6ul2IrzG.js +50 -0
- package/dist/{web-CrcrTQ2c.js → web-Cd8yK1Zq.js} +39 -39
- package/dist/{whatsapp-actions-B0u0ZAme.js → whatsapp-actions-CYEzUMBI.js} +15 -15
- package/dist/{workspace-CWDYHR27.js → workspace-DGIcKCCW.js} +20 -20
- package/extensions/cclawd-mfa-auth/README.md +118 -0
- package/extensions/cclawd-mfa-auth/index.ts +382 -0
- package/extensions/cclawd-mfa-auth/openclaw.plugin.json +34 -0
- package/extensions/cclawd-mfa-auth/package.json +27 -0
- package/extensions/cclawd-mfa-auth/src/auth-manager.ts +536 -0
- package/extensions/cclawd-mfa-auth/src/config.ts +82 -0
- package/extensions/cclawd-mfa-auth/src/dabby-client.ts +200 -0
- package/extensions/cclawd-mfa-auth/src/notification-service.ts +417 -0
- package/extensions/cclawd-mfa-auth/src/polling-manager.ts +232 -0
- package/extensions/cclawd-mfa-auth/src/providers/base.ts +25 -0
- package/extensions/cclawd-mfa-auth/src/providers/qr-code.ts +98 -0
- package/extensions/cclawd-mfa-auth/src/sensitive-detector.ts +159 -0
- package/extensions/cclawd-mfa-auth/src/session-resolver.ts +159 -0
- package/extensions/cclawd-mfa-auth/src/types.ts +156 -0
- package/extensions/mfa-auth/index.ts +675 -1028
- package/extensions/mfa-auth/openclaw.plugin.json +1 -1
- package/extensions/mfa-auth/src/notification-service.ts +2 -8
- package/package.json +453 -453
- package/dist/compact.runtime-BEn3giMt.js +0 -39
- package/dist/deliver-runtime-DkQ3XzGv.js +0 -19
- package/dist/deps-send-discord.runtime-BLpqSj6s.js +0 -19
- package/dist/deps-send-imessage.runtime-BFzyYqvR.js +0 -18
- package/dist/deps-send-signal.runtime-DT0TYCy1.js +0 -17
- package/dist/deps-send-slack.runtime-BhaGFfMX.js +0 -17
- package/dist/deps-send-telegram.runtime-B6Cic9NX.js +0 -20
- package/dist/deps-send-whatsapp.runtime-WtEhIq2S.js +0 -43
- package/dist/image-runtime-B1LFYfQ2.js +0 -12
- package/dist/manager-runtime-Da7ME9vS.js +0 -15
- package/dist/pi-model-discovery-runtime-DjM7Z1fx.js +0 -12
- package/dist/runtime-whatsapp-login.runtime-D4BRhQkK.js +0 -13
- package/dist/runtime-whatsapp-outbound.runtime-DJPpS6g-.js +0 -17
- package/dist/slash-commands.runtime-Cu1lTjV9.js +0 -12
- package/dist/slash-dispatch.runtime-DRVJEF4l.js +0 -39
- package/dist/slash-skill-commands.runtime-C373PJjv.js +0 -13
- package/dist/subagent-registry-runtime-D7hWBo1G.js +0 -39
|
@@ -1,1065 +1,712 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
-
import { authManager } from "./src/auth-manager.js";
|
|
3
|
-
import { config } from "./src/config.js";
|
|
4
|
-
import { dabbyClient } from "./src/dabby-client.js";
|
|
5
|
-
import { NotificationService } from "./src/notification-service.js";
|
|
6
|
-
import { qrCodeAuthProvider } from "./src/providers/qr-code.js";
|
|
7
|
-
import { setNotifyCallback } from "./src/server.js";
|
|
8
|
-
import type { AuthSession } from "./src/types.js";
|
|
9
|
-
|
|
10
|
-
const notificationService = NotificationService.getInstance();
|
|
11
|
-
const pendingAuthUsers = new Set<string>();
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
latestPending = session;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return latestPending;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function buildFirstMessageChallengeText(metadata: Record<string, unknown>): string {
|
|
199
|
-
const qrCodeUrl = typeof metadata.qrCodeUrl === "string" ? metadata.qrCodeUrl : "";
|
|
200
|
-
const isReauth = metadata.isReauth === true;
|
|
201
|
-
if (isReauth) {
|
|
202
|
-
return `\u9700\u8981\u91cd\u65b0\u8ba4\u8bc1\n\n\u8bf7\u70b9\u51fb\u4e0b\u65b9\u94fe\u63a5\u5b8c\u6210\u626b\u7801\u8ba4\u8bc1\uff1a\n${qrCodeUrl}\n\n\u8ba4\u8bc1\u6709\u6548\u671f\uff1a${Math.floor(config.timeout / 60000)} \u5206\u949f\u3002`;
|
|
203
|
-
}
|
|
204
|
-
return `\u9996\u6b21\u5bf9\u8bdd\u9700\u8981\u8ba4\u8bc1\n\n\u8bf7\u70b9\u51fb\u4e0b\u65b9\u94fe\u63a5\u5b8c\u6210\u626b\u7801\u8ba4\u8bc1\uff1a\n${qrCodeUrl}\n\n\u8ba4\u8bc1\u6709\u6548\u671f\uff1a${Math.floor(config.timeout / 60000)} \u5206\u949f\u3002`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function uniqueNonEmpty(values: Array<string | undefined>): string[] {
|
|
208
|
-
return Array.from(
|
|
209
|
-
new Set(
|
|
210
|
-
values
|
|
211
|
-
.map((value) => String(value ?? "").trim())
|
|
212
|
-
.filter(Boolean),
|
|
213
|
-
),
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function isAnyFirstMessageAliasVerified(userIds: string[]): string | undefined {
|
|
218
|
-
for (const userId of userIds) {
|
|
219
|
-
if (authManager.isUserVerifiedForFirstMessage(userId)) {
|
|
220
|
-
return userId;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function consumeNotificationByAliases(userIds: string[]): {
|
|
227
|
-
triggerType: "first_message" | "sensitive_operation";
|
|
228
|
-
isReauth: boolean;
|
|
229
|
-
} | null {
|
|
230
|
-
for (const userId of userIds) {
|
|
231
|
-
const notification = authManager.checkAndConsumeNotification(userId);
|
|
232
|
-
if (notification) {
|
|
233
|
-
return notification;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function resolveInboundAuthUserId(params: {
|
|
240
|
-
channelId?: string;
|
|
241
|
-
from?: string;
|
|
242
|
-
senderId?: string;
|
|
243
|
-
conversationId?: string;
|
|
244
|
-
metadataTo?: string;
|
|
245
|
-
metadataOriginatingTo?: string;
|
|
246
|
-
}): string {
|
|
247
|
-
const candidates = [
|
|
248
|
-
params.from,
|
|
249
|
-
params.senderId,
|
|
250
|
-
params.conversationId,
|
|
251
|
-
params.metadataOriginatingTo,
|
|
252
|
-
params.metadataTo,
|
|
253
|
-
];
|
|
254
|
-
const firstNonEmpty = candidates
|
|
255
|
-
.map((value) => String(value ?? "").trim())
|
|
256
|
-
.find((value) => value.length > 0);
|
|
257
|
-
|
|
258
|
-
if (isWebchatChannel(params.channelId)) {
|
|
259
|
-
return firstNonEmpty || "unknown";
|
|
260
|
-
}
|
|
261
|
-
return firstNonEmpty || "unknown";
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function sendAuthMessage(
|
|
265
|
-
channel: string | undefined,
|
|
266
|
-
accountId: string | undefined,
|
|
267
|
-
to: string,
|
|
268
|
-
message: string,
|
|
269
|
-
userId: string,
|
|
270
|
-
overrideSessionKey?: string,
|
|
271
|
-
): Promise<void> {
|
|
272
|
-
const session: AuthSession = {
|
|
273
|
-
userId,
|
|
274
|
-
sessionId: "notification",
|
|
275
|
-
authMethod: "qr-code",
|
|
276
|
-
timestamp: Date.now(),
|
|
277
|
-
originalContext: {
|
|
278
|
-
sessionKey: overrideSessionKey || `${channel || "web"}:${accountId || ""}:${userId}`,
|
|
279
|
-
senderId: userId,
|
|
280
|
-
commandBody: "",
|
|
281
|
-
channel: channel || "web",
|
|
282
|
-
accountId: accountId || "",
|
|
283
|
-
to,
|
|
284
|
-
toolName: "notification",
|
|
285
|
-
toolParams: {},
|
|
286
|
-
timestamp: Date.now(),
|
|
287
|
-
triggerType: "sensitive_operation",
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
await notificationService.sendAuthNotification(session, message);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export default function register(api: OpenClawPluginApi) {
|
|
295
|
-
console.log("[mfa-auth] Plugin registration started");
|
|
296
|
-
authManager.registerProvider(qrCodeAuthProvider);
|
|
297
|
-
|
|
298
|
-
notificationService.setConfig(api.config);
|
|
299
|
-
|
|
300
|
-
setNotifyCallback(async (session: AuthSession) => {
|
|
301
|
-
api.logger.info(`[mfa-auth] User ${session.userId} verified`);
|
|
302
|
-
|
|
303
|
-
if (!config.enableAuthNotification) {
|
|
304
|
-
api.logger.info(`[mfa-auth] Auth notification disabled, skipping message send.`);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
const commandBody = session.originalContext.commandBody;
|
|
310
|
-
const triggerType = session.originalContext.triggerType || "sensitive_operation";
|
|
311
|
-
|
|
312
|
-
const isFirstMessageAuth = triggerType === "first_message";
|
|
313
|
-
const isReauth = commandBody.trim() === "/reauth";
|
|
314
|
-
|
|
315
|
-
let messageText = "";
|
|
316
|
-
if (isFirstMessageAuth) {
|
|
317
|
-
messageText = isReauth
|
|
318
|
-
? "\u2705 \u91cd\u65b0\u8ba4\u8bc1\u6210\u529f\uff0c\u8bf7\u91cd\u65b0\u53d1\u9001\u6d88\u606f\u7ee7\u7eed\u5bf9\u8bdd\u3002"
|
|
319
|
-
: "\u2705 \u9996\u6b21\u8ba4\u8bc1\u6210\u529f\uff0c\u8bf7\u91cd\u65b0\u53d1\u9001\u6d88\u606f\u7ee7\u7eed\u5bf9\u8bdd\u3002";
|
|
320
|
-
} else {
|
|
321
|
-
messageText = "\u2705 \u4e8c\u6b21\u8ba4\u8bc1\u6210\u529f\uff0c\u8bf7\u91cd\u65b0\u53d1\u9001\u4e4b\u524d\u7684\u547d\u4ee4\u6216\u56de\u590d\u201c\u786e\u8ba4\u201d\u3002";
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const channel = session.originalContext.channel;
|
|
325
|
-
const sessionKey =
|
|
326
|
-
session.originalContext.sessionKey ||
|
|
327
|
-
`${channel}:${session.originalContext.accountId || ""}:${session.userId}`;
|
|
328
|
-
|
|
329
|
-
api.logger.info(`[mfa-auth] Sending notification to session: ${sessionKey}`);
|
|
330
|
-
|
|
331
|
-
await sendAuthMessage(
|
|
332
|
-
channel,
|
|
333
|
-
session.originalContext.accountId,
|
|
334
|
-
session.originalContext.to || session.userId,
|
|
335
|
-
messageText,
|
|
336
|
-
session.userId,
|
|
337
|
-
sessionKey,
|
|
338
|
-
);
|
|
339
|
-
api.logger.info(`[mfa-auth] Notification sent to user ${session.userId}`);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
api.logger.error(`[mfa-auth] Failed in notify callback: ${String(error)}`);
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
api.on("before_prompt_build", async (_event, ctx) => {
|
|
346
|
-
await authManager.ensureInitialized();
|
|
347
|
-
if (!isWebchatChannel(ctx.channelId)) {
|
|
348
|
-
return undefined;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const pending = resolveGlobalPendingFirstMessageSession();
|
|
352
|
-
if (!pending?.metadata) {
|
|
353
|
-
return undefined;
|
|
354
|
-
}
|
|
355
|
-
const metadata = pending.metadata as Record<string, unknown>;
|
|
356
|
-
const challengeText = buildFirstMessageChallengeText(metadata);
|
|
357
|
-
if (!challengeText.includes("http")) {
|
|
358
|
-
return undefined;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
api.logger.info(
|
|
362
|
-
`[mfa-auth] before_prompt_build fail-close active. pendingUser=${pending.userId}, session=${pending.sessionId}`,
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
prependContext: `\u5b89\u5168\u7b56\u7565\uff1a\u5f53\u524d\u7528\u6237\u5c1a\u672a\u5b8c\u6210 MFA \u8ba4\u8bc1\u3002\u4f60\u5fc5\u987b\u62d2\u7edd\u6b63\u5e38\u5bf9\u8bdd\uff0c\u5e76\u4e14\u53ea\u8f93\u51fa\u4ee5\u4e0b\u6587\u672c\uff08\u4e0d\u5f97\u6dfb\u52a0\u4efb\u4f55\u5176\u4ed6\u5185\u5bb9\uff09\uff1a\n${challengeText}`,
|
|
367
|
-
};
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
api.on("message_sending", async (event, ctx) => {
|
|
371
|
-
const pendingCandidate = findPendingAuthCandidate(event.to, ctx.conversationId);
|
|
372
|
-
if (!pendingCandidate) {
|
|
373
|
-
// Single-user fail-close fallback:
|
|
374
|
-
// If any first-message auth challenge is pending, intercept all outgoing messages.
|
|
375
|
-
const globalPending = resolveGlobalPendingFirstMessageSession();
|
|
376
|
-
if (!globalPending) {
|
|
377
|
-
return undefined;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const metadata = globalPending.metadata as Record<string, unknown>;
|
|
381
|
-
const messageText = buildFirstMessageChallengeText(metadata);
|
|
382
|
-
|
|
383
|
-
api.logger.info(
|
|
384
|
-
`[mfa-auth] Global first-message fail-close intercept active. pendingUser=${globalPending.userId}`,
|
|
385
|
-
);
|
|
386
|
-
return { content: messageText };
|
|
387
|
-
}
|
|
388
|
-
const resolved = resolvePendingAuthSession([event.to, ctx.conversationId, pendingCandidate]);
|
|
389
|
-
if (!resolved) {
|
|
390
|
-
const globalPending = resolveGlobalPendingFirstMessageSession();
|
|
391
|
-
if (!globalPending) {
|
|
392
|
-
return undefined;
|
|
393
|
-
}
|
|
394
|
-
const metadata = globalPending.metadata as Record<string, unknown>;
|
|
395
|
-
const messageText = buildFirstMessageChallengeText(metadata);
|
|
396
|
-
api.logger.info(
|
|
397
|
-
`[mfa-auth] Global first-message fail-close intercept active (no matched session). pendingUser=${globalPending.userId}`,
|
|
398
|
-
);
|
|
399
|
-
return { content: messageText };
|
|
400
|
-
}
|
|
401
|
-
const userId = resolved.session.userId;
|
|
402
|
-
const metadata = resolved.session.metadata as Record<string, unknown> | undefined;
|
|
403
|
-
|
|
404
|
-
if (metadata?.qrCodeUrl) {
|
|
405
|
-
pendingAuthUsers.delete(resolved.matchedId);
|
|
406
|
-
pendingAuthUsers.delete(userId);
|
|
407
|
-
|
|
408
|
-
let messageText = "";
|
|
409
|
-
if (metadata.triggerType === "first_message") {
|
|
410
|
-
const isReauth = metadata.isReauth === true;
|
|
411
|
-
messageText = isReauth
|
|
412
|
-
? `\u9700\u8981\u91cd\u65b0\u8ba4\u8bc1\n\n\u8bf7\u70b9\u51fb\u4e0b\u65b9\u94fe\u63a5\u5b8c\u6210\u626b\u7801\u8ba4\u8bc1\uff1a\n${metadata.qrCodeUrl}\n\n\u8ba4\u8bc1\u6709\u6548\u671f\uff1a${Math.floor(config.timeout / 60000)} \u5206\u949f\u3002`
|
|
413
|
-
: `\u9996\u6b21\u5bf9\u8bdd\u9700\u8981\u8ba4\u8bc1\n\n\u8bf7\u70b9\u51fb\u4e0b\u65b9\u94fe\u63a5\u5b8c\u6210\u626b\u7801\u8ba4\u8bc1\uff1a\n${metadata.qrCodeUrl}\n\n\u8ba4\u8bc1\u6709\u6548\u671f\uff1a${Math.floor(config.timeout / 60000)} \u5206\u949f\u3002`;
|
|
414
|
-
} else if (metadata.triggerType === "sensitive_operation") {
|
|
415
|
-
messageText = `\u68c0\u6d4b\u5230\u654f\u611f\u64cd\u4f5c\uff0c\u9700\u4e8c\u6b21\u8ba4\u8bc1\u3002\n\n\u64cd\u4f5c\u5185\u5bb9\uff1a${metadata.commandPreview}\n\n\u8bf7\u70b9\u51fb\u4e0b\u65b9\u94fe\u63a5\u5b8c\u6210\u626b\u7801\u8ba4\u8bc1\uff1a\n${metadata.qrCodeUrl}\n\n\u8ba4\u8bc1\u6709\u6548\u671f\uff1a${Math.floor(config.timeout / 60000)} \u5206\u949f\u3002\n\n\u8ba4\u8bc1\u5b8c\u6210\u540e\uff0c\u8bf7\u91cd\u65b0\u53d1\u9001\u4e0a\u4e00\u6761\u547d\u4ee4\u6216\u56de\u590d\u201c\u786e\u8ba4\u201d\u3002`;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return { content: messageText };
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
api.on("before_tool_call", async (event, ctx) => {
|
|
423
|
-
await authManager.ensureInitialized();
|
|
424
|
-
if (!config.requireAuthOnSensitiveOperation) {
|
|
425
|
-
return undefined;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const { toolName, params } = event;
|
|
429
|
-
|
|
430
|
-
api.logger.info(`[mfa-auth] Tool call detected: ${toolName}`);
|
|
431
|
-
|
|
432
|
-
const sensitiveTools = ["bash", "exec", "runCommand", "command", "process"];
|
|
433
|
-
if (!sensitiveTools.includes(toolName)) {
|
|
434
|
-
api.logger.info(`[mfa-auth] Tool ${toolName} is not in sensitive list, allowing`);
|
|
435
|
-
return undefined;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const command =
|
|
439
|
-
typeof params?.command === "string"
|
|
440
|
-
? params.command
|
|
441
|
-
: typeof params?.cmd === "string"
|
|
442
|
-
? params.cmd
|
|
443
|
-
: typeof params?.input === "string"
|
|
444
|
-
? params.input
|
|
445
|
-
: typeof params?.args === "string"
|
|
446
|
-
? params.args
|
|
447
|
-
: "";
|
|
448
|
-
|
|
449
|
-
api.logger.info(`[mfa-auth] Extracted command from ${toolName}: ${command}`);
|
|
450
|
-
|
|
451
|
-
if (!command) {
|
|
452
|
-
api.logger.info(`[mfa-auth] No command found in params, allowing`);
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const { isSensitive, preview } = checkSensitiveOperation(command);
|
|
457
|
-
if (!isSensitive) {
|
|
458
|
-
api.logger.info(`[mfa-auth] Command is not sensitive, allowing`);
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const sessionKey = ctx.sessionKey || "";
|
|
463
|
-
const parsed = parseSessionContextFromKey(sessionKey);
|
|
464
|
-
const userId = isWebchatChannel(parsed.channel)
|
|
465
|
-
? parsed.to || sessionKey || "unknown"
|
|
466
|
-
: sessionKey || "unknown";
|
|
467
|
-
|
|
468
|
-
if (authManager.isUserVerifiedForSensitiveOps(userId)) {
|
|
469
|
-
api.logger.info(`[mfa-auth] User ${userId} is verified for sensitive ops, allowing`);
|
|
470
|
-
|
|
471
|
-
const notificationInfo = authManager.checkAndConsumeNotification(userId);
|
|
472
|
-
if (notificationInfo) {
|
|
473
|
-
const parsedChannel = parsed.channel;
|
|
474
|
-
const parsedAccountId = parsed.accountId;
|
|
475
|
-
const parsedTo = parsed.to;
|
|
476
|
-
|
|
477
|
-
const targetSessionKey =
|
|
478
|
-
isWebchatChannel(parsedChannel) ? sessionKey || userId : sessionKey;
|
|
479
|
-
|
|
480
|
-
sendAuthMessage(
|
|
481
|
-
parsedChannel,
|
|
482
|
-
parsedAccountId,
|
|
483
|
-
parsedTo || userId,
|
|
484
|
-
"\u2705 \u4e8c\u6b21\u8ba4\u8bc1\u6210\u529f\uff0c\u8bf7\u91cd\u65b0\u53d1\u9001\u4e4b\u524d\u7684\u547d\u4ee4\u6216\u56de\u590d\u201c\u786e\u8ba4\u201d\u3002",
|
|
485
|
-
userId,
|
|
486
|
-
targetSessionKey,
|
|
487
|
-
).catch((err) =>
|
|
488
|
-
api.logger.error(`[mfa-auth] Failed to send success notification: ${err}`),
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
api.logger.info(`[mfa-auth] User ${userId} is NOT verified for sensitive ops.`);
|
|
496
|
-
|
|
497
|
-
const parsedChannel = parsed.channel;
|
|
498
|
-
const parsedAccountId = parsed.accountId;
|
|
499
|
-
const parsedTo = parsed.to;
|
|
500
|
-
|
|
501
|
-
api.logger.info(
|
|
502
|
-
`[mfa-auth] Parsed from sessionKey: channel=${parsedChannel}, accountId=${parsedAccountId}, to=${parsedTo}`,
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
const session = await authManager.generateSession(userId, {
|
|
506
|
-
sessionKey,
|
|
507
|
-
senderId: userId,
|
|
508
|
-
commandBody: command,
|
|
509
|
-
channel: parsedChannel,
|
|
510
|
-
to: parsedTo,
|
|
511
|
-
accountId: parsedAccountId,
|
|
512
|
-
toolName,
|
|
513
|
-
toolParams: params,
|
|
514
|
-
timestamp: Date.now(),
|
|
515
|
-
triggerType: "sensitive_operation",
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
if (!session) {
|
|
519
|
-
api.logger.error(`[mfa-auth] Failed to generate session for user ${userId}`);
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { authManager } from "./src/auth-manager.js";
|
|
3
|
+
import { config } from "./src/config.js";
|
|
4
|
+
import { dabbyClient } from "./src/dabby-client.js";
|
|
5
|
+
import { NotificationService } from "./src/notification-service.js";
|
|
6
|
+
import { qrCodeAuthProvider } from "./src/providers/qr-code.js";
|
|
7
|
+
import { setNotifyCallback } from "./src/server.js";
|
|
8
|
+
import type { AuthSession } from "./src/types.js";
|
|
9
|
+
|
|
10
|
+
const notificationService = NotificationService.getInstance();
|
|
11
|
+
const pendingAuthUsers = new Set<string>();
|
|
12
|
+
|
|
13
|
+
async function sendAuthMessage(
|
|
14
|
+
channel: string | undefined,
|
|
15
|
+
accountId: string | undefined,
|
|
16
|
+
to: string,
|
|
17
|
+
message: string,
|
|
18
|
+
userId: string,
|
|
19
|
+
overrideSessionKey?: string,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const session: AuthSession = {
|
|
22
|
+
userId,
|
|
23
|
+
sessionId: "notification",
|
|
24
|
+
authMethod: "qr-code",
|
|
25
|
+
timestamp: Date.now(),
|
|
26
|
+
originalContext: {
|
|
27
|
+
sessionKey: overrideSessionKey || `${channel || "web"}:${accountId || ""}:${userId}`,
|
|
28
|
+
senderId: userId,
|
|
29
|
+
commandBody: "",
|
|
30
|
+
channel: channel || "web",
|
|
31
|
+
accountId: accountId || "",
|
|
32
|
+
to,
|
|
33
|
+
toolName: "notification",
|
|
34
|
+
toolParams: {},
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
triggerType: "sensitive_operation",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
await notificationService.sendAuthNotification(session, message);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default function register(api: OpenClawPluginApi) {
|
|
44
|
+
console.log("[mfa-auth] Plugin registration started");
|
|
45
|
+
authManager.registerProvider(qrCodeAuthProvider);
|
|
46
|
+
|
|
47
|
+
notificationService.setConfig(api.config);
|
|
48
|
+
|
|
49
|
+
setNotifyCallback(async (session: AuthSession) => {
|
|
50
|
+
api.logger.info(`[mfa-auth] User ${session.userId} verified`);
|
|
51
|
+
|
|
52
|
+
if (!config.enableAuthNotification) {
|
|
53
|
+
api.logger.info(`[mfa-auth] Auth notification disabled, skipping message send.`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const commandBody = session.originalContext.commandBody;
|
|
59
|
+
const triggerType = session.originalContext.triggerType || "sensitive_operation";
|
|
60
|
+
|
|
61
|
+
const isFirstMessageAuth = triggerType === "first_message";
|
|
62
|
+
const isReauth = commandBody.trim() === "/reauth";
|
|
63
|
+
|
|
64
|
+
let messageText = "";
|
|
65
|
+
if (isFirstMessageAuth) {
|
|
66
|
+
messageText = isReauth
|
|
67
|
+
? `🎉 重新认证成功!请重新发送消息以继续对话。`
|
|
68
|
+
: `🎉 首次认证成功!请重新发送消息以继续对话。`;
|
|
69
|
+
} else {
|
|
70
|
+
messageText = `✅ 二次认证成功!\n\n请回到聊天窗口,重新发送之前的命令(或回复'确认')即可执行。`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const channel = session.originalContext.channel;
|
|
74
|
+
const sessionKey =
|
|
75
|
+
session.originalContext.sessionKey ||
|
|
76
|
+
`${channel}:${session.originalContext.accountId || ""}:${session.userId}`;
|
|
77
|
+
|
|
78
|
+
api.logger.info(`[mfa-auth] Sending notification to session: ${sessionKey}`);
|
|
79
|
+
|
|
80
|
+
await sendAuthMessage(
|
|
81
|
+
channel,
|
|
82
|
+
session.originalContext.accountId,
|
|
83
|
+
session.originalContext.to || session.userId,
|
|
84
|
+
messageText,
|
|
85
|
+
session.userId,
|
|
86
|
+
sessionKey,
|
|
87
|
+
);
|
|
88
|
+
api.logger.info(`[mfa-auth] Notification sent to user ${session.userId}`);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
api.logger.error(`[mfa-auth] Failed in notify callback: ${String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
api.on("message_sending", async (event, ctx) => {
|
|
95
|
+
const userId = event.to || ctx.conversationId || "unknown";
|
|
96
|
+
|
|
97
|
+
if (pendingAuthUsers.has(userId)) {
|
|
98
|
+
const session = authManager.getLatestSessionByUserId(userId);
|
|
99
|
+
const metadata = session?.metadata as Record<string, unknown> | undefined;
|
|
100
|
+
|
|
101
|
+
if (metadata?.qrCodeUrl) {
|
|
102
|
+
pendingAuthUsers.delete(userId);
|
|
103
|
+
|
|
104
|
+
let messageText = "";
|
|
105
|
+
if (metadata.triggerType === "first_message") {
|
|
106
|
+
const isReauth = metadata.isReauth === true;
|
|
107
|
+
messageText = isReauth
|
|
108
|
+
? `🔐 重新认证\n\n📱 请点击以下链接完成扫码认证:\n${metadata.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟`
|
|
109
|
+
: `🔐 首次对话需要进行认证\n\n为了您的账户安全,首次对话前需要完成身份验证。\n\n📱 请点击以下链接完成扫码认证:\n${metadata.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟`;
|
|
110
|
+
} else if (metadata.triggerType === "sensitive_operation") {
|
|
111
|
+
messageText = `🔐 该操作需要二次认证\n\n检测到敏感操作: ${metadata.commandPreview}\n\n📱 请点击以下链接完成扫码认证:\n${metadata.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟\n\n验证成功后,请回复"确认"或者重新发送之前的命令以继续执行。`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { content: messageText };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
api.on("before_tool_call", async (event, ctx) => {
|
|
120
|
+
await authManager.ensureInitialized();
|
|
121
|
+
if (!config.requireAuthOnSensitiveOperation) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { toolName, params } = event;
|
|
126
|
+
|
|
127
|
+
api.logger.info(`[mfa-auth] Tool call detected: ${toolName}`);
|
|
128
|
+
|
|
129
|
+
const sensitiveTools = ["bash", "exec", "runCommand", "command", "process"];
|
|
130
|
+
if (!sensitiveTools.includes(toolName)) {
|
|
131
|
+
api.logger.info(`[mfa-auth] Tool ${toolName} is not in sensitive list, allowing`);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const command =
|
|
136
|
+
typeof params?.command === "string"
|
|
137
|
+
? params.command
|
|
138
|
+
: typeof params?.cmd === "string"
|
|
139
|
+
? params.cmd
|
|
140
|
+
: typeof params?.input === "string"
|
|
141
|
+
? params.input
|
|
142
|
+
: typeof params?.args === "string"
|
|
143
|
+
? params.args
|
|
144
|
+
: "";
|
|
145
|
+
|
|
146
|
+
api.logger.info(`[mfa-auth] Extracted command from ${toolName}: ${command}`);
|
|
147
|
+
|
|
148
|
+
if (!command) {
|
|
149
|
+
api.logger.info(`[mfa-auth] No command found in params, allowing`);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { isSensitive, preview } = checkSensitiveOperation(command);
|
|
154
|
+
if (!isSensitive) {
|
|
155
|
+
api.logger.info(`[mfa-auth] Command is not sensitive, allowing`);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const userId = ctx.sessionKey || "unknown";
|
|
160
|
+
|
|
161
|
+
if (authManager.isUserVerifiedForSensitiveOps(userId)) {
|
|
162
|
+
api.logger.info(`[mfa-auth] User ${userId} is verified for sensitive ops, allowing`);
|
|
163
|
+
|
|
164
|
+
const notificationInfo = authManager.checkAndConsumeNotification(userId);
|
|
165
|
+
if (notificationInfo) {
|
|
166
|
+
const sessionKey = ctx.sessionKey || "";
|
|
167
|
+
const sessionKeyParts = sessionKey.split(":").filter(Boolean);
|
|
168
|
+
const parsedChannel = sessionKeyParts[2] || undefined;
|
|
169
|
+
let parsedAccountId = sessionKeyParts[3] || undefined;
|
|
170
|
+
const parsedTo = sessionKeyParts[sessionKeyParts.length - 1] || undefined;
|
|
171
|
+
|
|
172
|
+
if (parsedAccountId === "direct" || parsedAccountId === "group") {
|
|
173
|
+
parsedAccountId = undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const targetSessionKey =
|
|
177
|
+
parsedChannel === "webchat" || parsedChannel === "web" ? userId : sessionKey;
|
|
178
|
+
|
|
179
|
+
sendAuthMessage(
|
|
180
|
+
parsedChannel,
|
|
181
|
+
parsedAccountId,
|
|
182
|
+
parsedTo || userId,
|
|
183
|
+
"✅ 二次认证成功,请重新发送之前的命令(或回复'确认')即可执行。",
|
|
184
|
+
userId,
|
|
185
|
+
targetSessionKey,
|
|
186
|
+
).catch((err) =>
|
|
187
|
+
api.logger.error(`[mfa-auth] Failed to send success notification: ${err}`),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
520
191
|
return undefined;
|
|
521
192
|
}
|
|
522
193
|
|
|
194
|
+
api.logger.info(`[mfa-auth] User ${userId} is NOT verified for sensitive ops.`);
|
|
195
|
+
|
|
196
|
+
const sessionKey = ctx.sessionKey || "";
|
|
197
|
+
const sessionKeyParts = sessionKey.split(":").filter(Boolean);
|
|
198
|
+
|
|
199
|
+
const parsedChannel = sessionKeyParts[2] || undefined;
|
|
200
|
+
let parsedAccountId = sessionKeyParts[3] || undefined;
|
|
201
|
+
const parsedTo = sessionKeyParts[sessionKeyParts.length - 1] || undefined;
|
|
202
|
+
|
|
203
|
+
// Fix: If accountId is "direct" or "group", it's actually the peerKind, not an accountId.
|
|
204
|
+
// This happens when the sessionKey omits the accountId (using default account).
|
|
205
|
+
if (parsedAccountId === "direct" || parsedAccountId === "group") {
|
|
206
|
+
parsedAccountId = undefined;
|
|
207
|
+
}
|
|
208
|
+
|
|
523
209
|
api.logger.info(
|
|
524
|
-
`[mfa-auth]
|
|
210
|
+
`[mfa-auth] Parsed from sessionKey: channel=${parsedChannel}, accountId=${parsedAccountId}, to=${parsedTo}`,
|
|
525
211
|
);
|
|
526
212
|
|
|
213
|
+
const session = await authManager.generateSession(userId, {
|
|
214
|
+
sessionKey,
|
|
215
|
+
senderId: userId,
|
|
216
|
+
commandBody: command,
|
|
217
|
+
channel: parsedChannel,
|
|
218
|
+
to: parsedTo,
|
|
219
|
+
accountId: parsedAccountId,
|
|
220
|
+
toolName,
|
|
221
|
+
toolParams: params,
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
triggerType: "sensitive_operation",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!session) {
|
|
227
|
+
api.logger.error(`[mfa-auth] Failed to generate session for user ${userId}`);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
527
231
|
api.logger.info(`[mfa-auth] Blocking sensitive tool call: ${toolName} from ${userId}`);
|
|
528
|
-
|
|
529
|
-
// For webchat, use userId as sessionKey instead of agent:main:<userId>
|
|
530
|
-
if (
|
|
531
|
-
const sessionKeyForWebchat =
|
|
532
|
-
const authChallengeText = `检测到敏感操作,需二次认证。\n\n操作内容:${preview}\n\n请点击下方链接完成扫码认证:\n${session.qrCodeUrl}\n\n认证有效期:${Math.floor(config.timeout / 60000)} 分钟。\n\n认证完成后,请重新发送上一条命令或回复“确认”。`;
|
|
232
|
+
|
|
233
|
+
// For webchat, use userId as sessionKey instead of agent:main:<userId>
|
|
234
|
+
if (parsedChannel === "webchat" || parsedChannel === "web") {
|
|
235
|
+
const sessionKeyForWebchat = userId;
|
|
533
236
|
|
|
534
237
|
try {
|
|
535
238
|
await sendAuthMessage(
|
|
536
239
|
parsedChannel,
|
|
537
240
|
parsedAccountId,
|
|
538
241
|
parsedTo || userId,
|
|
539
|
-
|
|
242
|
+
`🔐 该操作需要二次认证\n\n检测到敏感操作: ${preview}\n\n📱 请点击以下链接完成扫码认证:\n${session.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟\n\n验证成功后,请回复"确认"或者重新发送之前的命令以继续执行。`,
|
|
540
243
|
userId,
|
|
541
244
|
sessionKeyForWebchat,
|
|
542
245
|
);
|
|
543
|
-
api.logger.info(
|
|
544
|
-
`[mfa-auth] Sent sensitive operation auth notification to webchat: sessionKey=${sessionKeyForWebchat}`,
|
|
545
|
-
);
|
|
546
|
-
} catch (error) {
|
|
547
|
-
api.logger.error(
|
|
548
|
-
`[mfa-auth] Failed to send webchat sensitive auth notification: ${String(error)}`,
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
// Also add to pending users as fallback
|
|
552
|
-
|
|
553
|
-
authManager.setSessionMetadata(session.sessionId, {
|
|
554
|
-
qrCodeUrl: session.qrCodeUrl,
|
|
555
|
-
triggerType: "sensitive_operation",
|
|
556
|
-
commandPreview: preview,
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
startPollingForAuth(api, userId, session.sessionId, {
|
|
560
|
-
triggerType: "sensitive_operation",
|
|
561
|
-
isReauth: false,
|
|
562
|
-
channel: parsedChannel,
|
|
563
|
-
accountId: parsedAccountId,
|
|
564
|
-
to: parsedTo,
|
|
565
|
-
sessionKey: sessionKeyForWebchat,
|
|
566
|
-
});
|
|
567
|
-
|
|
246
|
+
api.logger.info(
|
|
247
|
+
`[mfa-auth] Sent sensitive operation auth notification to webchat: sessionKey=${sessionKeyForWebchat}`,
|
|
248
|
+
);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
api.logger.error(
|
|
251
|
+
`[mfa-auth] Failed to send webchat sensitive auth notification: ${String(error)}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
// Also add to pending users as fallback
|
|
255
|
+
pendingAuthUsers.add(userId);
|
|
256
|
+
authManager.setSessionMetadata(session.sessionId, {
|
|
257
|
+
qrCodeUrl: session.qrCodeUrl,
|
|
258
|
+
triggerType: "sensitive_operation",
|
|
259
|
+
commandPreview: preview,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
263
|
+
triggerType: "sensitive_operation",
|
|
264
|
+
isReauth: false,
|
|
265
|
+
channel: parsedChannel,
|
|
266
|
+
accountId: parsedAccountId,
|
|
267
|
+
to: parsedTo,
|
|
268
|
+
sessionKey: sessionKeyForWebchat,
|
|
269
|
+
});
|
|
270
|
+
|
|
568
271
|
return {
|
|
569
272
|
block: true,
|
|
570
|
-
blockReason:
|
|
273
|
+
blockReason: `🔐 该操作需要二次认证`,
|
|
571
274
|
};
|
|
572
275
|
}
|
|
573
276
|
|
|
574
|
-
const authChallengeText = `检测到敏感操作,需二次认证。\n\n操作内容:${preview}\n\n请点击下方链接完成扫码认证:\n${session.qrCodeUrl}\n\n认证有效期:${Math.floor(config.timeout / 60000)} 分钟。\n\n认证完成后,请重新发送上一条命令或回复“确认”。`;
|
|
575
|
-
|
|
576
277
|
if (parsedChannel && parsedChannel !== "web") {
|
|
577
278
|
if (parsedChannel !== "feishu") {
|
|
578
279
|
api.logger.warn(
|
|
579
280
|
`[mfa-auth] Channel ${parsedChannel} not supported, skipping auth notification`,
|
|
580
281
|
);
|
|
581
282
|
} else {
|
|
283
|
+
const messageText = `🔐 该操作需要二次认证\n\n检测到敏感操作: ${preview}\n\n📱 请点击以下链接完成扫码认证:\n${session.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟\n\n验证成功后,请回复"确认"或者重新发送之前的命令以继续执行。`;
|
|
284
|
+
|
|
582
285
|
await sendAuthMessage(
|
|
583
286
|
parsedChannel,
|
|
584
287
|
parsedAccountId,
|
|
585
288
|
parsedTo || userId,
|
|
586
|
-
|
|
289
|
+
messageText,
|
|
587
290
|
userId,
|
|
588
291
|
);
|
|
589
|
-
|
|
590
|
-
startPollingForAuth(api, userId, session.sessionId, {
|
|
591
|
-
triggerType: "sensitive_operation",
|
|
592
|
-
isReauth: false,
|
|
593
|
-
channel: parsedChannel,
|
|
594
|
-
accountId: parsedAccountId,
|
|
595
|
-
to: parsedTo,
|
|
596
|
-
sessionKey: ctx.sessionKey || "",
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
authManager.registerPendingExecution(userId, session.sessionId);
|
|
602
|
-
|
|
292
|
+
|
|
293
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
294
|
+
triggerType: "sensitive_operation",
|
|
295
|
+
isReauth: false,
|
|
296
|
+
channel: parsedChannel,
|
|
297
|
+
accountId: parsedAccountId,
|
|
298
|
+
to: parsedTo,
|
|
299
|
+
sessionKey: ctx.sessionKey || "",
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
authManager.registerPendingExecution(userId, session.sessionId);
|
|
305
|
+
|
|
603
306
|
return {
|
|
604
307
|
block: true,
|
|
605
|
-
blockReason:
|
|
308
|
+
blockReason: `🔐 该操作需要二次认证`,
|
|
606
309
|
};
|
|
607
310
|
});
|
|
608
|
-
|
|
609
|
-
api.on("message_received", async (event, ctx) => {
|
|
610
|
-
await authManager.ensureInitialized();
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
userId
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
channel
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
sendAuthMessage(
|
|
1012
|
-
context.channel,
|
|
1013
|
-
context.accountId,
|
|
1014
|
-
context.to || userId,
|
|
1015
|
-
messageText,
|
|
1016
|
-
userId,
|
|
1017
|
-
context.sessionKey,
|
|
1018
|
-
).catch((err) =>
|
|
1019
|
-
api.logger.error(`[mfa-auth] Failed to send success notification from polling: ${err}`),
|
|
1020
|
-
);
|
|
1021
|
-
}
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
const session = authManager.getSession(sessionId);
|
|
1026
|
-
if (!session) {
|
|
1027
|
-
clearInterval(pollInterval);
|
|
1028
|
-
api.logger.info(
|
|
1029
|
-
`[mfa-auth] Polling stopped: session ${sessionId} not found and user not verified`,
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
}, 2000);
|
|
1033
|
-
|
|
1034
|
-
setTimeout(() => {
|
|
1035
|
-
clearInterval(pollInterval);
|
|
1036
|
-
}, config.timeout + 10000);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
function checkSensitiveOperation(text: string): { isSensitive: boolean; preview: string } {
|
|
1040
|
-
const lowerText = text.toLowerCase();
|
|
1041
|
-
|
|
1042
|
-
if (config.debug) {
|
|
1043
|
-
console.log(`[mfa-auth] Checking sensitive keywords for: ${text}`);
|
|
1044
|
-
console.log(
|
|
1045
|
-
`[mfa-auth] Sensitive keywords configured: ${JSON.stringify(config.sensitiveKeywords)}`,
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
for (const keyword of config.sensitiveKeywords) {
|
|
1050
|
-
if (lowerText.includes(keyword.toLowerCase())) {
|
|
1051
|
-
const preview = text;
|
|
1052
|
-
if (config.debug) {
|
|
1053
|
-
console.log(`[mfa-auth] Sensitive keyword matched: ${keyword}`);
|
|
1054
|
-
}
|
|
1055
|
-
return { isSensitive: true, preview };
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
if (config.debug) {
|
|
1060
|
-
console.log(`[mfa-auth] No sensitive keyword matched`);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
return { isSensitive: false, preview: "" };
|
|
1064
|
-
}
|
|
1065
|
-
|
|
311
|
+
|
|
312
|
+
api.on("message_received", async (event, ctx) => {
|
|
313
|
+
await authManager.ensureInitialized();
|
|
314
|
+
|
|
315
|
+
api.logger.info(
|
|
316
|
+
`[mfa-auth] First message auth check: config.requireAuthOnFirstMessage=${config.requireAuthOnFirstMessage}`,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (!config.requireAuthOnFirstMessage) {
|
|
320
|
+
api.logger.warn(`[mfa-auth] First message auth is disabled in config, skipping.`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const content = event.content || "";
|
|
325
|
+
const isReauthCommand = content.trim() === "/reauth";
|
|
326
|
+
|
|
327
|
+
if (isReauthCommand) {
|
|
328
|
+
api.logger.info(`[mfa-auth] /reauth command detected, skipping first message auth check`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const userId = event.from || ctx.conversationId || "unknown";
|
|
333
|
+
|
|
334
|
+
if (authManager.isUserVerifiedForFirstMessage(userId)) {
|
|
335
|
+
api.logger.info(`[mfa-auth] User ${userId} already verified for first message`);
|
|
336
|
+
|
|
337
|
+
const notificationInfo = authManager.checkAndConsumeNotification(userId);
|
|
338
|
+
if (notificationInfo) {
|
|
339
|
+
const parsedChannel = ctx.channelId;
|
|
340
|
+
const parsedAccountId = ctx.accountId || "";
|
|
341
|
+
const parsedTo = event.from;
|
|
342
|
+
|
|
343
|
+
let sessionKey = ctx.conversationId;
|
|
344
|
+
if (!sessionKey) {
|
|
345
|
+
if (parsedChannel === "webchat" || parsedChannel === "web") {
|
|
346
|
+
sessionKey = userId;
|
|
347
|
+
} else {
|
|
348
|
+
sessionKey = `${parsedChannel}:${parsedAccountId}:${event.from}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const messageText = notificationInfo.isReauth
|
|
353
|
+
? "✅ 重新认证成功,请继续对话。"
|
|
354
|
+
: "✅ 首次认证成功,请继续对话。";
|
|
355
|
+
|
|
356
|
+
sendAuthMessage(
|
|
357
|
+
parsedChannel,
|
|
358
|
+
parsedAccountId,
|
|
359
|
+
parsedTo || userId,
|
|
360
|
+
messageText,
|
|
361
|
+
userId,
|
|
362
|
+
sessionKey,
|
|
363
|
+
).catch((err) =>
|
|
364
|
+
api.logger.error(`[mfa-auth] Failed to send success notification: ${err}`),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
api.logger.info(`[mfa-auth] First message from unauthenticated user ${userId}, requiring auth`);
|
|
372
|
+
api.logger.info(
|
|
373
|
+
`[mfa-auth] Debug Context: channelId=${ctx.channelId}, conversationId=${ctx.conversationId}, accountId=${ctx.accountId}, from=${event.from}`,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const parsedChannel = ctx.channelId;
|
|
377
|
+
const parsedAccountId = ctx.accountId || "";
|
|
378
|
+
const parsedTo = event.from;
|
|
379
|
+
|
|
380
|
+
// Use conversationId as sessionKey if available
|
|
381
|
+
// For webchat, try to use userId directly as sessionKey (common pattern)
|
|
382
|
+
let sessionKey = ctx.conversationId;
|
|
383
|
+
if (!sessionKey) {
|
|
384
|
+
if (parsedChannel === "webchat" || parsedChannel === "web") {
|
|
385
|
+
// For webchat, use userId as sessionKey (this is the most common pattern)
|
|
386
|
+
sessionKey = userId;
|
|
387
|
+
api.logger.info(`[mfa-auth] Using webchat sessionKey (userId): ${sessionKey}`);
|
|
388
|
+
} else {
|
|
389
|
+
// Fallback to channel:accountId:from format
|
|
390
|
+
sessionKey = `${parsedChannel}:${parsedAccountId}:${event.from}`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const session = await authManager.generateSession(userId, {
|
|
395
|
+
sessionKey,
|
|
396
|
+
senderId: userId,
|
|
397
|
+
commandBody: event.content || "",
|
|
398
|
+
channel: parsedChannel,
|
|
399
|
+
to: parsedTo,
|
|
400
|
+
accountId: parsedAccountId,
|
|
401
|
+
toolName: "",
|
|
402
|
+
toolParams: {},
|
|
403
|
+
timestamp: Date.now(),
|
|
404
|
+
triggerType: "first_message",
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (!session) {
|
|
408
|
+
api.logger.error(
|
|
409
|
+
`[mfa-auth] Failed to generate first message auth session for user ${userId}`,
|
|
410
|
+
);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
api.logger.info(`[mfa-auth] Blocking first message from ${userId}`);
|
|
415
|
+
|
|
416
|
+
if (parsedChannel === "webchat" || parsedChannel === "web") {
|
|
417
|
+
pendingAuthUsers.add(userId);
|
|
418
|
+
authManager.setSessionMetadata(session.sessionId, {
|
|
419
|
+
qrCodeUrl: session.qrCodeUrl,
|
|
420
|
+
triggerType: "first_message",
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
424
|
+
triggerType: "first_message",
|
|
425
|
+
isReauth: false,
|
|
426
|
+
channel: parsedChannel,
|
|
427
|
+
accountId: parsedAccountId,
|
|
428
|
+
to: parsedTo,
|
|
429
|
+
sessionKey: userId,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (parsedChannel && parsedChannel !== "web") {
|
|
436
|
+
if (parsedChannel !== "feishu") {
|
|
437
|
+
api.logger.warn(
|
|
438
|
+
`[mfa-auth] Channel ${parsedChannel} not supported, skipping auth notification`,
|
|
439
|
+
);
|
|
440
|
+
} else {
|
|
441
|
+
const messageText = `🔐 首次对话需要进行认证\n\n为了您的账户安全,首次对话前需要完成身份验证。\n\n📱 请点击以下链接完成扫码认证:\n${session.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟`;
|
|
442
|
+
|
|
443
|
+
await sendAuthMessage(
|
|
444
|
+
parsedChannel,
|
|
445
|
+
parsedAccountId,
|
|
446
|
+
parsedTo || userId,
|
|
447
|
+
messageText,
|
|
448
|
+
userId,
|
|
449
|
+
sessionKey,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
453
|
+
triggerType: "first_message",
|
|
454
|
+
isReauth: false,
|
|
455
|
+
channel: parsedChannel,
|
|
456
|
+
accountId: parsedAccountId,
|
|
457
|
+
to: parsedTo,
|
|
458
|
+
sessionKey: sessionKey,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
api.registerCommand({
|
|
465
|
+
name: "reauth",
|
|
466
|
+
description: "重新进行首次对话认证",
|
|
467
|
+
acceptsArgs: false,
|
|
468
|
+
requireAuth: false,
|
|
469
|
+
handler: async (ctx) => {
|
|
470
|
+
const userId = ctx.from || ctx.senderId || "unknown";
|
|
471
|
+
api.logger.info(
|
|
472
|
+
`[mfa-auth] /reauth command received. userId=${userId}, ctx.channel=${ctx.channel}, ctx.accountId=${ctx.accountId}, ctx.to=${ctx.to}`,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
authManager.clearFirstMessageAuth(userId);
|
|
476
|
+
|
|
477
|
+
const parsedChannel = ctx.channel;
|
|
478
|
+
const parsedAccountId = ctx.accountId || "";
|
|
479
|
+
const parsedTo = ctx.to;
|
|
480
|
+
|
|
481
|
+
api.logger.info(
|
|
482
|
+
`[mfa-auth] Parsed: channel=${parsedChannel}, accountId=${parsedAccountId}, to=${parsedTo}`,
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
// For webchat, use userId as sessionKey
|
|
486
|
+
const sessionKey =
|
|
487
|
+
parsedChannel === "webchat" || parsedChannel === "web"
|
|
488
|
+
? userId
|
|
489
|
+
: `${parsedChannel}:${parsedAccountId}:${userId}`;
|
|
490
|
+
|
|
491
|
+
api.logger.info(`[mfa-auth] Using sessionKey for reauth: ${sessionKey}`);
|
|
492
|
+
|
|
493
|
+
const session = await authManager.generateSession(userId, {
|
|
494
|
+
sessionKey,
|
|
495
|
+
senderId: userId,
|
|
496
|
+
commandBody: "/reauth",
|
|
497
|
+
channel: parsedChannel,
|
|
498
|
+
to: parsedTo,
|
|
499
|
+
accountId: parsedAccountId,
|
|
500
|
+
toolName: "",
|
|
501
|
+
toolParams: {},
|
|
502
|
+
timestamp: Date.now(),
|
|
503
|
+
triggerType: "first_message",
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (!session) {
|
|
507
|
+
api.logger.error(`[mfa-auth] Failed to generate reauth session for user ${userId}`);
|
|
508
|
+
return { text: "❌ 认证会话创建失败,请稍后重试。" };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
api.logger.info(
|
|
512
|
+
`[mfa-auth] Reauth requested by user ${userId}, session=${session.sessionId}`,
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
const messageText = `🔐 重新认证\n\n📱 请点击以下链接完成扫码认证:\n${session.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟`;
|
|
516
|
+
|
|
517
|
+
// Use sendAuthMessage to ensure consistent delivery via WebSocket for WebChat
|
|
518
|
+
// This will use the new robust session resolution logic
|
|
519
|
+
if (parsedChannel === "webchat" || parsedChannel === "web") {
|
|
520
|
+
try {
|
|
521
|
+
pendingAuthUsers.add(userId);
|
|
522
|
+
authManager.setSessionMetadata(session.sessionId, {
|
|
523
|
+
qrCodeUrl: session.qrCodeUrl,
|
|
524
|
+
triggerType: "first_message",
|
|
525
|
+
isReauth: true,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
await sendAuthMessage(
|
|
529
|
+
parsedChannel,
|
|
530
|
+
parsedAccountId,
|
|
531
|
+
parsedTo || userId,
|
|
532
|
+
messageText,
|
|
533
|
+
userId,
|
|
534
|
+
sessionKey,
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
538
|
+
triggerType: "first_message",
|
|
539
|
+
isReauth: true,
|
|
540
|
+
channel: parsedChannel,
|
|
541
|
+
accountId: parsedAccountId,
|
|
542
|
+
to: parsedTo,
|
|
543
|
+
sessionKey: sessionKey,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
return { text: "� 认证链接已发送,请查看最新消息。" };
|
|
547
|
+
} catch (error) {
|
|
548
|
+
api.logger.error(`[mfa-auth] Failed to send reauth link to webchat: ${String(error)}`);
|
|
549
|
+
// Fallback to returning text directly if push fails, though this might be less reliable if session context is lost
|
|
550
|
+
return { text: messageText };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!parsedChannel || parsedChannel !== "feishu") {
|
|
555
|
+
api.logger.warn(`[mfa-auth] Channel ${parsedChannel} not supported`);
|
|
556
|
+
return { text: "❌ 当前渠道不支持认证。" };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
api.logger.info(
|
|
561
|
+
`[mfa-auth] Sending reauth notification: channel=${parsedChannel}, to=${parsedTo}, accountId=${parsedAccountId}`,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
await sendAuthMessage(
|
|
565
|
+
parsedChannel,
|
|
566
|
+
parsedAccountId,
|
|
567
|
+
parsedTo || userId,
|
|
568
|
+
`🔐 重新认证\n\n📱 请点击以下链接完成扫码认证:\n${session.qrCodeUrl}\n\n验证有效期: ${Math.floor(config.timeout / 60000)} 分钟`,
|
|
569
|
+
userId,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
startPollingForAuth(api, userId, session.sessionId, {
|
|
573
|
+
triggerType: "first_message",
|
|
574
|
+
isReauth: true,
|
|
575
|
+
channel: parsedChannel,
|
|
576
|
+
accountId: parsedAccountId,
|
|
577
|
+
to: parsedTo,
|
|
578
|
+
sessionKey: sessionKey,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
api.logger.info(`[mfa-auth] Reauth notification sent successfully`);
|
|
582
|
+
return { text: "📱 认证链接已发送,请查收。" };
|
|
583
|
+
} catch (error) {
|
|
584
|
+
api.logger.error(`[mfa-auth] Failed to send reauth notification: ${String(error)}`);
|
|
585
|
+
return { text: "❌ 认证链接发送失败,请稍后重试。" };
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
api.logger.info("mfa-auth plugin loaded");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function startPollingForAuth(
|
|
594
|
+
api: OpenClawPluginApi,
|
|
595
|
+
userId: string,
|
|
596
|
+
sessionId: string,
|
|
597
|
+
context: {
|
|
598
|
+
triggerType: string;
|
|
599
|
+
isReauth: boolean;
|
|
600
|
+
channel?: string;
|
|
601
|
+
accountId?: string;
|
|
602
|
+
to?: string;
|
|
603
|
+
sessionKey?: string;
|
|
604
|
+
},
|
|
605
|
+
) {
|
|
606
|
+
api.logger.info(
|
|
607
|
+
`[mfa-auth] Starting polling for auth status: userId=${userId}, sessionId=${sessionId}`,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
const pollInterval = setInterval(async () => {
|
|
611
|
+
let isVerified = false;
|
|
612
|
+
if (context.triggerType === "first_message") {
|
|
613
|
+
isVerified = authManager.isUserVerifiedForFirstMessage(userId);
|
|
614
|
+
} else {
|
|
615
|
+
isVerified = authManager.isUserVerifiedForSensitiveOps(userId);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (!isVerified) {
|
|
619
|
+
const session = authManager.getSession(sessionId);
|
|
620
|
+
if (session && session.certToken) {
|
|
621
|
+
try {
|
|
622
|
+
const authResult = await dabbyClient.getAuthResult(session.certToken);
|
|
623
|
+
if (authResult.status === "verified") {
|
|
624
|
+
api.logger.info(`[mfa-auth] Auth verification successful for session ${sessionId}`);
|
|
625
|
+
authManager.markUserVerified(
|
|
626
|
+
userId,
|
|
627
|
+
context.triggerType === "first_message" ? "first_message" : "sensitive_operation",
|
|
628
|
+
context.isReauth,
|
|
629
|
+
);
|
|
630
|
+
isVerified = true;
|
|
631
|
+
} else if (authResult.status === "failed" || authResult.status === "expired") {
|
|
632
|
+
api.logger.warn(
|
|
633
|
+
`[mfa-auth] Auth failed or expired for session ${sessionId}: ${authResult.error}`,
|
|
634
|
+
);
|
|
635
|
+
clearInterval(pollInterval);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
} catch (error) {
|
|
639
|
+
api.logger.error(
|
|
640
|
+
`[mfa-auth] Failed to check auth status for session ${sessionId}: ${String(error)}`,
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (isVerified) {
|
|
647
|
+
clearInterval(pollInterval);
|
|
648
|
+
|
|
649
|
+
const notificationInfo = authManager.checkAndConsumeNotification(userId);
|
|
650
|
+
if (notificationInfo) {
|
|
651
|
+
api.logger.info(`[mfa-auth] Polling detected verification for user ${userId}`);
|
|
652
|
+
|
|
653
|
+
const messageText = notificationInfo.isReauth
|
|
654
|
+
? "✅ 重新认证成功,请继续对话。"
|
|
655
|
+
: notificationInfo.triggerType === "first_message"
|
|
656
|
+
? "✅ 首次认证成功,请继续对话。"
|
|
657
|
+
: "✅ 二次认证成功,请重新发送之前的命令(或回复'确认')即可执行。";
|
|
658
|
+
|
|
659
|
+
sendAuthMessage(
|
|
660
|
+
context.channel,
|
|
661
|
+
context.accountId,
|
|
662
|
+
context.to || userId,
|
|
663
|
+
messageText,
|
|
664
|
+
userId,
|
|
665
|
+
context.sessionKey,
|
|
666
|
+
).catch((err) =>
|
|
667
|
+
api.logger.error(`[mfa-auth] Failed to send success notification from polling: ${err}`),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const session = authManager.getSession(sessionId);
|
|
674
|
+
if (!session) {
|
|
675
|
+
clearInterval(pollInterval);
|
|
676
|
+
api.logger.info(
|
|
677
|
+
`[mfa-auth] Polling stopped: session ${sessionId} not found and user not verified`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}, 2000);
|
|
681
|
+
|
|
682
|
+
setTimeout(() => {
|
|
683
|
+
clearInterval(pollInterval);
|
|
684
|
+
}, config.timeout + 10000);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function checkSensitiveOperation(text: string): { isSensitive: boolean; preview: string } {
|
|
688
|
+
const lowerText = text.toLowerCase();
|
|
689
|
+
|
|
690
|
+
if (config.debug) {
|
|
691
|
+
console.log(`[mfa-auth] Checking sensitive keywords for: ${text}`);
|
|
692
|
+
console.log(
|
|
693
|
+
`[mfa-auth] Sensitive keywords configured: ${JSON.stringify(config.sensitiveKeywords)}`,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
for (const keyword of config.sensitiveKeywords) {
|
|
698
|
+
if (lowerText.includes(keyword.toLowerCase())) {
|
|
699
|
+
const preview = text;
|
|
700
|
+
if (config.debug) {
|
|
701
|
+
console.log(`[mfa-auth] Sensitive keyword matched: ${keyword}`);
|
|
702
|
+
}
|
|
703
|
+
return { isSensitive: true, preview };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (config.debug) {
|
|
708
|
+
console.log(`[mfa-auth] No sensitive keyword matched`);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return { isSensitive: false, preview: "" };
|
|
712
|
+
}
|