mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 01:10:22 +00:00
fix(auth): persist codex oauth refresh tokens
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
ensureAuthProfileStore,
|
||||
saveAuthProfileStore,
|
||||
} from "./store.js";
|
||||
import type { AuthProfileStore } from "./types.js";
|
||||
import type { AuthProfileStore, OAuthCredential } from "./types.js";
|
||||
let resolveApiKeyForProfile: typeof import("./oauth.js").resolveApiKeyForProfile;
|
||||
|
||||
const { getOAuthApiKeyMock } = vi.hoisted(() => ({
|
||||
@@ -24,7 +24,7 @@ const {
|
||||
buildProviderAuthDoctorHintWithPluginMock,
|
||||
} = vi.hoisted(() => ({
|
||||
refreshProviderOAuthCredentialWithPluginMock: vi.fn(
|
||||
async (_params?: { context?: unknown }) => undefined,
|
||||
async (_params?: { context?: unknown }): Promise<OAuthCredential | undefined> => undefined,
|
||||
),
|
||||
formatProviderAuthProfileApiKeyWithPluginMock: vi.fn(() => undefined),
|
||||
buildProviderAuthDoctorHintWithPluginMock: vi.fn(async () => undefined),
|
||||
@@ -61,6 +61,12 @@ async function loadFreshOAuthModuleForTest() {
|
||||
({ resolveApiKeyForProfile } = await import("./oauth.js"));
|
||||
}
|
||||
|
||||
async function readPersistedStore(agentDir: string): Promise<AuthProfileStore> {
|
||||
return JSON.parse(
|
||||
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8"),
|
||||
) as AuthProfileStore;
|
||||
}
|
||||
|
||||
function createExpiredOauthStore(params: {
|
||||
profileId: string;
|
||||
provider: string;
|
||||
@@ -142,6 +148,47 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => {
|
||||
expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("persists plugin-refreshed openai-codex credentials before returning", async () => {
|
||||
const profileId = "openai-codex:default";
|
||||
saveAuthProfileStore(
|
||||
createExpiredOauthStore({
|
||||
profileId,
|
||||
provider: "openai-codex",
|
||||
access: "stale-access-token",
|
||||
}),
|
||||
agentDir,
|
||||
);
|
||||
refreshProviderOAuthCredentialWithPluginMock.mockResolvedValueOnce({
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "rotated-access-token",
|
||||
refresh: "rotated-refresh-token",
|
||||
expires: Date.now() + 86_400_000,
|
||||
accountId: "acct-rotated",
|
||||
});
|
||||
|
||||
const result = await resolveApiKeyForProfile({
|
||||
store: ensureAuthProfileStore(agentDir),
|
||||
profileId,
|
||||
agentDir,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
apiKey: "rotated-access-token",
|
||||
provider: "openai-codex",
|
||||
email: undefined,
|
||||
});
|
||||
|
||||
const persisted = await readPersistedStore(agentDir);
|
||||
expect(persisted.profiles[profileId]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "rotated-access-token",
|
||||
refresh: "rotated-refresh-token",
|
||||
accountId: "acct-rotated",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps throwing for non-codex providers on the same refresh error", async () => {
|
||||
const profileId = "anthropic:default";
|
||||
saveAuthProfileStore(
|
||||
|
||||
@@ -185,9 +185,16 @@ async function refreshOAuthTokenWithLock(params: {
|
||||
context: cred,
|
||||
});
|
||||
if (pluginRefreshed) {
|
||||
const refreshedCredentials: OAuthCredential = {
|
||||
...cred,
|
||||
...pluginRefreshed,
|
||||
type: "oauth",
|
||||
};
|
||||
store.profiles[params.profileId] = refreshedCredentials;
|
||||
saveAuthProfileStore(store, params.agentDir);
|
||||
return {
|
||||
apiKey: await buildOAuthApiKey(cred.provider, pluginRefreshed),
|
||||
newCredentials: pluginRefreshed,
|
||||
apiKey: await buildOAuthApiKey(cred.provider, refreshedCredentials),
|
||||
newCredentials: refreshedCredentials,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user