fix(google): bound OAuth token error response reads (#99605)

* fix(google): bound OAuth token error response reads

* test(google): consolidate OAuth error cases

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
mushuiyu886
2026-07-04 07:00:01 +08:00
committed by GitHub
parent 96b029718b
commit 6207a4e75b
2 changed files with 41 additions and 1 deletions
+35
View File
@@ -680,6 +680,17 @@ describe("loginGeminiCliOAuth", () => {
});
}
function responseTextBodyWithTextTrap(body: string, status = 500) {
const response = new Response(body, {
status,
headers: { "Content-Type": "text/plain" },
});
const text = vi
.spyOn(response, "text")
.mockRejectedValue(new Error("unexpected response.text() call"));
return { response, text };
}
function tokenResponse(): Response {
return responseJson({
access_token: "access-token",
@@ -927,6 +938,30 @@ describe("loginGeminiCliOAuth", () => {
expect(requests.filter(({ url }) => url.includes("v1internal:loadCodeAssist"))).toHaveLength(3);
});
it.each([
[
"exchange",
"x",
async () =>
(await import("./oauth.token.js")).exchangeCodeForTokens("oauth-code", "pkce-verifier"),
],
[
"refresh",
"y",
async () =>
(await import("./oauth.token.js")).refreshTokensForGeminiCli({ refresh: "refresh-token" }),
],
])("bounds token %s error bodies without using response.text()", async (_flow, fill, request) => {
const { response, text } = responseTextBodyWithTextTrap(fill.repeat(32 * 1024), 500);
installGeminiOAuthFetchMock(() => undefined, { tokenResponse: () => response });
const error = await request().catch((err: unknown) => err);
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe(`Token exchange failed: ${fill.repeat(8 * 1024)}`);
expect(text).not.toHaveBeenCalled();
});
it("falls back to GOOGLE_CLOUD_PROJECT when all loadCodeAssist endpoints fail", async () => {
process.env.GOOGLE_CLOUD_PROJECT = "env-project";
+6 -1
View File
@@ -3,6 +3,7 @@ import {
asDateTimestampMs,
resolveExpiresAtMsFromDurationSeconds,
} from "openclaw/plugin-sdk/number-runtime";
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
import { resolveOAuthClientConfig } from "./oauth.credentials.js";
import { fetchWithTimeout } from "./oauth.http.js";
import { resolveGoogleOAuthIdentity, resolveGooglePersonalOAuthIdentity } from "./oauth.project.js";
@@ -10,6 +11,7 @@ import { isGeminiCliPersonalOAuth } from "./oauth.settings.js";
import { REDIRECT_URI, TOKEN_URL, type GeminiCliOAuthCredentials } from "./oauth.shared.js";
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
const GOOGLE_OAUTH_TOKEN_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
async function requestTokenGrant(body: URLSearchParams): Promise<{
access_token?: string;
@@ -27,7 +29,10 @@ async function requestTokenGrant(body: URLSearchParams): Promise<{
});
if (!response.ok) {
const errorText = await response.text();
const errorText = await readResponseTextLimited(
response,
GOOGLE_OAUTH_TOKEN_ERROR_BODY_LIMIT_BYTES,
);
throw new Error(`Token exchange failed: ${errorText}`);
}