mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 16:14:05 +00:00
fix(plugin-sdk): bound copilot token expiry
This commit is contained in:
@@ -190,4 +190,74 @@ describe("provider auth profile helpers", () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects Copilot token expiry values outside the supported date range", async () => {
|
||||
vi.resetModules();
|
||||
|
||||
const fetchImpl = vi.fn(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
token: "token;proxy-ep=proxy.individual.githubcopilot.com",
|
||||
expires_at: Number.MAX_SAFE_INTEGER,
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const { resolveCopilotApiToken } = await import("./provider-auth.js");
|
||||
|
||||
await expect(
|
||||
resolveCopilotApiToken({
|
||||
githubToken: "github-token",
|
||||
fetchImpl,
|
||||
cachePath: "/tmp/copilot-token.json",
|
||||
loadJsonFileImpl: () => undefined,
|
||||
saveJsonFileImpl: () => {
|
||||
throw new Error("should not save invalid token");
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("Copilot token response has invalid expires_at");
|
||||
});
|
||||
|
||||
it("refreshes cached Copilot tokens with out-of-range expiry values", async () => {
|
||||
vi.resetModules();
|
||||
|
||||
const saved: unknown[] = [];
|
||||
const fetchImpl = vi.fn(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
token: "fresh;proxy-ep=proxy.individual.githubcopilot.com",
|
||||
expires_at: "+2000000000",
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
);
|
||||
|
||||
const { COPILOT_INTEGRATION_ID, resolveCopilotApiToken } = await import("./provider-auth.js");
|
||||
|
||||
const result = await resolveCopilotApiToken({
|
||||
githubToken: "github-token",
|
||||
fetchImpl,
|
||||
cachePath: "/tmp/copilot-token.json",
|
||||
loadJsonFileImpl: () => ({
|
||||
token: "cached;proxy-ep=proxy.individual.githubcopilot.com",
|
||||
expiresAt: Number.MAX_SAFE_INTEGER,
|
||||
updatedAt: Date.now(),
|
||||
integrationId: COPILOT_INTEGRATION_ID,
|
||||
}),
|
||||
saveJsonFileImpl: (_path, value) => saved.push(value),
|
||||
});
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
||||
expect(result.source).toBe("fetched:https://api.github.com/copilot_internal/v2/token");
|
||||
expect(result.token).toBe("fresh;proxy-ep=proxy.individual.githubcopilot.com");
|
||||
expect(saved).toEqual([
|
||||
expect.objectContaining({
|
||||
expiresAt: 2_000_000_000_000,
|
||||
token: "fresh;proxy-ep=proxy.individual.githubcopilot.com",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,11 @@ import { resolveEnvApiKey } from "../agents/model-auth-env.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { loadJsonFile, saveJsonFile } from "../infra/json-file.js";
|
||||
import { parseStrictNonNegativeInteger } from "../infra/parse-finite-number.js";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
resolveExpiresAtMsFromEpochSeconds,
|
||||
parseStrictNonNegativeInteger,
|
||||
} from "../shared/number-coercion.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { resolveProviderEndpoint } from "./provider-model-shared.js";
|
||||
|
||||
@@ -141,7 +145,27 @@ function resolveCopilotTokenCachePath(env: NodeJS.ProcessEnv = process.env) {
|
||||
}
|
||||
|
||||
function isCopilotTokenUsable(cache: CachedCopilotToken, now = Date.now()): boolean {
|
||||
return cache.integrationId === COPILOT_INTEGRATION_ID && cache.expiresAt - now > 5 * 60 * 1000;
|
||||
const expiresAt = asDateTimestampMs(cache.expiresAt);
|
||||
return (
|
||||
cache.integrationId === COPILOT_INTEGRATION_ID &&
|
||||
expiresAt !== undefined &&
|
||||
expiresAt - now > 5 * 60 * 1000
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCopilotTokenExpiresAtMs(expiresAt: unknown): number | undefined {
|
||||
const parsed =
|
||||
typeof expiresAt === "number" && Number.isFinite(expiresAt)
|
||||
? expiresAt
|
||||
: typeof expiresAt === "string" && expiresAt.trim().length > 0
|
||||
? parseStrictNonNegativeInteger(expiresAt)
|
||||
: undefined;
|
||||
if (parsed === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed < 100_000_000_000
|
||||
? resolveExpiresAtMsFromEpochSeconds(parsed)
|
||||
: asDateTimestampMs(parsed);
|
||||
}
|
||||
|
||||
function parseCopilotTokenResponse(value: unknown): {
|
||||
@@ -158,18 +182,17 @@ function parseCopilotTokenResponse(value: unknown): {
|
||||
throw new Error("Copilot token response missing token");
|
||||
}
|
||||
|
||||
let expiresAtMs: number;
|
||||
if (typeof expiresAt === "number" && Number.isFinite(expiresAt)) {
|
||||
expiresAtMs = expiresAt < 100_000_000_000 ? expiresAt * 1000 : expiresAt;
|
||||
} else if (typeof expiresAt === "string" && expiresAt.trim().length > 0) {
|
||||
const parsed = parseStrictNonNegativeInteger(expiresAt);
|
||||
if (parsed === undefined) {
|
||||
throw new Error("Copilot token response has invalid expires_at");
|
||||
}
|
||||
expiresAtMs = parsed < 100_000_000_000 ? parsed * 1000 : parsed;
|
||||
} else {
|
||||
const expiresAtMs = resolveCopilotTokenExpiresAtMs(expiresAt);
|
||||
if (
|
||||
expiresAt === undefined ||
|
||||
expiresAt === null ||
|
||||
(typeof expiresAt === "string" && expiresAt.trim().length === 0)
|
||||
) {
|
||||
throw new Error("Copilot token response missing expires_at");
|
||||
}
|
||||
if (expiresAtMs === undefined) {
|
||||
throw new Error("Copilot token response has invalid expires_at");
|
||||
}
|
||||
|
||||
return { token, expiresAt: expiresAtMs };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user