fix(google): normalize unsafe oauth expiry

This commit is contained in:
Peter Steinberger
2026-05-29 12:59:24 -04:00
parent 65b00716d2
commit 4ef77dadec
2 changed files with 39 additions and 7 deletions

View File

@@ -950,4 +950,34 @@ describe("loginGeminiCliOAuth", () => {
expect(Number.isFinite(result.expires)).toBe(true);
expect(result.expires).toBeLessThanOrEqual(beforeRefresh);
});
it("keeps unsafe token expiry values out of refreshed Gemini CLI credentials", async () => {
mockSettingsExistsSync.mockReturnValue(true);
mockSettingsReadFileSync.mockReturnValue(
JSON.stringify({
security: {
auth: {
selectedType: "oauth-personal",
},
},
}),
);
const beforeRefresh = Date.now();
installGeminiOAuthFetchMock(() => undefined, {
tokenResponse: () =>
responseJson({
access_token: "access-token",
expires_in: Number.MAX_SAFE_INTEGER,
}),
});
const { refreshTokensForGeminiCli } = await import("./oauth.token.js");
const result = await refreshTokensForGeminiCli({
refresh: "refresh-token",
email: "lobster@openclaw.ai",
});
expect(Number.isSafeInteger(result.expires)).toBe(true);
expect(result.expires).toBeLessThanOrEqual(beforeRefresh);
});
});

View File

@@ -1,9 +1,12 @@
import { resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
import { resolveOAuthClientConfig } from "./oauth.credentials.js";
import { fetchWithTimeout } from "./oauth.http.js";
import { resolveGoogleOAuthIdentity, resolveGooglePersonalOAuthIdentity } from "./oauth.project.js";
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;
async function requestTokenGrant(body: URLSearchParams): Promise<{
access_token?: string;
refresh_token?: string;
@@ -31,11 +34,11 @@ async function requestTokenGrant(body: URLSearchParams): Promise<{
};
}
function resolveExpiresInMs(value: unknown): number {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return 0;
}
return Math.trunc(value * 1000);
function resolveTokenExpiresAt(value: unknown): number {
return (
resolveExpiresAtMsFromDurationSeconds(value, { bufferMs: TOKEN_EXPIRY_BUFFER_MS }) ??
Date.now() - TOKEN_EXPIRY_BUFFER_MS
);
}
async function buildGeminiCliCredentials(params: {
@@ -70,8 +73,7 @@ async function buildGeminiCliCredentials(params: {
// already-stored identity binding instead of failing token renewal.
}
const expiresInMs = resolveExpiresInMs(params.tokenResponse.expires_in);
const expiresAt = Date.now() + expiresInMs - 5 * 60 * 1000;
const expiresAt = resolveTokenExpiresAt(params.tokenResponse.expires_in);
return {
refresh: params.tokenResponse.refresh_token ?? params.refreshTokenFallback ?? "",