fix(zed): harden OAuth lifecycle and live model support

- executors/zed.js: use exact wire values (anthropic, open_ai, google, x_ai)
  and strip incompatible Vertex safetySettings on the Google path
- shared/zedAuth.js: robust callback query parsing, reject garbage PKCS#1 v1.5
  decryptions, and thread proxyOptions when fetching LLM tokens
- oauth: preserve systemId across authorize/register/exchange lifecycle,
  renew proxy idle timeout on reuse, and ignore non-callback localhost requests
- shared/OAuthModal.js: track owned proxy in flowRef and stop at most once
- api/providers/[id]/models: add connection-scoped live Zed model resolver
- registry: unhide provider in dashboard
- tests: add unit coverage for wire format, native auth, and live models
This commit is contained in:
Mosabbir Maruf
2026-09-17 18:46:13 +07:00
parent 725e2c1187
commit ef18175226
13 changed files with 866 additions and 105 deletions

View File

@@ -112,7 +112,14 @@ export function parseZedCallbackPayload(input) {
url = new URL(raw);
} catch {
try {
url = new URL(`http://127.0.0.1/?${raw.replace(/^\?/, "")}`);
// Accept pathname+query (what the local proxy forwards, e.g.
// "/?user_id=..&access_token=.." or "/callback?.."), a bare query,
// or a lone query string. Only the query part is parsed — a leading
// path must never become part of the first parameter name.
const query = raw.includes("?")
? raw.slice(raw.indexOf("?") + 1)
: raw.replace(/^\?/, "");
url = new URL(`http://127.0.0.1/?${query}`);
} catch {
throw new Error("Invalid Zed callback URL");
}
@@ -134,6 +141,10 @@ export function parseZedCallbackPayload(input) {
export function decryptZedAccessToken(encryptedAccessToken, privateKeyVerifier) {
const privateKey = decodeZedPrivateKeyVerifier(privateKeyVerifier);
const encrypted = Buffer.from(String(encryptedAccessToken), "base64url");
const fail = (oaepError) => {
const message = oaepError instanceof Error ? oaepError.message : String(oaepError);
throw new Error(`Failed to decrypt Zed access token: ${message}`);
};
try {
return crypto
.privateDecrypt(
@@ -143,15 +154,21 @@ export function decryptZedAccessToken(encryptedAccessToken, privateKeyVerifier)
.toString("utf8");
} catch (oaepError) {
try {
return crypto
const text = crypto
.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
encrypted,
)
.toString("utf8");
} catch {
const message = oaepError instanceof Error ? oaepError.message : String(oaepError);
throw new Error(`Failed to decrypt Zed access token: ${message}`);
// PKCS#1 v1.5 unpadding is not integrity-checked: a wrong-key decrypt
// can "succeed" with garbage bytes instead of throwing. Replacement
// characters prove the output is not the real UTF-8 token — fail loudly
// rather than storing garbage as a credential.
if (text.includes("<22>")) fail(oaepError);
return text;
} catch (err) {
if (err.message.startsWith("Failed to decrypt Zed access token")) throw err;
fail(oaepError);
}
}
}
@@ -280,6 +297,7 @@ export async function fetchZedLlmToken(credentials, options = {}) {
body: JSON.stringify({ organization_id: organizationId }),
signal: options.signal ?? undefined,
},
options.proxyOptions ?? null,
);
const token =
typeof data?.token === "string" ? data.token : data?.token?.[0] || data?.token?.value;