@openclaw/crabline 0.1.10 → 0.1.12
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.md +99 -12
- package/dist/src/bin/crabline.js +3 -1
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +5 -1
- package/dist/src/cli/program.js +264 -50
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +39 -3
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -0
- package/dist/src/config/schema.js +163 -35
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.d.ts +1 -0
- package/dist/src/core/errors.js +6 -3
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/http-path.d.ts +1 -0
- package/dist/src/core/http-path.js +14 -0
- package/dist/src/core/http-path.js.map +1 -0
- package/dist/src/core/matcher.d.ts +3 -1
- package/dist/src/core/matcher.js +58 -2
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -1
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +5 -0
- package/dist/src/core/reporters.js +26 -2
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -1
- package/dist/src/core/run.js +178 -66
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/matrix-ids.d.ts +5 -0
- package/dist/src/matrix-ids.js +83 -0
- package/dist/src/matrix-ids.js.map +1 -0
- package/dist/src/openclaw/artifact-generation.d.ts +12 -2
- package/dist/src/openclaw/artifact-generation.js +436 -15
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/matrix.js +21 -4
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +13 -8
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
- package/dist/src/openclaw/bridges/probe-response.js +5 -0
- package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
- package/dist/src/openclaw/bridges/signal.js +15 -6
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +116 -23
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +45 -19
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +32 -16
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +8 -3
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/private-file.d.ts +40 -3
- package/dist/src/openclaw/private-file.js +2070 -95
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/shared.d.ts +1 -0
- package/dist/src/openclaw/shared.js +48 -13
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +9 -0
- package/dist/src/openclaw/smoke-lock.js +696 -187
- package/dist/src/openclaw/smoke-lock.js.map +1 -1
- package/dist/src/openclaw.js +352 -29
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +11 -0
- package/dist/src/platform/process-owned-lock.js +1698 -0
- package/dist/src/platform/process-owned-lock.js.map +1 -0
- package/dist/src/platform/recorder-directory.d.ts +1 -0
- package/dist/src/platform/recorder-directory.js +14 -0
- package/dist/src/platform/recorder-directory.js.map +1 -0
- package/dist/src/platform/windows-acl.d.ts +18 -0
- package/dist/src/platform/windows-acl.js +1367 -0
- package/dist/src/platform/windows-acl.js.map +1 -0
- package/dist/src/platform/windows-lock-root.d.ts +13 -0
- package/dist/src/platform/windows-lock-root.js +98 -0
- package/dist/src/platform/windows-lock-root.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +4 -0
- package/dist/src/providers/builtin/discord.js +52 -9
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
- package/dist/src/providers/builtin/feishu.d.ts +1 -0
- package/dist/src/providers/builtin/feishu.js +96 -26
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +1 -0
- package/dist/src/providers/builtin/googlechat.js +95 -26
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.js +60 -17
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +88 -35
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +2 -2
- package/dist/src/providers/builtin/matrix.js +78 -21
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +11 -7
- package/dist/src/providers/builtin/mattermost.js +138 -24
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +1 -0
- package/dist/src/providers/builtin/msteams.js +118 -38
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.js +4 -4
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.js +1057 -146
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +8 -1
- package/dist/src/providers/builtin/slack.js +327 -24
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +1 -1
- package/dist/src/providers/builtin/telegram.js +55 -14
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
- package/dist/src/providers/builtin/whatsapp.js +157 -29
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.d.ts +5 -2
- package/dist/src/providers/builtin/zalo.js +31 -7
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +1 -1
- package/dist/src/providers/catalog.js +1 -1
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +4 -0
- package/dist/src/providers/local-mock.js +162 -35
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +2 -0
- package/dist/src/providers/native-ids.js +3 -0
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +14 -3
- package/dist/src/providers/recorder.js +532 -42
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +1 -0
- package/dist/src/providers/registry.js +65 -20
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +6 -1
- package/dist/src/providers/signed-jwt.js +241 -50
- package/dist/src/providers/signed-jwt.js.map +1 -1
- package/dist/src/providers/slack-ids.d.ts +1 -0
- package/dist/src/providers/slack-ids.js +5 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +9 -1
- package/dist/src/providers/target-normalizers.js +177 -58
- package/dist/src/providers/target-normalizers.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +3 -0
- package/dist/src/providers/webhook-server.js +268 -47
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/http.d.ts +14 -2
- package/dist/src/servers/http.js +229 -58
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/matrix.d.ts +3 -0
- package/dist/src/servers/matrix.js +268 -110
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +389 -91
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +11 -0
- package/dist/src/servers/recorder.js +1241 -22
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +1 -0
- package/dist/src/servers/signal.js +279 -44
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +15 -0
- package/dist/src/servers/slack.js +259 -51
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram-identity.d.ts +6 -0
- package/dist/src/servers/telegram-identity.js +27 -0
- package/dist/src/servers/telegram-identity.js.map +1 -0
- package/dist/src/servers/telegram.d.ts +17 -6
- package/dist/src/servers/telegram.js +1193 -118
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +12 -4
- package/dist/src/servers/webhook-target.js +226 -28
- package/dist/src/servers/webhook-target.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +1 -0
- package/dist/src/servers/whatsapp-jid.js +9 -5
- package/dist/src/servers/whatsapp-jid.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
- package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
- package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js +3 -3
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +7 -0
- package/dist/src/servers/whatsapp.js +149 -24
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +1 -0
- package/dist/src/servers/zalo.js +193 -82
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +185 -43
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
|
@@ -6,11 +6,21 @@ import { FEISHU_CHAT_ID_RULE, FEISHU_MESSAGE_ID_RULE, getBuiltinTargetCodec, } f
|
|
|
6
6
|
import { authorFromBotFlag, createSecretVerifier, genericMockPayloadWithNativeThread, isRecord, optionalRecord, optionalString, requireNativeInboundId, } from "./native-local-mock.js";
|
|
7
7
|
import { requireExternalWebhookAuthentication } from "./external-webhook-auth.js";
|
|
8
8
|
const FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;
|
|
9
|
+
const FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES = 1024 * 1024;
|
|
10
|
+
const FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH = Math.ceil(FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES / 3) * 4;
|
|
9
11
|
const FEISHU_REPLAY_CACHE_LIMIT = 2_048;
|
|
12
|
+
const FEISHU_MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;
|
|
10
13
|
export function resolveFeishuAdapterConfig(config, env = process.env) {
|
|
14
|
+
const configuredEncryptKey = config.feishu?.encryptKey;
|
|
15
|
+
const encryptKey = configuredEncryptKey ?? env.FEISHU_ENCRYPT_KEY;
|
|
16
|
+
if (encryptKey !== undefined && !encryptKey.trim()) {
|
|
17
|
+
throw new CrablineError(configuredEncryptKey === undefined
|
|
18
|
+
? "FEISHU_ENCRYPT_KEY must not be empty or whitespace-only."
|
|
19
|
+
: "Feishu encryptKey must not be empty or whitespace-only.", { kind: "config" });
|
|
20
|
+
}
|
|
11
21
|
return {
|
|
12
22
|
appId: config.feishu?.appId ?? "local-mock-feishu-app",
|
|
13
|
-
encryptKey
|
|
23
|
+
encryptKey,
|
|
14
24
|
userName: config.feishu?.userName,
|
|
15
25
|
verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,
|
|
16
26
|
};
|
|
@@ -27,8 +37,14 @@ export function handleFeishuWebhookPayload(payload) {
|
|
|
27
37
|
return new Response(null, { status: 200 });
|
|
28
38
|
}
|
|
29
39
|
const messageType = optionalString(message, "message_type");
|
|
40
|
+
if (messageType !== "text") {
|
|
41
|
+
return new Response(null, { status: 200 });
|
|
42
|
+
}
|
|
30
43
|
const text = parseFeishuText(optionalString(message, "content"));
|
|
31
|
-
if (
|
|
44
|
+
if (typeof text !== "string") {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (!text) {
|
|
32
48
|
return new Response(null, { status: 200 });
|
|
33
49
|
}
|
|
34
50
|
}
|
|
@@ -46,6 +62,17 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
|
|
|
46
62
|
return undefined;
|
|
47
63
|
}
|
|
48
64
|
return async (request, rawBody) => {
|
|
65
|
+
if (!isFeishuWebhookBodyWithinLimit(rawBody)) {
|
|
66
|
+
return unauthorizedFeishuWebhook();
|
|
67
|
+
}
|
|
68
|
+
const signaturePresent = request.headers.has("x-lark-signature");
|
|
69
|
+
let signedRequest = false;
|
|
70
|
+
if (signaturePresent) {
|
|
71
|
+
if (!resolved.encryptKey || !verifyFeishuSignature(request, rawBody, resolved.encryptKey)) {
|
|
72
|
+
return unauthorizedFeishuWebhook();
|
|
73
|
+
}
|
|
74
|
+
signedRequest = true;
|
|
75
|
+
}
|
|
49
76
|
let payload;
|
|
50
77
|
try {
|
|
51
78
|
payload = JSON.parse(rawBody);
|
|
@@ -58,7 +85,6 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
|
|
|
58
85
|
}
|
|
59
86
|
const encryptedEnvelope = payload;
|
|
60
87
|
const encrypted = typeof payload.encrypt === "string";
|
|
61
|
-
let signedRequest = false;
|
|
62
88
|
if (encrypted) {
|
|
63
89
|
if (!resolved.encryptKey) {
|
|
64
90
|
return unauthorizedFeishuWebhook();
|
|
@@ -69,11 +95,9 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
|
|
|
69
95
|
catch {
|
|
70
96
|
return unauthorizedFeishuWebhook();
|
|
71
97
|
}
|
|
72
|
-
if (!
|
|
73
|
-
!isFeishuUrlVerification(payload)) {
|
|
98
|
+
if (!signedRequest && !(isFeishuUrlVerification(payload) && validateCallback)) {
|
|
74
99
|
return unauthorizedFeishuWebhook();
|
|
75
100
|
}
|
|
76
|
-
signedRequest = !isFeishuUrlVerification(payload);
|
|
77
101
|
}
|
|
78
102
|
else if (resolved.encryptKey) {
|
|
79
103
|
return unauthorizedFeishuWebhook();
|
|
@@ -94,6 +118,7 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
|
|
|
94
118
|
if (!replayState) {
|
|
95
119
|
return undefined;
|
|
96
120
|
}
|
|
121
|
+
const callbackExpiresAt = (callbackTimestamp ?? now) + FEISHU_MAX_CALLBACK_AGE_MS;
|
|
97
122
|
pruneFeishuReplayCache(replayState.accepted, now);
|
|
98
123
|
while (callbackKeys.length > 0) {
|
|
99
124
|
if (callbackKeys.some((key) => replayState.accepted.has(key))) {
|
|
@@ -103,7 +128,9 @@ function createFeishuWebhookAuthenticatorWithReplay(config, env, runtime, replay
|
|
|
103
128
|
.map((key) => replayState.inFlight.get(key))
|
|
104
129
|
.find((candidate) => candidate !== undefined);
|
|
105
130
|
if (!reservation) {
|
|
106
|
-
reserveFeishuCallback(replayState, callbackKeys)
|
|
131
|
+
if (!reserveFeishuCallback(replayState, callbackKeys, callbackExpiresAt, runtime.replayCacheLimit ?? FEISHU_REPLAY_CACHE_LIMIT)) {
|
|
132
|
+
return feishuReplayCapacityResponse();
|
|
133
|
+
}
|
|
107
134
|
break;
|
|
108
135
|
}
|
|
109
136
|
if (await reservation.promise) {
|
|
@@ -170,12 +197,13 @@ export function normalizeFeishuWebhookPayload(payload) {
|
|
|
170
197
|
if (!isRecord(payload)) {
|
|
171
198
|
throw new CrablineError("Feishu webhook payload must be an object", { kind: "inbound" });
|
|
172
199
|
}
|
|
200
|
+
const recordedPayload = redactFeishuVerificationTokens(payload);
|
|
173
201
|
const event = optionalRecord(payload, "event");
|
|
174
202
|
const message = event ? optionalRecord(event, "message") : undefined;
|
|
175
203
|
if (!message) {
|
|
176
204
|
return genericMockPayloadWithNativeThread({
|
|
177
205
|
channelRule: FEISHU_CHAT_ID_RULE,
|
|
178
|
-
payload,
|
|
206
|
+
payload: recordedPayload,
|
|
179
207
|
threadRule: FEISHU_MESSAGE_ID_RULE,
|
|
180
208
|
});
|
|
181
209
|
}
|
|
@@ -185,13 +213,18 @@ export function normalizeFeishuWebhookPayload(payload) {
|
|
|
185
213
|
const rootId = optionalString(message, "root_id");
|
|
186
214
|
const rawContent = optionalString(message, "content");
|
|
187
215
|
const text = parseFeishuText(rawContent);
|
|
216
|
+
if (text === null) {
|
|
217
|
+
throw new CrablineError("Feishu message.content must be valid JSON", {
|
|
218
|
+
kind: "inbound",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
188
221
|
if (messageType !== "text") {
|
|
189
222
|
throw new CrablineError("Feishu event payload requires message.message_type=text", {
|
|
190
223
|
kind: "inbound",
|
|
191
224
|
});
|
|
192
225
|
}
|
|
193
|
-
if (!chatId || !text) {
|
|
194
|
-
throw new CrablineError("Feishu event payload requires message.chat_id and message.content", {
|
|
226
|
+
if (!chatId || !messageId || !text) {
|
|
227
|
+
throw new CrablineError("Feishu event payload requires message.chat_id, message.message_id, and message.content", {
|
|
195
228
|
kind: "inbound",
|
|
196
229
|
});
|
|
197
230
|
}
|
|
@@ -199,16 +232,25 @@ export function normalizeFeishuWebhookPayload(payload) {
|
|
|
199
232
|
author: authorFromBotFlag(event
|
|
200
233
|
? ["app", "bot"].includes(optionalString(optionalRecord(event, "sender") ?? {}, "sender_type") ?? "")
|
|
201
234
|
: false),
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
: {}),
|
|
205
|
-
raw: payload,
|
|
235
|
+
id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, "Feishu message_id"),
|
|
236
|
+
raw: recordedPayload,
|
|
206
237
|
text,
|
|
207
238
|
threadId: rootId
|
|
208
239
|
? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, "Feishu root_id")
|
|
209
240
|
: requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, "Feishu chat_id"),
|
|
210
241
|
};
|
|
211
242
|
}
|
|
243
|
+
function redactFeishuVerificationTokens(payload) {
|
|
244
|
+
const redacted = { ...payload };
|
|
245
|
+
delete redacted.token;
|
|
246
|
+
const header = optionalRecord(payload, "header");
|
|
247
|
+
if (header) {
|
|
248
|
+
const redactedHeader = { ...header };
|
|
249
|
+
delete redactedHeader.token;
|
|
250
|
+
redacted.header = redactedHeader;
|
|
251
|
+
}
|
|
252
|
+
return redacted;
|
|
253
|
+
}
|
|
212
254
|
function parseFeishuText(content) {
|
|
213
255
|
if (!content) {
|
|
214
256
|
return undefined;
|
|
@@ -221,7 +263,7 @@ function parseFeishuText(content) {
|
|
|
221
263
|
return undefined;
|
|
222
264
|
}
|
|
223
265
|
catch {
|
|
224
|
-
return
|
|
266
|
+
return null;
|
|
225
267
|
}
|
|
226
268
|
}
|
|
227
269
|
function readFeishuSettledEnvelope(payload, rawBody) {
|
|
@@ -239,7 +281,13 @@ export function decryptFeishuWebhookPayload(payload, encryptKey) {
|
|
|
239
281
|
if (!isRecord(payload) || typeof payload.encrypt !== "string") {
|
|
240
282
|
return payload;
|
|
241
283
|
}
|
|
284
|
+
if (payload.encrypt.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH) {
|
|
285
|
+
throw new Error("Feishu encrypted payload is too large.");
|
|
286
|
+
}
|
|
242
287
|
const encrypted = Buffer.from(payload.encrypt, "base64");
|
|
288
|
+
if (encrypted.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES) {
|
|
289
|
+
throw new Error("Feishu encrypted payload is too large.");
|
|
290
|
+
}
|
|
243
291
|
if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {
|
|
244
292
|
throw new Error("Feishu encrypted payload is truncated.");
|
|
245
293
|
}
|
|
@@ -264,6 +312,10 @@ function isFeishuUrlVerification(payload) {
|
|
|
264
312
|
typeof payload.challenge === "string" &&
|
|
265
313
|
payload.challenge.length > 0);
|
|
266
314
|
}
|
|
315
|
+
function isFeishuWebhookBodyWithinLimit(rawBody) {
|
|
316
|
+
return (rawBody.length <= FEISHU_MAX_WEBHOOK_BODY_BYTES &&
|
|
317
|
+
Buffer.byteLength(rawBody, "utf8") <= FEISHU_MAX_WEBHOOK_BODY_BYTES);
|
|
318
|
+
}
|
|
267
319
|
function verifyFeishuSignature(request, rawBody, encryptKey) {
|
|
268
320
|
const timestamp = request.headers.get("x-lark-request-timestamp");
|
|
269
321
|
const nonce = request.headers.get("x-lark-request-nonce");
|
|
@@ -272,7 +324,10 @@ function verifyFeishuSignature(request, rawBody, encryptKey) {
|
|
|
272
324
|
return false;
|
|
273
325
|
}
|
|
274
326
|
const expected = createHash("sha256")
|
|
275
|
-
.update(timestamp
|
|
327
|
+
.update(timestamp)
|
|
328
|
+
.update(nonce)
|
|
329
|
+
.update(encryptKey)
|
|
330
|
+
.update(rawBody)
|
|
276
331
|
.digest();
|
|
277
332
|
return timingSafeEqual(expected, Buffer.from(signature, "hex"));
|
|
278
333
|
}
|
|
@@ -313,18 +368,26 @@ function readFeishuCallbackKeys(payload, encryptedEnvelope) {
|
|
|
313
368
|
const encrypted = isRecord(encryptedEnvelope)
|
|
314
369
|
? optionalString(encryptedEnvelope, "encrypt")
|
|
315
370
|
: undefined;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
371
|
+
if (eventId) {
|
|
372
|
+
return [`event:${eventId}`];
|
|
373
|
+
}
|
|
374
|
+
if (messageId) {
|
|
375
|
+
return [`message:${messageId}`];
|
|
376
|
+
}
|
|
377
|
+
return encrypted ? [`encrypted:${createHash("sha256").update(encrypted).digest("hex")}`] : [];
|
|
321
378
|
}
|
|
322
|
-
function reserveFeishuCallback(replayState, callbackKeys) {
|
|
379
|
+
function reserveFeishuCallback(replayState, callbackKeys, expiresAt, cacheLimit) {
|
|
380
|
+
const occupiedKeys = new Set([...replayState.accepted.keys(), ...replayState.inFlight.keys()]);
|
|
381
|
+
const newKeys = new Set(callbackKeys.filter((key) => !occupiedKeys.has(key)));
|
|
382
|
+
if (occupiedKeys.size + newKeys.size > cacheLimit) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
323
385
|
let resolveReservation;
|
|
324
386
|
const promise = new Promise((resolve) => {
|
|
325
387
|
resolveReservation = resolve;
|
|
326
388
|
});
|
|
327
389
|
const reservation = {
|
|
390
|
+
expiresAt,
|
|
328
391
|
keys: new Set(callbackKeys),
|
|
329
392
|
promise,
|
|
330
393
|
resolve: resolveReservation,
|
|
@@ -332,12 +395,14 @@ function reserveFeishuCallback(replayState, callbackKeys) {
|
|
|
332
395
|
for (const key of callbackKeys) {
|
|
333
396
|
replayState.inFlight.set(key, reservation);
|
|
334
397
|
}
|
|
398
|
+
return true;
|
|
335
399
|
}
|
|
336
400
|
function acceptFeishuCallback(replayState, payload, encryptedEnvelope, now) {
|
|
337
401
|
const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);
|
|
338
402
|
pruneFeishuReplayCache(replayState.accepted, now);
|
|
339
403
|
for (const key of callbackKeys) {
|
|
340
|
-
replayState.
|
|
404
|
+
const reservation = replayState.inFlight.get(key);
|
|
405
|
+
replayState.accepted.set(key, Math.max(replayState.accepted.get(key) ?? 0, reservation?.expiresAt ?? now + FEISHU_MAX_CALLBACK_AGE_MS));
|
|
341
406
|
}
|
|
342
407
|
const reservations = new Set(callbackKeys
|
|
343
408
|
.map((key) => replayState.inFlight.get(key))
|
|
@@ -364,13 +429,18 @@ function releaseFeishuReservation(replayState, reservation, accepted) {
|
|
|
364
429
|
reservation.resolve(accepted);
|
|
365
430
|
}
|
|
366
431
|
function pruneFeishuReplayCache(recentCallbacks, now) {
|
|
367
|
-
for (const [key,
|
|
368
|
-
if (now
|
|
369
|
-
recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT) {
|
|
432
|
+
for (const [key, expiresAt] of recentCallbacks) {
|
|
433
|
+
if (now > expiresAt) {
|
|
370
434
|
recentCallbacks.delete(key);
|
|
371
435
|
}
|
|
372
436
|
}
|
|
373
437
|
}
|
|
438
|
+
function feishuReplayCapacityResponse() {
|
|
439
|
+
return new Response("service unavailable", {
|
|
440
|
+
headers: { "cache-control": "no-store" },
|
|
441
|
+
status: 503,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
374
444
|
function unauthorizedFeishuWebhook() {
|
|
375
445
|
return new Response("unauthorized", { status: 401 });
|
|
376
446
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport type { ProviderAdapter } from \"../types.js\";\nimport {\n FEISHU_CHAT_ID_RULE,\n FEISHU_MESSAGE_ID_RULE,\n getBuiltinTargetCodec,\n} from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n createSecretVerifier,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\ntype FeishuEnvironment = Partial<\n Pick<NodeJS.ProcessEnv, \"FEISHU_ENCRYPT_KEY\" | \"FEISHU_VERIFICATION_TOKEN\">\n>;\n\ntype FeishuAuthRuntime = {\n now?: (() => number) | undefined;\n};\n\ntype FeishuReplayReservation = {\n keys: Set<string>;\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype FeishuReplayState = {\n accepted: Map<string, number>;\n inFlight: Map<string, FeishuReplayReservation>;\n};\n\nconst FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;\nconst FEISHU_REPLAY_CACHE_LIMIT = 2_048;\n\nexport function resolveFeishuAdapterConfig(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n) {\n return {\n appId: config.feishu?.appId ?? \"local-mock-feishu-app\",\n encryptKey: config.feishu?.encryptKey ?? env.FEISHU_ENCRYPT_KEY,\n userName: config.feishu?.userName,\n verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,\n };\n}\n\nexport function handleFeishuWebhookPayload(payload: unknown): Response | undefined {\n if (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\"\n ) {\n return Response.json({ challenge: payload.challenge });\n }\n if (isRecord(payload) && isRecord(payload.event)) {\n const message = optionalRecord(payload.event, \"message\");\n if (!message) {\n return new Response(null, { status: 200 });\n }\n const messageType = optionalString(message, \"message_type\");\n const text = parseFeishuText(optionalString(message, \"content\"));\n if (messageType !== \"text\" || !text) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n}\n\nexport function createFeishuWebhookAuthenticator(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n runtime: FeishuAuthRuntime = {},\n) {\n return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);\n}\n\nfunction createFeishuWebhookAuthenticatorWithReplay(\n config: ProviderConfig,\n env: FeishuEnvironment,\n runtime: FeishuAuthRuntime,\n replayState?: FeishuReplayState,\n) {\n const resolved = resolveFeishuAdapterConfig(config, env);\n const validateCallback = resolved.verificationToken\n ? createSecretVerifier(resolved.verificationToken)\n : undefined;\n if (!(resolved.encryptKey || validateCallback)) {\n return undefined;\n }\n return async (request: Request, rawBody: string): Promise<Response | undefined> => {\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody) as unknown;\n } catch {\n return undefined;\n }\n if (!isRecord(payload)) {\n return unauthorizedFeishuWebhook();\n }\n const encryptedEnvelope = payload;\n\n const encrypted = typeof payload.encrypt === \"string\";\n let signedRequest = false;\n if (encrypted) {\n if (!resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n try {\n payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);\n } catch {\n return unauthorizedFeishuWebhook();\n }\n if (\n !verifyFeishuSignature(request, rawBody, resolved.encryptKey) &&\n !isFeishuUrlVerification(payload)\n ) {\n return unauthorizedFeishuWebhook();\n }\n signedRequest = !isFeishuUrlVerification(payload);\n } else if (resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n\n if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {\n return unauthorizedFeishuWebhook();\n }\n const now = runtime.now?.() ?? Date.now();\n const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);\n if (callbackTimestamp === null) {\n return unauthorizedFeishuWebhook();\n }\n if (\n callbackTimestamp !== undefined &&\n Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS\n ) {\n return unauthorizedFeishuWebhook();\n }\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n if (!replayState) {\n return undefined;\n }\n pruneFeishuReplayCache(replayState.accepted, now);\n while (callbackKeys.length > 0) {\n if (callbackKeys.some((key) => replayState.accepted.has(key))) {\n return new Response(null, { status: 200 });\n }\n const reservation = callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .find((candidate) => candidate !== undefined);\n if (!reservation) {\n reserveFeishuCallback(replayState, callbackKeys);\n break;\n }\n if (await reservation.promise) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n };\n}\n\nexport class FeishuProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const authRuntime =\n (runtime as (FeishuAuthRuntime & { env?: FeishuEnvironment }) | undefined) ?? {};\n const env = authRuntime.env ?? process.env;\n const resolved = resolveFeishuAdapterConfig(config, env);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolved.encryptKey),\n provider: \"Feishu\",\n requirement: \"feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification\",\n webhook: config.feishu?.webhook,\n });\n const replayState: FeishuReplayState = {\n accepted: new Map(),\n inFlight: new Map(),\n };\n const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(\n config,\n env,\n authRuntime,\n replayState,\n );\n const decodePayload = (payload: unknown) =>\n resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;\n super({\n codec: getBuiltinTargetCodec(\"feishu\"),\n config,\n id,\n options: {\n ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),\n createWebhookSuccessResponse(payload, responseId) {\n return new Response(JSON.stringify({ id: responseId, ok: true }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n },\n defaultWebhook: { host: \"127.0.0.1\", path: \"/feishu/webhook\", port: 8795 },\n endpointLabel: \"webhook endpoint\",\n handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),\n normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),\n platform: \"feishu\",\n publicUrl: config.feishu?.webhook.publicUrl,\n recorderPath: config.feishu?.recorder.path\n ? path.resolve(config.feishu.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload, rawBody }) {\n const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);\n const decodedPayload = decodePayload(encryptedEnvelope);\n if (accepted) {\n acceptFeishuCallback(\n replayState,\n decodedPayload,\n encryptedEnvelope,\n authRuntime.now?.() ?? Date.now(),\n );\n } else {\n rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);\n }\n },\n webhook: config.feishu?.webhook,\n },\n });\n }\n}\n\nexport function normalizeFeishuWebhookPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Feishu webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n if (!message) {\n return genericMockPayloadWithNativeThread({\n channelRule: FEISHU_CHAT_ID_RULE,\n payload,\n threadRule: FEISHU_MESSAGE_ID_RULE,\n });\n }\n\n const chatId = optionalString(message, \"chat_id\");\n const messageType = optionalString(message, \"message_type\");\n const messageId = optionalString(message, \"message_id\");\n const rootId = optionalString(message, \"root_id\");\n const rawContent = optionalString(message, \"content\");\n const text = parseFeishuText(rawContent);\n if (messageType !== \"text\") {\n throw new CrablineError(\"Feishu event payload requires message.message_type=text\", {\n kind: \"inbound\",\n });\n }\n if (!chatId || !text) {\n throw new CrablineError(\"Feishu event payload requires message.chat_id and message.content\", {\n kind: \"inbound\",\n });\n }\n\n return {\n author: authorFromBotFlag(\n event\n ? [\"app\", \"bot\"].includes(\n optionalString(optionalRecord(event, \"sender\") ?? {}, \"sender_type\") ?? \"\",\n )\n : false,\n ),\n ...(messageId\n ? { id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, \"Feishu message_id\") }\n : {}),\n raw: payload,\n text,\n threadId: rootId\n ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, \"Feishu root_id\")\n : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, \"Feishu chat_id\"),\n };\n}\n\nfunction parseFeishuText(content: string | undefined): string | undefined {\n if (!content) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(content);\n if (isRecord(parsed) && typeof parsed.text === \"string\") {\n return parsed.text;\n }\n return undefined;\n } catch {\n return content;\n }\n}\n\nfunction readFeishuSettledEnvelope(payload: unknown, rawBody: string): unknown {\n if (isRecord(payload)) {\n return payload;\n }\n try {\n return JSON.parse(rawBody) as unknown;\n } catch {\n return payload;\n }\n}\n\nexport function decryptFeishuWebhookPayload(payload: unknown, encryptKey: string): unknown {\n if (!isRecord(payload) || typeof payload.encrypt !== \"string\") {\n return payload;\n }\n const encrypted = Buffer.from(payload.encrypt, \"base64\");\n if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {\n throw new Error(\"Feishu encrypted payload is truncated.\");\n }\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const decipher = createDecipheriv(\"aes-256-cbc\", key, encrypted.subarray(0, 16));\n const decrypted = Buffer.concat([\n decipher.update(encrypted.subarray(16)),\n decipher.final(),\n ]).toString(\"utf8\");\n return JSON.parse(decrypted) as unknown;\n}\n\nfunction readFeishuVerificationToken(payload: unknown): string | null {\n if (!isRecord(payload)) {\n return null;\n }\n const header = optionalRecord(payload, \"header\");\n return (\n optionalString(payload, \"token\") ?? (header ? optionalString(header, \"token\") : null) ?? null\n );\n}\n\nfunction isFeishuUrlVerification(payload: unknown): boolean {\n return (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\" &&\n payload.challenge.length > 0\n );\n}\n\nfunction verifyFeishuSignature(request: Request, rawBody: string, encryptKey: string): boolean {\n const timestamp = request.headers.get(\"x-lark-request-timestamp\");\n const nonce = request.headers.get(\"x-lark-request-nonce\");\n const signature = request.headers.get(\"x-lark-signature\");\n if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {\n return false;\n }\n const expected = createHash(\"sha256\")\n .update(timestamp + nonce + encryptKey + rawBody)\n .digest();\n return timingSafeEqual(expected, Buffer.from(signature, \"hex\"));\n}\n\nfunction readFeishuCallbackTimestamp(\n request: Request,\n payload: unknown,\n signedRequest: boolean,\n): number | null | undefined {\n const requestTimestamp = signedRequest\n ? request.headers.get(\"x-lark-request-timestamp\")\n : undefined;\n const header = isRecord(payload) ? optionalRecord(payload, \"header\") : undefined;\n const headerTimestamp = header ? optionalString(header, \"create_time\") : undefined;\n const value = requestTimestamp ?? headerTimestamp;\n if (!value) {\n return undefined;\n }\n if (!/^\\d+$/u.test(value)) {\n return null;\n }\n const timestamp = Number(value);\n if (!Number.isSafeInteger(timestamp)) {\n return null;\n }\n if (timestamp >= 100_000_000_000_000) {\n return Math.floor(timestamp / 1_000);\n }\n if (timestamp >= 100_000_000_000) {\n return timestamp;\n }\n return timestamp * 1_000;\n}\n\nfunction readFeishuCallbackKeys(payload: unknown, encryptedEnvelope: unknown): string[] {\n if (!isRecord(payload) || isFeishuUrlVerification(payload)) {\n return [];\n }\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n const header = optionalRecord(payload, \"header\");\n const messageId = message ? optionalString(message, \"message_id\") : undefined;\n const eventId = header ? optionalString(header, \"event_id\") : undefined;\n const encrypted = isRecord(encryptedEnvelope)\n ? optionalString(encryptedEnvelope, \"encrypt\")\n : undefined;\n return [\n ...(messageId ? [`message:${messageId}`] : []),\n ...(eventId ? [`event:${eventId}`] : []),\n ...(encrypted ? [`encrypted:${createHash(\"sha256\").update(encrypted).digest(\"hex\")}`] : []),\n ];\n}\n\nfunction reserveFeishuCallback(replayState: FeishuReplayState, callbackKeys: string[]): void {\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n const reservation: FeishuReplayReservation = {\n keys: new Set(callbackKeys),\n promise,\n resolve: resolveReservation,\n };\n for (const key of callbackKeys) {\n replayState.inFlight.set(key, reservation);\n }\n}\n\nfunction acceptFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n now: number,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n pruneFeishuReplayCache(replayState.accepted, now);\n for (const key of callbackKeys) {\n replayState.accepted.set(key, now);\n }\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, true);\n }\n}\n\nfunction rejectFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, false);\n }\n}\n\nfunction releaseFeishuReservation(\n replayState: FeishuReplayState,\n reservation: FeishuReplayReservation,\n accepted: boolean,\n): void {\n for (const key of reservation.keys) {\n if (replayState.inFlight.get(key) === reservation) {\n replayState.inFlight.delete(key);\n }\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneFeishuReplayCache(recentCallbacks: Map<string, number>, now: number): void {\n for (const [key, acceptedAt] of recentCallbacks) {\n if (\n now - acceptedAt > FEISHU_MAX_CALLBACK_AGE_MS ||\n recentCallbacks.size > FEISHU_REPLAY_CACHE_LIMIT\n ) {\n recentCallbacks.delete(key);\n }\n }\n}\n\nfunction unauthorizedFeishuWebhook(): Response {\n return new Response(\"unauthorized\", { status: 401 });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../../../src/providers/builtin/feishu.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDecipheriv, createHash, timingSafeEqual } from \"node:crypto\";\nimport path from \"node:path\";\nimport { CrablineError } from \"../../core/errors.js\";\nimport type { ProviderConfig } from \"../../config/schema.js\";\nimport { LocalMockProviderAdapter } from \"../local-mock.js\";\nimport type { ProviderAdapter } from \"../types.js\";\nimport {\n FEISHU_CHAT_ID_RULE,\n FEISHU_MESSAGE_ID_RULE,\n getBuiltinTargetCodec,\n} from \"../target-normalizers.js\";\nimport {\n authorFromBotFlag,\n createSecretVerifier,\n genericMockPayloadWithNativeThread,\n isRecord,\n optionalRecord,\n optionalString,\n requireNativeInboundId,\n} from \"./native-local-mock.js\";\nimport { requireExternalWebhookAuthentication } from \"./external-webhook-auth.js\";\n\ntype FeishuEnvironment = Partial<\n Pick<NodeJS.ProcessEnv, \"FEISHU_ENCRYPT_KEY\" | \"FEISHU_VERIFICATION_TOKEN\">\n>;\n\ntype FeishuAuthRuntime = {\n now?: (() => number) | undefined;\n replayCacheLimit?: number | undefined;\n};\n\ntype FeishuReplayReservation = {\n expiresAt: number;\n keys: Set<string>;\n promise: Promise<boolean>;\n resolve: (accepted: boolean) => void;\n};\n\ntype FeishuReplayState = {\n accepted: Map<string, number>;\n inFlight: Map<string, FeishuReplayReservation>;\n};\n\nconst FEISHU_MAX_CALLBACK_AGE_MS = 5 * 60_000;\nconst FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES = 1024 * 1024;\nconst FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH =\n Math.ceil(FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES / 3) * 4;\nconst FEISHU_REPLAY_CACHE_LIMIT = 2_048;\nconst FEISHU_MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;\n\nexport function resolveFeishuAdapterConfig(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n) {\n const configuredEncryptKey = config.feishu?.encryptKey;\n const encryptKey = configuredEncryptKey ?? env.FEISHU_ENCRYPT_KEY;\n if (encryptKey !== undefined && !encryptKey.trim()) {\n throw new CrablineError(\n configuredEncryptKey === undefined\n ? \"FEISHU_ENCRYPT_KEY must not be empty or whitespace-only.\"\n : \"Feishu encryptKey must not be empty or whitespace-only.\",\n { kind: \"config\" },\n );\n }\n return {\n appId: config.feishu?.appId ?? \"local-mock-feishu-app\",\n encryptKey,\n userName: config.feishu?.userName,\n verificationToken: config.feishu?.verificationToken ?? env.FEISHU_VERIFICATION_TOKEN,\n };\n}\n\nexport function handleFeishuWebhookPayload(payload: unknown): Response | undefined {\n if (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\"\n ) {\n return Response.json({ challenge: payload.challenge });\n }\n if (isRecord(payload) && isRecord(payload.event)) {\n const message = optionalRecord(payload.event, \"message\");\n if (!message) {\n return new Response(null, { status: 200 });\n }\n const messageType = optionalString(message, \"message_type\");\n if (messageType !== \"text\") {\n return new Response(null, { status: 200 });\n }\n const text = parseFeishuText(optionalString(message, \"content\"));\n if (typeof text !== \"string\") {\n return undefined;\n }\n if (!text) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n}\n\nexport function createFeishuWebhookAuthenticator(\n config: ProviderConfig,\n env: FeishuEnvironment = process.env,\n runtime: FeishuAuthRuntime = {},\n) {\n return createFeishuWebhookAuthenticatorWithReplay(config, env, runtime);\n}\n\nfunction createFeishuWebhookAuthenticatorWithReplay(\n config: ProviderConfig,\n env: FeishuEnvironment,\n runtime: FeishuAuthRuntime,\n replayState?: FeishuReplayState,\n) {\n const resolved = resolveFeishuAdapterConfig(config, env);\n const validateCallback = resolved.verificationToken\n ? createSecretVerifier(resolved.verificationToken)\n : undefined;\n if (!(resolved.encryptKey || validateCallback)) {\n return undefined;\n }\n return async (request: Request, rawBody: string): Promise<Response | undefined> => {\n if (!isFeishuWebhookBodyWithinLimit(rawBody)) {\n return unauthorizedFeishuWebhook();\n }\n const signaturePresent = request.headers.has(\"x-lark-signature\");\n let signedRequest = false;\n if (signaturePresent) {\n if (!resolved.encryptKey || !verifyFeishuSignature(request, rawBody, resolved.encryptKey)) {\n return unauthorizedFeishuWebhook();\n }\n signedRequest = true;\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody) as unknown;\n } catch {\n return undefined;\n }\n if (!isRecord(payload)) {\n return unauthorizedFeishuWebhook();\n }\n const encryptedEnvelope = payload;\n\n const encrypted = typeof payload.encrypt === \"string\";\n if (encrypted) {\n if (!resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n try {\n payload = decryptFeishuWebhookPayload(payload, resolved.encryptKey);\n } catch {\n return unauthorizedFeishuWebhook();\n }\n if (!signedRequest && !(isFeishuUrlVerification(payload) && validateCallback)) {\n return unauthorizedFeishuWebhook();\n }\n } else if (resolved.encryptKey) {\n return unauthorizedFeishuWebhook();\n }\n\n if (validateCallback && !validateCallback(readFeishuVerificationToken(payload))) {\n return unauthorizedFeishuWebhook();\n }\n const now = runtime.now?.() ?? Date.now();\n const callbackTimestamp = readFeishuCallbackTimestamp(request, payload, signedRequest);\n if (callbackTimestamp === null) {\n return unauthorizedFeishuWebhook();\n }\n if (\n callbackTimestamp !== undefined &&\n Math.abs(now - callbackTimestamp) > FEISHU_MAX_CALLBACK_AGE_MS\n ) {\n return unauthorizedFeishuWebhook();\n }\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n if (!replayState) {\n return undefined;\n }\n const callbackExpiresAt = (callbackTimestamp ?? now) + FEISHU_MAX_CALLBACK_AGE_MS;\n pruneFeishuReplayCache(replayState.accepted, now);\n while (callbackKeys.length > 0) {\n if (callbackKeys.some((key) => replayState.accepted.has(key))) {\n return new Response(null, { status: 200 });\n }\n const reservation = callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .find((candidate) => candidate !== undefined);\n if (!reservation) {\n if (\n !reserveFeishuCallback(\n replayState,\n callbackKeys,\n callbackExpiresAt,\n runtime.replayCacheLimit ?? FEISHU_REPLAY_CACHE_LIMIT,\n )\n ) {\n return feishuReplayCapacityResponse();\n }\n break;\n }\n if (await reservation.promise) {\n return new Response(null, { status: 200 });\n }\n }\n return undefined;\n };\n}\n\nexport class FeishuProviderAdapter extends LocalMockProviderAdapter implements ProviderAdapter {\n constructor(id: string, config: ProviderConfig, _userName: string, runtime?: unknown) {\n const authRuntime =\n (runtime as (FeishuAuthRuntime & { env?: FeishuEnvironment }) | undefined) ?? {};\n const env = authRuntime.env ?? process.env;\n const resolved = resolveFeishuAdapterConfig(config, env);\n requireExternalWebhookAuthentication({\n authenticated: Boolean(resolved.encryptKey),\n provider: \"Feishu\",\n requirement: \"feishu.encryptKey or FEISHU_ENCRYPT_KEY for X-Lark-Signature verification\",\n webhook: config.feishu?.webhook,\n });\n const replayState: FeishuReplayState = {\n accepted: new Map(),\n inFlight: new Map(),\n };\n const authenticateWebhookRequest = createFeishuWebhookAuthenticatorWithReplay(\n config,\n env,\n authRuntime,\n replayState,\n );\n const decodePayload = (payload: unknown) =>\n resolved.encryptKey ? decryptFeishuWebhookPayload(payload, resolved.encryptKey) : payload;\n super({\n codec: getBuiltinTargetCodec(\"feishu\"),\n config,\n id,\n options: {\n ...(authenticateWebhookRequest ? { authenticateWebhookRequest } : {}),\n createWebhookSuccessResponse(payload, responseId) {\n return new Response(JSON.stringify({ id: responseId, ok: true }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n },\n defaultWebhook: { host: \"127.0.0.1\", path: \"/feishu/webhook\", port: 8795 },\n endpointLabel: \"webhook endpoint\",\n handleWebhookPayload: (payload) => handleFeishuWebhookPayload(decodePayload(payload)),\n normalizeWebhookPayload: (payload) => normalizeFeishuWebhookPayload(decodePayload(payload)),\n platform: \"feishu\",\n publicUrl: config.feishu?.webhook.publicUrl,\n recorderPath: config.feishu?.recorder.path\n ? path.resolve(config.feishu.recorder.path)\n : undefined,\n settleWebhookRequest({ accepted, payload, rawBody }) {\n const encryptedEnvelope = readFeishuSettledEnvelope(payload, rawBody);\n const decodedPayload = decodePayload(encryptedEnvelope);\n if (accepted) {\n acceptFeishuCallback(\n replayState,\n decodedPayload,\n encryptedEnvelope,\n authRuntime.now?.() ?? Date.now(),\n );\n } else {\n rejectFeishuCallback(replayState, decodedPayload, encryptedEnvelope);\n }\n },\n webhook: config.feishu?.webhook,\n },\n });\n }\n}\n\nexport function normalizeFeishuWebhookPayload(payload: unknown) {\n if (!isRecord(payload)) {\n throw new CrablineError(\"Feishu webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const recordedPayload = redactFeishuVerificationTokens(payload);\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n if (!message) {\n return genericMockPayloadWithNativeThread({\n channelRule: FEISHU_CHAT_ID_RULE,\n payload: recordedPayload,\n threadRule: FEISHU_MESSAGE_ID_RULE,\n });\n }\n\n const chatId = optionalString(message, \"chat_id\");\n const messageType = optionalString(message, \"message_type\");\n const messageId = optionalString(message, \"message_id\");\n const rootId = optionalString(message, \"root_id\");\n const rawContent = optionalString(message, \"content\");\n const text = parseFeishuText(rawContent);\n if (text === null) {\n throw new CrablineError(\"Feishu message.content must be valid JSON\", {\n kind: \"inbound\",\n });\n }\n if (messageType !== \"text\") {\n throw new CrablineError(\"Feishu event payload requires message.message_type=text\", {\n kind: \"inbound\",\n });\n }\n if (!chatId || !messageId || !text) {\n throw new CrablineError(\n \"Feishu event payload requires message.chat_id, message.message_id, and message.content\",\n {\n kind: \"inbound\",\n },\n );\n }\n\n return {\n author: authorFromBotFlag(\n event\n ? [\"app\", \"bot\"].includes(\n optionalString(optionalRecord(event, \"sender\") ?? {}, \"sender_type\") ?? \"\",\n )\n : false,\n ),\n id: requireNativeInboundId(messageId, FEISHU_MESSAGE_ID_RULE, \"Feishu message_id\"),\n raw: recordedPayload,\n text,\n threadId: rootId\n ? requireNativeInboundId(rootId, FEISHU_MESSAGE_ID_RULE, \"Feishu root_id\")\n : requireNativeInboundId(chatId, FEISHU_CHAT_ID_RULE, \"Feishu chat_id\"),\n };\n}\n\nfunction redactFeishuVerificationTokens(payload: Record<string, unknown>): Record<string, unknown> {\n const redacted = { ...payload };\n delete redacted.token;\n const header = optionalRecord(payload, \"header\");\n if (header) {\n const redactedHeader = { ...header };\n delete redactedHeader.token;\n redacted.header = redactedHeader;\n }\n return redacted;\n}\n\nfunction parseFeishuText(content: string | undefined): string | null | undefined {\n if (!content) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(content);\n if (isRecord(parsed) && typeof parsed.text === \"string\") {\n return parsed.text;\n }\n return undefined;\n } catch {\n return null;\n }\n}\n\nfunction readFeishuSettledEnvelope(payload: unknown, rawBody: string): unknown {\n if (isRecord(payload)) {\n return payload;\n }\n try {\n return JSON.parse(rawBody) as unknown;\n } catch {\n return payload;\n }\n}\n\nexport function decryptFeishuWebhookPayload(payload: unknown, encryptKey: string): unknown {\n if (!isRecord(payload) || typeof payload.encrypt !== \"string\") {\n return payload;\n }\n if (payload.encrypt.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BASE64_LENGTH) {\n throw new Error(\"Feishu encrypted payload is too large.\");\n }\n const encrypted = Buffer.from(payload.encrypt, \"base64\");\n if (encrypted.length > FEISHU_MAX_ENCRYPTED_PAYLOAD_BYTES) {\n throw new Error(\"Feishu encrypted payload is too large.\");\n }\n if (encrypted.length <= 16 || (encrypted.length - 16) % 16 !== 0) {\n throw new Error(\"Feishu encrypted payload is truncated.\");\n }\n const key = createHash(\"sha256\").update(encryptKey).digest();\n const decipher = createDecipheriv(\"aes-256-cbc\", key, encrypted.subarray(0, 16));\n const decrypted = Buffer.concat([\n decipher.update(encrypted.subarray(16)),\n decipher.final(),\n ]).toString(\"utf8\");\n return JSON.parse(decrypted) as unknown;\n}\n\nfunction readFeishuVerificationToken(payload: unknown): string | null {\n if (!isRecord(payload)) {\n return null;\n }\n const header = optionalRecord(payload, \"header\");\n return (\n optionalString(payload, \"token\") ?? (header ? optionalString(header, \"token\") : null) ?? null\n );\n}\n\nfunction isFeishuUrlVerification(payload: unknown): boolean {\n return (\n isRecord(payload) &&\n payload.type === \"url_verification\" &&\n typeof payload.challenge === \"string\" &&\n payload.challenge.length > 0\n );\n}\n\nfunction isFeishuWebhookBodyWithinLimit(rawBody: string): boolean {\n return (\n rawBody.length <= FEISHU_MAX_WEBHOOK_BODY_BYTES &&\n Buffer.byteLength(rawBody, \"utf8\") <= FEISHU_MAX_WEBHOOK_BODY_BYTES\n );\n}\n\nfunction verifyFeishuSignature(request: Request, rawBody: string, encryptKey: string): boolean {\n const timestamp = request.headers.get(\"x-lark-request-timestamp\");\n const nonce = request.headers.get(\"x-lark-request-nonce\");\n const signature = request.headers.get(\"x-lark-signature\");\n if (!timestamp || !nonce || !signature || !/^[0-9a-f]{64}$/iu.test(signature)) {\n return false;\n }\n const expected = createHash(\"sha256\")\n .update(timestamp)\n .update(nonce)\n .update(encryptKey)\n .update(rawBody)\n .digest();\n return timingSafeEqual(expected, Buffer.from(signature, \"hex\"));\n}\n\nfunction readFeishuCallbackTimestamp(\n request: Request,\n payload: unknown,\n signedRequest: boolean,\n): number | null | undefined {\n const requestTimestamp = signedRequest\n ? request.headers.get(\"x-lark-request-timestamp\")\n : undefined;\n const header = isRecord(payload) ? optionalRecord(payload, \"header\") : undefined;\n const headerTimestamp = header ? optionalString(header, \"create_time\") : undefined;\n const value = requestTimestamp ?? headerTimestamp;\n if (!value) {\n return undefined;\n }\n if (!/^\\d+$/u.test(value)) {\n return null;\n }\n const timestamp = Number(value);\n if (!Number.isSafeInteger(timestamp)) {\n return null;\n }\n if (timestamp >= 100_000_000_000_000) {\n return Math.floor(timestamp / 1_000);\n }\n if (timestamp >= 100_000_000_000) {\n return timestamp;\n }\n return timestamp * 1_000;\n}\n\nfunction readFeishuCallbackKeys(payload: unknown, encryptedEnvelope: unknown): string[] {\n if (!isRecord(payload) || isFeishuUrlVerification(payload)) {\n return [];\n }\n const event = optionalRecord(payload, \"event\");\n const message = event ? optionalRecord(event, \"message\") : undefined;\n const header = optionalRecord(payload, \"header\");\n const messageId = message ? optionalString(message, \"message_id\") : undefined;\n const eventId = header ? optionalString(header, \"event_id\") : undefined;\n const encrypted = isRecord(encryptedEnvelope)\n ? optionalString(encryptedEnvelope, \"encrypt\")\n : undefined;\n if (eventId) {\n return [`event:${eventId}`];\n }\n if (messageId) {\n return [`message:${messageId}`];\n }\n return encrypted ? [`encrypted:${createHash(\"sha256\").update(encrypted).digest(\"hex\")}`] : [];\n}\n\nfunction reserveFeishuCallback(\n replayState: FeishuReplayState,\n callbackKeys: string[],\n expiresAt: number,\n cacheLimit: number,\n): boolean {\n const occupiedKeys = new Set([...replayState.accepted.keys(), ...replayState.inFlight.keys()]);\n const newKeys = new Set(callbackKeys.filter((key) => !occupiedKeys.has(key)));\n if (occupiedKeys.size + newKeys.size > cacheLimit) {\n return false;\n }\n let resolveReservation!: (accepted: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveReservation = resolve;\n });\n const reservation: FeishuReplayReservation = {\n expiresAt,\n keys: new Set(callbackKeys),\n promise,\n resolve: resolveReservation,\n };\n for (const key of callbackKeys) {\n replayState.inFlight.set(key, reservation);\n }\n return true;\n}\n\nfunction acceptFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n now: number,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n pruneFeishuReplayCache(replayState.accepted, now);\n for (const key of callbackKeys) {\n const reservation = replayState.inFlight.get(key);\n replayState.accepted.set(\n key,\n Math.max(\n replayState.accepted.get(key) ?? 0,\n reservation?.expiresAt ?? now + FEISHU_MAX_CALLBACK_AGE_MS,\n ),\n );\n }\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, true);\n }\n}\n\nfunction rejectFeishuCallback(\n replayState: FeishuReplayState,\n payload: unknown,\n encryptedEnvelope: unknown,\n): void {\n const callbackKeys = readFeishuCallbackKeys(payload, encryptedEnvelope);\n const reservations = new Set(\n callbackKeys\n .map((key) => replayState.inFlight.get(key))\n .filter((reservation) => reservation !== undefined),\n );\n for (const reservation of reservations) {\n releaseFeishuReservation(replayState, reservation, false);\n }\n}\n\nfunction releaseFeishuReservation(\n replayState: FeishuReplayState,\n reservation: FeishuReplayReservation,\n accepted: boolean,\n): void {\n for (const key of reservation.keys) {\n if (replayState.inFlight.get(key) === reservation) {\n replayState.inFlight.delete(key);\n }\n }\n reservation.resolve(accepted);\n}\n\nfunction pruneFeishuReplayCache(recentCallbacks: Map<string, number>, now: number): void {\n for (const [key, expiresAt] of recentCallbacks) {\n if (now > expiresAt) {\n recentCallbacks.delete(key);\n }\n }\n}\n\nfunction feishuReplayCapacityResponse(): Response {\n return new Response(\"service unavailable\", {\n headers: { \"cache-control\": \"no-store\" },\n status: 503,\n });\n}\n\nfunction unauthorizedFeishuWebhook(): Response {\n return new Response(\"unauthorized\", { status: 401 });\n}\n"]}
|