mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-09 02:52:55 +00:00
fix(msteams): validate oauth token lifetimes
This commit is contained in:
@@ -247,6 +247,25 @@ describe("exchangeMSTeamsCodeForTokens", () => {
|
||||
}),
|
||||
).rejects.toThrow("MSTeams token exchange failed: malformed JSON response");
|
||||
});
|
||||
|
||||
it("rejects unsafe token exchange expiry values", async () => {
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
new Response('{"access_token":"at-unsafe","refresh_token":"rt-unsafe","expires_in":1e309}', {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
exchangeMSTeamsCodeForTokens({
|
||||
tenantId: "t",
|
||||
clientId: "c",
|
||||
clientSecret: "s", // pragma: allowlist secret
|
||||
code: "unsafe-expiry",
|
||||
verifier: "v",
|
||||
}),
|
||||
).rejects.toThrow("MSTeams token exchange failed: invalid token response fields");
|
||||
});
|
||||
});
|
||||
|
||||
describe("refreshMSTeamsDelegatedTokens", () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { createMSTeamsHttpError } from "./http-error.js";
|
||||
@@ -15,7 +16,7 @@ const EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
||||
type MSTeamsTokenResponse = {
|
||||
access_token: string;
|
||||
refresh_token?: string;
|
||||
expires_in: number;
|
||||
expiresAt: number;
|
||||
scope?: string;
|
||||
};
|
||||
|
||||
@@ -40,6 +41,42 @@ function createMSTeamsTokenBody(params: {
|
||||
return body;
|
||||
}
|
||||
|
||||
function resolveMSTeamsTokenExpiresAt(value: unknown): number | undefined {
|
||||
const expiresInSeconds = parseStrictPositiveInteger(value);
|
||||
if (expiresInSeconds === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lifetimeMs = expiresInSeconds * 1000;
|
||||
const expiresAt = Date.now() + lifetimeMs - EXPIRY_BUFFER_MS;
|
||||
return Number.isSafeInteger(lifetimeMs) && Number.isSafeInteger(expiresAt)
|
||||
? expiresAt
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function parseMSTeamsTokenResponse(
|
||||
data: Record<string, unknown>,
|
||||
failureLabel: string,
|
||||
): MSTeamsTokenResponse {
|
||||
const expiresAt = resolveMSTeamsTokenExpiresAt(data.expires_in);
|
||||
if (
|
||||
typeof data.access_token !== "string" ||
|
||||
!data.access_token ||
|
||||
expiresAt === undefined ||
|
||||
(data.refresh_token !== undefined && typeof data.refresh_token !== "string") ||
|
||||
(data.scope !== undefined && typeof data.scope !== "string")
|
||||
) {
|
||||
throw new Error(`MSTeams ${failureLabel} failed: invalid token response fields`);
|
||||
}
|
||||
|
||||
return {
|
||||
access_token: data.access_token,
|
||||
refresh_token: data.refresh_token,
|
||||
expiresAt,
|
||||
scope: data.scope,
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchMSTeamsTokens(params: {
|
||||
tokenUrl: string;
|
||||
body: URLSearchParams;
|
||||
@@ -66,10 +103,11 @@ async function fetchMSTeamsTokens(params: {
|
||||
if (!response.ok) {
|
||||
throw await createMSTeamsHttpError(response, `MSTeams ${params.failureLabel} failed`);
|
||||
}
|
||||
return await readProviderJsonResponse<MSTeamsTokenResponse>(
|
||||
const data = await readProviderJsonResponse<Record<string, unknown>>(
|
||||
response,
|
||||
`MSTeams ${params.failureLabel} failed`,
|
||||
);
|
||||
return parseMSTeamsTokenResponse(data, params.failureLabel);
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
@@ -104,7 +142,7 @@ async function requestMSTeamsDelegatedTokens(params: {
|
||||
return {
|
||||
accessToken: data.access_token,
|
||||
refreshToken: params.resolveRefreshToken(data),
|
||||
expiresAt: Date.now() + data.expires_in * 1000 - EXPIRY_BUFFER_MS,
|
||||
expiresAt: data.expiresAt,
|
||||
scopes: data.scope ? data.scope.split(" ") : [...scopes],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user