diff --git a/CHANGELOG.md b/CHANGELOG.md index bba48accffa..ebb4b49b43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai - Providers/Cloudflare AI Gateway: strip assistant prefill turns from Anthropic Messages payloads when thinking is enabled, so Claude requests through Cloudflare AI Gateway no longer fail Anthropic conversation-ending validation. Fixes #72905; carries forward #73005. Thanks @AaronFaby and @sahilsatralkar. - Gateway/startup: scope primary-model provider discovery during channel prewarm to the configured provider owner and add split startup trace timings, so boot avoids staging unrelated bundled provider dependencies while setup discovery remains broad. Fixes #73002. Thanks @Schnup03. +- Channels/Microsoft Teams: unwrap staged CommonJS JWT runtime dependencies before Bot Connector token validation so inbound Teams messages no longer 401 after the bundled runtime-deps move. Fixes #73026. Thanks @kbrown10000. - Channels/sessions: prevent guarded inbound session recording from creating route-only phantom sessions while still allowing last-route updates for sessions that already exist. Carries forward #73009. Thanks @jzakirov. - Cron: accept `delivery.threadId` in Gateway cron add/update schemas so scheduled announce delivery can target Telegram forum topics and other threaded channel destinations through the documented delivery path. Fixes #73017. Thanks @coachsootz. - Plugins/runtime deps: stage bundled plugin dependencies imported by mirrored root dist chunks, so packaged memory and status commands do not miss `chokidar` or similar root-chunk dependencies after update. Fixes #72882 and #72970; carries forward #72992. Thanks @shrimpy8, @colin-chang, and @Schnup03. diff --git a/extensions/msteams/src/sdk.test.ts b/extensions/msteams/src/sdk.test.ts index b240efd0932..d9ba09cfddd 100644 --- a/extensions/msteams/src/sdk.test.ts +++ b/extensions/msteams/src/sdk.test.ts @@ -60,7 +60,9 @@ const jwtMockImpl = { }; vi.mock("jsonwebtoken", () => ({ - ...jwtMockImpl, + // Match jsonwebtoken@9 under dynamic ESM import from staged runtime deps: + // Node exposes decode as a named export, while verify is only on default. + decode: jwtMockImpl.decode, default: jwtMockImpl, })); diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index dd7e6d028de..3281f705a53 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -659,9 +659,11 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{ ]; type BotFrameworkJwtDeps = { - jwt: typeof import("jsonwebtoken"); + jwt: Pick; JwksClient: typeof import("jwks-rsa").JwksClient; }; +type JsonwebtokenRuntime = BotFrameworkJwtDeps["jwt"]; +type JwksClientCtor = BotFrameworkJwtDeps["JwksClient"]; const BOT_FRAMEWORK_GLOBAL_AUDIENCE = "https://api.botframework.com"; @@ -713,9 +715,55 @@ function hasExpectedBotIdentity(payload: unknown, expectedAppId: string): boolea let botFrameworkJwtDepsPromise: Promise | null = null; +function hasDefaultExport(value: unknown): value is { default?: unknown } { + return !!value && typeof value === "object" && "default" in value; +} + +function isJsonwebtokenRuntime(value: unknown): value is JsonwebtokenRuntime { + return ( + !!value && + typeof value === "object" && + typeof (value as { decode?: unknown }).decode === "function" && + typeof (value as { verify?: unknown }).verify === "function" + ); +} + +function loadJsonwebtokenRuntime(jwtModule: unknown): JsonwebtokenRuntime { + const jwt = hasDefaultExport(jwtModule) ? (jwtModule.default ?? jwtModule) : jwtModule; + if (!isJsonwebtokenRuntime(jwt)) { + throw new Error("jsonwebtoken did not export decode/verify"); + } + return jwt; +} + +function isJwksClientRuntime(value: unknown): value is JwksClientCtor { + return typeof value === "function"; +} + +function loadJwksClientRuntime(jwksModule: unknown): JwksClientCtor { + const direct = + jwksModule && typeof jwksModule === "object" + ? (jwksModule as { JwksClient?: unknown }).JwksClient + : undefined; + const fallback = + hasDefaultExport(jwksModule) && jwksModule.default && typeof jwksModule.default === "object" + ? (jwksModule.default as { JwksClient?: unknown }).JwksClient + : undefined; + const JwksClient = direct ?? fallback; + if (!isJwksClientRuntime(JwksClient)) { + throw new Error("jwks-rsa did not export JwksClient"); + } + return JwksClient; +} + async function loadBotFrameworkJwtDeps(): Promise { botFrameworkJwtDepsPromise ??= Promise.all([import("jsonwebtoken"), import("jwks-rsa")]).then( - ([jwt, { JwksClient }]) => ({ jwt, JwksClient }), + ([jwtModule, jwksModule]) => { + return { + jwt: loadJsonwebtokenRuntime(jwtModule), + JwksClient: loadJwksClientRuntime(jwksModule), + }; + }, ); return botFrameworkJwtDepsPromise; }