fix(auth): accept oauthRef profiles for runtime auth

This commit is contained in:
Ayaan Zaidi
2026-05-14 08:53:34 +05:30
parent c04bbd3cbb
commit df4aac8f96
4 changed files with 83 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ import {
bridgeCodexAppServerStartOptions,
refreshCodexAppServerAuthTokens,
resolveCodexAppServerAuthAccountCacheKey,
resolveCodexAppServerAuthProfileId,
resolveCodexAppServerHomeDir,
resolveCodexAppServerNativeHomeDir,
} from "./auth-bridge.js";
@@ -651,6 +652,30 @@ describe("bridgeCodexAppServerStartOptions", () => {
}
});
it("selects an oauthRef-backed Codex profile for app-server login", () => {
expect(
resolveCodexAppServerAuthProfileId({
store: {
version: 1,
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
access: "",
refresh: "",
expires: Date.now() + 60_000,
oauthRef: {
source: "openclaw-credentials",
provider: "openai-codex",
id: "0123456789abcdef0123456789abcdef",
},
},
},
},
}),
).toBe("openai-codex:default");
});
it("applies native Codex CLI OAuth when no OpenClaw auth profile exists", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
const agentDir = path.join(root, "agent");

View File

@@ -103,4 +103,23 @@ describe("evaluateStoredCredentialEligibility", () => {
});
expect(result).toEqual({ eligible: false, reasonCode: "invalid_expires" });
});
it("marks oauth with oauthRef as eligible", () => {
const result = evaluateStoredCredentialEligibility({
credential: {
type: "oauth",
provider: "openai-codex",
access: "",
refresh: "",
expires: now + 60_000,
oauthRef: {
source: "openclaw-credentials",
provider: "openai-codex",
id: "0123456789abcdef0123456789abcdef",
},
},
now,
});
expect(result).toEqual({ eligible: true, reasonCode: "ok" });
});
});

View File

@@ -1,5 +1,5 @@
import { coerceSecretRef, normalizeSecretInputString } from "../../config/types.secrets.js";
import type { AuthProfileCredential, OAuthCredential } from "./types.js";
import type { AuthProfileCredential, OAuthCredential, OAuthCredentialRef } from "./types.js";
export type AuthCredentialReasonCode =
| "ok"
@@ -69,6 +69,15 @@ function hasConfiguredSecretString(value: unknown): boolean {
return normalizeSecretInputString(value) !== undefined;
}
function hasConfiguredOAuthRef(value: OAuthCredentialRef | undefined): boolean {
return (
value?.source === "openclaw-credentials" &&
value.provider === "openai-codex" &&
typeof value.id === "string" &&
/^[a-f0-9]{32}$/.test(value.id)
);
}
export function evaluateStoredCredentialEligibility(params: {
credential: AuthProfileCredential;
now?: number;
@@ -104,7 +113,8 @@ export function evaluateStoredCredentialEligibility(params: {
if (
normalizeSecretInputString(credential.access) === undefined &&
normalizeSecretInputString(credential.refresh) === undefined
normalizeSecretInputString(credential.refresh) === undefined &&
!hasConfiguredOAuthRef(credential.oauthRef)
) {
return { eligible: false, reasonCode: "missing_credential" };
}

View File

@@ -276,6 +276,33 @@ describe("resolveAuthProfileOrder", () => {
expect(order).toEqual(["openai-codex:personal", "openai:backup"]);
});
it("lets Codex auth discover oauthRef-backed OAuth profiles", async () => {
const store: AuthProfileStore = {
version: 1,
profiles: {
"openai-codex:personal": {
type: "oauth",
provider: "openai-codex",
access: "",
refresh: "",
expires: Date.now() + 60_000,
oauthRef: {
source: "openclaw-credentials",
provider: "openai-codex",
id: "0123456789abcdef0123456789abcdef",
},
},
},
};
const order = resolveAuthProfileOrder({
store,
provider: "openai-codex",
});
expect(order).toEqual(["openai-codex:personal"]);
});
it("preserves native Codex profiles before OpenAI alias API-key order", async () => {
const store: AuthProfileStore = {
version: 1,