mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 09:48:39 +00:00
fix(openai-codex-oauth): stop mutating authorize url scopes
This commit is contained in:
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- OpenAI Codex OAuth/auth URL integrity: stop rewriting Pi-generated OAuth authorize URLs during browser handoff so provider-signed authorization requests remain valid; keep post-login missing-scope detection for actionable remediation. Thanks @vignesh for the report.
|
||||||
- Onboarding/headless Linux daemon probe hardening: treat `systemctl --user is-enabled` probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.
|
- Onboarding/headless Linux daemon probe hardening: treat `systemctl --user is-enabled` probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.
|
||||||
- Memory/QMD mcporter Windows spawn hardening: when `mcporter.cmd` launch fails with `spawn EINVAL`, retry via bare `mcporter` shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.
|
- Memory/QMD mcporter Windows spawn hardening: when `mcporter.cmd` launch fails with `spawn EINVAL`, retry via bare `mcporter` shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.
|
||||||
- Tools/web_search Brave language-code validation: align `search_lang` handling with Brave-supported codes (including `zh-hans`, `zh-hant`, `en-gb`, and `pt-br`), map common alias inputs (`zh`, `ja`) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
|
- Tools/web_search Brave language-code validation: align `search_lang` handling with Brave-supported codes (including `zh-hans`, `zh-hant`, `en-gb`, and `pt-br`), map common alias inputs (`zh`, `ja`) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("loginOpenAICodexOAuth", () => {
|
|||||||
expect(runtime.error).not.toHaveBeenCalled();
|
expect(runtime.error).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("augments OAuth authorize URL with required OpenAI API scopes", async () => {
|
it("passes through Pi-provided OAuth authorize URL without mutation", async () => {
|
||||||
const creds = {
|
const creds = {
|
||||||
provider: "openai-codex" as const,
|
provider: "openai-codex" as const,
|
||||||
access: "access-token",
|
access: "access-token",
|
||||||
@@ -130,14 +130,9 @@ describe("loginOpenAICodexOAuth", () => {
|
|||||||
|
|
||||||
expect(onAuthSpy).toHaveBeenCalledTimes(1);
|
expect(onAuthSpy).toHaveBeenCalledTimes(1);
|
||||||
const event = onAuthSpy.mock.calls[0]?.[0] as { url: string };
|
const event = onAuthSpy.mock.calls[0]?.[0] as { url: string };
|
||||||
const scopes = new Set((new URL(event.url).searchParams.get("scope") ?? "").split(/\s+/));
|
expect(event.url).toBe(
|
||||||
expect(scopes.has("openid")).toBe(true);
|
"https://auth.openai.com/oauth/authorize?scope=openid+profile+email+offline_access&state=abc",
|
||||||
expect(scopes.has("profile")).toBe(true);
|
);
|
||||||
expect(scopes.has("email")).toBe(true);
|
|
||||||
expect(scopes.has("offline_access")).toBe(true);
|
|
||||||
expect(scopes.has("api.responses.write")).toBe(true);
|
|
||||||
expect(scopes.has("model.request")).toBe(true);
|
|
||||||
expect(scopes.has("api.model.read")).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports oauth errors and rethrows", async () => {
|
it("reports oauth errors and rethrows", async () => {
|
||||||
|
|||||||
@@ -10,46 +10,6 @@ import {
|
|||||||
|
|
||||||
const OPENAI_RESPONSES_ENDPOINT = "https://api.openai.com/v1/responses";
|
const OPENAI_RESPONSES_ENDPOINT = "https://api.openai.com/v1/responses";
|
||||||
const OPENAI_RESPONSES_WRITE_SCOPE = "api.responses.write";
|
const OPENAI_RESPONSES_WRITE_SCOPE = "api.responses.write";
|
||||||
const OPENAI_REQUIRED_OAUTH_SCOPES = [
|
|
||||||
OPENAI_RESPONSES_WRITE_SCOPE,
|
|
||||||
"model.request",
|
|
||||||
"api.model.read",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
function augmentOpenAIOAuthScopes(authUrl: string): string {
|
|
||||||
try {
|
|
||||||
const parsed = new URL(authUrl);
|
|
||||||
const scopeParam = parsed.searchParams.get("scope");
|
|
||||||
if (!scopeParam) {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
const scopes = scopeParam
|
|
||||||
.split(/\s+/)
|
|
||||||
.map((scope) => scope.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
if (scopes.length === 0) {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
const seen = new Set(scopes.map((scope) => scope.toLowerCase()));
|
|
||||||
let changed = false;
|
|
||||||
for (const requiredScope of OPENAI_REQUIRED_OAUTH_SCOPES) {
|
|
||||||
const normalized = requiredScope.toLowerCase();
|
|
||||||
if (seen.has(normalized)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
scopes.push(requiredScope);
|
|
||||||
seen.add(normalized);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (!changed) {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
parsed.searchParams.set("scope", scopes.join(" "));
|
|
||||||
return parsed.toString();
|
|
||||||
} catch {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractResponsesScopeErrorMessage(status: number, bodyText: string): string | null {
|
function extractResponsesScopeErrorMessage(status: number, bodyText: string): string | null {
|
||||||
if (status !== 401) {
|
if (status !== 401) {
|
||||||
@@ -124,15 +84,9 @@ export async function loginOpenAICodexOAuth(params: {
|
|||||||
openUrl,
|
openUrl,
|
||||||
localBrowserMessage: localBrowserMessage ?? "Complete sign-in in browser…",
|
localBrowserMessage: localBrowserMessage ?? "Complete sign-in in browser…",
|
||||||
});
|
});
|
||||||
const onAuth = async (event: { url: string }) => {
|
|
||||||
await baseOnAuth({
|
|
||||||
...event,
|
|
||||||
url: augmentOpenAIOAuthScopes(event.url),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const creds = await loginOpenAICodex({
|
const creds = await loginOpenAICodex({
|
||||||
onAuth,
|
onAuth: baseOnAuth,
|
||||||
onPrompt,
|
onPrompt,
|
||||||
onProgress: (msg) => spin.update(msg),
|
onProgress: (msg) => spin.update(msg),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user