Skip to content

Commit 1d61563

Browse files
Alix-007Leon-SK668
authored andcommitted
fix(msteams): reject malformed OAuth token envelopes
1 parent 25d6f13 commit 1d61563

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

extensions/msteams/src/oauth.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ describe("exchangeMSTeamsCodeForTokens", () => {
274274
}),
275275
).rejects.toThrow("MSTeams token exchange failed: invalid token response fields");
276276
});
277+
278+
it.each([
279+
{ label: "null", body: null },
280+
{ label: "array", body: [] },
281+
])("rejects a top-level $label token exchange response", async ({ body }) => {
282+
fetchSpy.mockResolvedValueOnce(responseJson(body));
283+
284+
await expect(
285+
exchangeMSTeamsCodeForTokens({
286+
tenantId: "t",
287+
clientId: "c",
288+
clientSecret: "s", // pragma: allowlist secret
289+
code: "invalid-shape",
290+
verifier: "v",
291+
}),
292+
).rejects.toThrow("MSTeams token exchange failed: invalid token response fields");
293+
});
277294
});
278295

279296
describe("refreshMSTeamsDelegatedTokens", () => {
@@ -374,4 +391,20 @@ describe("refreshMSTeamsDelegatedTokens", () => {
374391
}),
375392
).rejects.toThrow("MSTeams token refresh failed: malformed JSON response");
376393
});
394+
395+
it.each([
396+
{ label: "null", body: null },
397+
{ label: "array", body: [] },
398+
])("rejects a top-level $label token refresh response", async ({ body }) => {
399+
fetchSpy.mockResolvedValueOnce(responseJson(body));
400+
401+
await expect(
402+
refreshMSTeamsDelegatedTokens({
403+
tenantId: "t",
404+
clientId: "c",
405+
clientSecret: "s", // pragma: allowlist secret
406+
refreshToken: "rt",
407+
}),
408+
).rejects.toThrow("MSTeams token refresh failed: invalid token response fields");
409+
});
377410
});

extensions/msteams/src/oauth.token.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ function resolveMSTeamsTokenExpiresAt(value: unknown): number | undefined {
4646
return resolveExpiresAtMsFromDurationSeconds(value, { bufferMs: EXPIRY_BUFFER_MS });
4747
}
4848

49+
function assertMSTeamsTokenResponseObject(
50+
data: unknown,
51+
failureLabel: string,
52+
): Record<string, unknown> {
53+
// JSON.parse can yield null or arrays even when the provider response is typed as an object.
54+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
55+
throw new Error(`MSTeams ${failureLabel} failed: invalid token response fields`);
56+
}
57+
return data as Record<string, unknown>;
58+
}
59+
4960
function parseMSTeamsTokenResponse(
5061
data: Record<string, unknown>,
5162
failureLabel: string,
@@ -93,11 +104,14 @@ async function fetchMSTeamsTokens(params: {
93104
if (!response.ok) {
94105
throw await createMSTeamsHttpError(response, `MSTeams ${params.failureLabel} failed`);
95106
}
96-
const data = await readProviderJsonResponse<Record<string, unknown>>(
107+
const data = await readProviderJsonResponse<unknown>(
97108
response,
98109
`MSTeams ${params.failureLabel} failed`,
99110
);
100-
return parseMSTeamsTokenResponse(data, params.failureLabel);
111+
return parseMSTeamsTokenResponse(
112+
assertMSTeamsTokenResponseObject(data, params.failureLabel),
113+
params.failureLabel,
114+
);
101115
} finally {
102116
await release();
103117
}

0 commit comments

Comments
 (0)