fix: reuse provider auth hook lookup context

This commit is contained in:
Shakker
2026-05-02 04:41:51 +01:00
parent c2a2161404
commit 854323a124
3 changed files with 90 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { captureEnv } from "../../test-utils/env.js";
import { __testing as externalAuthTesting } from "./external-auth.js";
import {
@@ -146,6 +147,44 @@ describe("OAuthManagerRefreshError", () => {
});
describe("createOAuthManager", () => {
it("passes active config to OAuth API-key formatting", async () => {
const profileId = "openai-codex:default";
const credential = createCredential({ expires: Date.now() + 10 * 60_000 });
const cfg = {
models: {
providers: {
"openai-codex": { auth: "oauth", models: [] },
},
},
} satisfies OpenClawConfig;
const buildApiKey = vi.fn(async (_provider, value: OAuthCredential) => value.access);
const manager = createOAuthManager({
buildApiKey,
refreshCredential: vi.fn(async () => null),
readBootstrapCredential: () => null,
isRefreshTokenReusedError: () => false,
});
await expect(
manager.resolveOAuthAccess({
store: {
version: 1,
profiles: {
[profileId]: credential,
},
},
profileId,
credential,
cfg,
}),
).resolves.toMatchObject({ apiKey: "access-token" });
expect(buildApiKey).toHaveBeenCalledWith("openai-codex", credential, {
cfg,
agentDir: undefined,
});
});
it("does not overlay external auth while checking main-store adoption", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "oauth-manager-main-adopt-"));
tempDirs.push(tempRoot);

View File

@@ -1,3 +1,4 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { withFileLock } from "../../infra/file-lock.js";
import {
@@ -34,7 +35,11 @@ import {
import type { AuthProfileStore, OAuthCredential, OAuthCredentials } from "./types.js";
export type OAuthManagerAdapter = {
buildApiKey: (provider: string, credentials: OAuthCredential) => Promise<string>;
buildApiKey: (
provider: string,
credentials: OAuthCredential,
context: { cfg?: OpenClawConfig; agentDir?: string },
) => Promise<string>;
refreshCredential: (credential: OAuthCredential) => Promise<OAuthCredentials | null>;
readBootstrapCredential: (params: {
profileId: string;
@@ -318,6 +323,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
profileId: string;
provider: string;
agentDir?: string;
cfg?: OpenClawConfig;
}): Promise<ResolvedOAuthAccess | null> {
const ownerAgentDir = resolvePersistedAuthProfileOwnerAgentDir(params);
const authPath = resolveAuthStorePath(ownerAgentDir);
@@ -336,7 +342,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
if (hasUsableOAuthCredential(cred)) {
return {
apiKey: await adapter.buildApiKey(cred.provider, cred),
apiKey: await adapter.buildApiKey(cred.provider, cred, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: cred,
};
}
@@ -358,7 +367,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
expires: new Date(mainCred.expires).toISOString(),
});
return {
apiKey: await adapter.buildApiKey(mainCred.provider, mainCred),
apiKey: await adapter.buildApiKey(mainCred.provider, mainCred, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: mainCred,
};
} else if (
@@ -409,7 +421,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
credentialToRefresh = externallyManaged;
if (hasUsableOAuthCredential(externallyManaged)) {
return {
apiKey: await adapter.buildApiKey(externallyManaged.provider, externallyManaged),
apiKey: await adapter.buildApiKey(externallyManaged.provider, externallyManaged, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: externallyManaged,
};
}
@@ -445,7 +460,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
}
}
return {
apiKey: await adapter.buildApiKey(cred.provider, refreshedCredentials),
apiKey: await adapter.buildApiKey(cred.provider, refreshedCredentials, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: refreshedCredentials,
};
}),
@@ -466,6 +484,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
profileId: string;
provider: string;
agentDir?: string;
cfg?: OpenClawConfig;
}): Promise<ResolvedOAuthAccess | null> {
const key = refreshQueueKey(params.provider, params.profileId);
const prev = refreshQueues.get(key) ?? Promise.resolve();
@@ -490,6 +509,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
profileId: string;
credential: OAuthCredential;
agentDir?: string;
cfg?: OpenClawConfig;
}): Promise<ResolvedOAuthAccess | null> {
const adoptedCredential =
adoptNewerMainOAuthCredential({
@@ -506,7 +526,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
if (hasUsableOAuthCredential(effectiveCredential)) {
return {
apiKey: await adapter.buildApiKey(effectiveCredential.provider, effectiveCredential),
apiKey: await adapter.buildApiKey(effectiveCredential.provider, effectiveCredential, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: effectiveCredential,
};
}
@@ -516,6 +539,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
profileId: params.profileId,
provider: params.credential.provider,
agentDir: params.agentDir,
cfg: params.cfg,
});
return refreshed;
} catch (error) {
@@ -523,7 +547,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
const refreshed = refreshedStore.profiles[params.profileId];
if (refreshed?.type === "oauth" && hasUsableOAuthCredential(refreshed)) {
return {
apiKey: await adapter.buildApiKey(refreshed.provider, refreshed),
apiKey: await adapter.buildApiKey(refreshed.provider, refreshed, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: refreshed,
};
}
@@ -542,7 +569,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
});
if (recovered) {
return {
apiKey: await adapter.buildApiKey(recovered.provider, recovered),
apiKey: await adapter.buildApiKey(recovered.provider, recovered, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: recovered,
};
}
@@ -551,6 +581,7 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
profileId: params.profileId,
provider: params.credential.provider,
agentDir: params.agentDir,
cfg: params.cfg,
});
if (retried) {
return retried;
@@ -579,7 +610,10 @@ export function createOAuthManager(adapter: OAuthManagerAdapter) {
expires: new Date(mainCred.expires).toISOString(),
});
return {
apiKey: await adapter.buildApiKey(mainCred.provider, mainCred),
apiKey: await adapter.buildApiKey(mainCred.provider, mainCred, {
cfg: params.cfg,
agentDir: params.agentDir,
}),
credential: mainCred,
};
}

View File

@@ -93,9 +93,14 @@ function isProfileConfigCompatible(params: {
return true;
}
async function buildOAuthApiKey(provider: string, credentials: OAuthCredential): Promise<string> {
async function buildOAuthApiKey(
provider: string,
credentials: OAuthCredential,
context: { cfg?: OpenClawConfig },
): Promise<string> {
const formatted = await formatProviderAuthProfileApiKeyWithPlugin({
provider,
config: context.cfg,
context: credentials,
});
return typeof formatted === "string" && formatted.length > 0 ? formatted : credentials.access;
@@ -195,6 +200,7 @@ async function tryResolveOAuthProfile(
profileId,
credential: cred,
agentDir: params.agentDir,
cfg,
});
if (!resolved) {
return null;
@@ -333,6 +339,7 @@ export async function resolveApiKeyForProfile(
agentDir: params.agentDir,
profileId,
credential: cred,
cfg,
});
if (!resolved) {
return null;