Skip to content

Commit c6e1276

Browse files
committed
fix(auth): accept oauthRef profiles for runtime auth
1 parent 78eb92e commit c6e1276

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

extensions/codex/src/app-server/auth-bridge.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
bridgeCodexAppServerStartOptions,
1313
refreshCodexAppServerAuthTokens,
1414
resolveCodexAppServerAuthAccountCacheKey,
15+
resolveCodexAppServerAuthProfileId,
1516
resolveCodexAppServerHomeDir,
1617
resolveCodexAppServerNativeHomeDir,
1718
} from "./auth-bridge.js";
@@ -651,6 +652,30 @@ describe("bridgeCodexAppServerStartOptions", () => {
651652
}
652653
});
653654

655+
it("selects an oauthRef-backed Codex profile for app-server login", () => {
656+
expect(
657+
resolveCodexAppServerAuthProfileId({
658+
store: {
659+
version: 1,
660+
profiles: {
661+
"openai-codex:default": {
662+
type: "oauth",
663+
provider: "openai-codex",
664+
access: "",
665+
refresh: "",
666+
expires: Date.now() + 60_000,
667+
oauthRef: {
668+
source: "openclaw-credentials",
669+
provider: "openai-codex",
670+
id: "0123456789abcdef0123456789abcdef",
671+
},
672+
},
673+
},
674+
},
675+
}),
676+
).toBe("openai-codex:default");
677+
});
678+
654679
it("applies native Codex CLI OAuth when no OpenClaw auth profile exists", async () => {
655680
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
656681
const agentDir = path.join(root, "agent");

src/agents/auth-profiles/credential-state.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,23 @@ describe("evaluateStoredCredentialEligibility", () => {
103103
});
104104
expect(result).toEqual({ eligible: false, reasonCode: "invalid_expires" });
105105
});
106+
107+
it("marks oauth with oauthRef as eligible", () => {
108+
const result = evaluateStoredCredentialEligibility({
109+
credential: {
110+
type: "oauth",
111+
provider: "openai-codex",
112+
access: "",
113+
refresh: "",
114+
expires: now + 60_000,
115+
oauthRef: {
116+
source: "openclaw-credentials",
117+
provider: "openai-codex",
118+
id: "0123456789abcdef0123456789abcdef",
119+
},
120+
},
121+
now,
122+
});
123+
expect(result).toEqual({ eligible: true, reasonCode: "ok" });
124+
});
106125
});

src/agents/auth-profiles/credential-state.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { coerceSecretRef, normalizeSecretInputString } from "../../config/types.secrets.js";
2-
import type { AuthProfileCredential, OAuthCredential } from "./types.js";
2+
import type { AuthProfileCredential, OAuthCredential, OAuthCredentialRef } from "./types.js";
33

44
export type AuthCredentialReasonCode =
55
| "ok"
@@ -69,6 +69,15 @@ function hasConfiguredSecretString(value: unknown): boolean {
6969
return normalizeSecretInputString(value) !== undefined;
7070
}
7171

72+
function hasConfiguredOAuthRef(value: OAuthCredentialRef | undefined): boolean {
73+
return (
74+
value?.source === "openclaw-credentials" &&
75+
value.provider === "openai-codex" &&
76+
typeof value.id === "string" &&
77+
/^[a-f0-9]{32}$/.test(value.id)
78+
);
79+
}
80+
7281
export function evaluateStoredCredentialEligibility(params: {
7382
credential: AuthProfileCredential;
7483
now?: number;
@@ -104,7 +113,8 @@ export function evaluateStoredCredentialEligibility(params: {
104113

105114
if (
106115
normalizeSecretInputString(credential.access) === undefined &&
107-
normalizeSecretInputString(credential.refresh) === undefined
116+
normalizeSecretInputString(credential.refresh) === undefined &&
117+
!hasConfiguredOAuthRef(credential.oauthRef)
108118
) {
109119
return { eligible: false, reasonCode: "missing_credential" };
110120
}

src/agents/auth-profiles/order.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,33 @@ describe("resolveAuthProfileOrder", () => {
276276
expect(order).toEqual(["openai-codex:personal", "openai:backup"]);
277277
});
278278

279+
it("lets Codex auth discover oauthRef-backed OAuth profiles", async () => {
280+
const store: AuthProfileStore = {
281+
version: 1,
282+
profiles: {
283+
"openai-codex:personal": {
284+
type: "oauth",
285+
provider: "openai-codex",
286+
access: "",
287+
refresh: "",
288+
expires: Date.now() + 60_000,
289+
oauthRef: {
290+
source: "openclaw-credentials",
291+
provider: "openai-codex",
292+
id: "0123456789abcdef0123456789abcdef",
293+
},
294+
},
295+
},
296+
};
297+
298+
const order = resolveAuthProfileOrder({
299+
store,
300+
provider: "openai-codex",
301+
});
302+
303+
expect(order).toEqual(["openai-codex:personal"]);
304+
});
305+
279306
it("preserves native Codex profiles before OpenAI alias API-key order", async () => {
280307
const store: AuthProfileStore = {
281308
version: 1,

0 commit comments

Comments
 (0)