fix: register a real Firefly auth probe for the firefly/adobe-firefly alias pair (#10522) (#10743)

Co-authored-by: Markus Hartung <mail@hartmark.se>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-19 11:08:17 -03:00
committed by GitHub
parent b755dd5e74
commit d6c4fec2ee
4 changed files with 143 additions and 2 deletions

View File

@@ -0,0 +1 @@
- fix(providers): register a real Firefly auth probe under both the `firefly` alias and the `adobe-firefly` canonical id, and normalize the provider id before the generic web-cookie fallback, so a Firefly connection stops always reporting "Provider validation not supported" (#10522)

View File

@@ -7,6 +7,7 @@ import {
isOpenAICompatibleProvider,
isSelfHostedChatProvider,
providerAllowsOptionalApiKey,
resolveProviderId,
WEB_COOKIE_PROVIDERS,
} from "@/shared/constants/providers";
import { MODAL_DEFAULT_VALIDATION_MODEL_ID } from "@/shared/constants/modal";
@@ -107,6 +108,7 @@ import {
validateBytezProvider,
} from "./validation/webCookie";
import { validateAiHordeProvider } from "./validation/aihorde";
import { validateAdobeFireflyProvider } from "./validation/adobeFirefly";
import {
validateV0VercelProvider,
validateAuggieProvider,
@@ -184,6 +186,11 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
devin: validateDevinCloudAgentProvider,
auggie: validateAuggieProvider,
aihorde: validateAiHordeProvider,
// #10522: registered under both the canonical id and the short alias — Firefly
// connections are commonly stored as "firefly" (same prefix as firefly/<model>
// routing ids), not the canonical "adobe-firefly" WEB_COOKIE_PROVIDERS key.
"adobe-firefly": validateAdobeFireflyProvider,
firefly: validateAdobeFireflyProvider,
qoder: validateQoderProvider,
kiro: validateKiroProvider,
"command-code": validateCommandCodeProvider,
@@ -328,9 +335,14 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
// per-provider validator (grok-web, chatgpt-web, claude-web, …) are handled by
// SPECIALTY_VALIDATORS first and must not be shadowed by this generic probe (issue: the
// #4023 dispatch was placed too early and intercepted every web-cookie provider).
if (WEB_COOKIE_PROVIDERS[provider]) {
const canonicalProvider = resolveProviderId(provider);
if (WEB_COOKIE_PROVIDERS[canonicalProvider]) {
try {
return await validateWebCookieProvider({ provider, apiKey, providerSpecificData });
return await validateWebCookieProvider({
provider: canonicalProvider,
apiKey,
providerSpecificData,
});
} catch (error: any) {
return toValidationErrorResult(error);
}

View File

@@ -0,0 +1,38 @@
/**
* Adobe Firefly key/cookie validation (#10522).
*
* validateProviderApiKey() previously had no SPECIALTY_VALIDATORS entry for
* "firefly"/"adobe-firefly" and the generic WEB_COOKIE_PROVIDERS fallback used a raw,
* unaliased lookup — so a Firefly connection always fell through to the "Provider
* validation not supported" branch regardless of whether the pasted cookie/JWT was
* valid. This reuses the already-working credits/balance probe
* (getAdobeFireflyUsage, used today by the Limits/quota page) as a real auth check:
* a successful balance fetch means the token is valid, a `{ message }` result means
* it was rejected (expired/guest/invalid), and thrown transport errors are mapped
* through the shared toValidationErrorResult() helper.
*/
import { getAdobeFireflyUsage } from "@omniroute/open-sse/services/usage/adobeFirefly.ts";
import { toValidationErrorResult } from "./transport";
export async function validateAdobeFireflyProvider({
apiKey,
providerSpecificData,
fetchImpl = fetch,
}: {
apiKey?: unknown;
providerSpecificData?: Record<string, unknown> | null;
fetchImpl?: typeof fetch;
}) {
try {
const key = typeof apiKey === "string" ? apiKey : undefined;
const result = await getAdobeFireflyUsage(key, undefined, providerSpecificData, fetchImpl);
if ("message" in result) {
return { valid: false, error: result.message };
}
return { valid: true, error: null };
} catch (error) {
return toValidationErrorResult(error);
}
}

View File

@@ -0,0 +1,90 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { validateProviderApiKey } from "../../src/lib/providers/validation.ts";
import { validateAdobeFireflyProvider } from "../../src/lib/providers/validation/adobeFirefly.ts";
import { resolveProviderId } from "../../src/shared/constants/providers.ts";
import { ADOBE_FIREFLY_CREDITS_BALANCE_URL } from "../../open-sse/services/adobeFireflyClient.ts";
// A well-formed Adobe IMS *user* access token (3-segment JWT, non-guest payload, long
// enough to satisfy looksLikeAdobeJwt). Only used as a routing/shape fixture — never a
// real credential.
const userJwt =
`eyJhbGciOiJSUzI1NiJ9.` +
Buffer.from(
JSON.stringify({
user_id: "0EB@AdobeID",
type: "access_token",
client_id: "clio-playground-web",
})
).toString("base64url") +
`.` +
"x".repeat(60);
function jsonResponse(status: number, body: unknown) {
return new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
});
}
test("#10522: resolveProviderId('firefly') stays stable (SPECIALTY_VALIDATORS dual-key registration depends on it)", () => {
assert.equal(resolveProviderId("firefly"), "adobe-firefly");
});
test("#10522: firefly alias dispatches to the Firefly validator, not the generic unsupported fallback", async () => {
const result = await validateProviderApiKey({
provider: "firefly",
apiKey: "not-a-real-token",
providerSpecificData: {},
});
assert.notEqual(result.unsupported, true);
});
test("#10522: adobe-firefly canonical id dispatches to the Firefly validator, not the generic unsupported fallback", async () => {
const result = await validateProviderApiKey({
provider: "adobe-firefly",
apiKey: "not-a-real-token",
providerSpecificData: {},
});
assert.notEqual(result.unsupported, true);
});
test("#10522: expired/invalid token reports valid:false with a real error message (not 'not supported')", async () => {
const fetchImpl = async (url: string | URL) => {
assert.equal(String(url), ADOBE_FIREFLY_CREDITS_BALANCE_URL);
return jsonResponse(401, { error: "invalid_token" });
};
const result = await validateAdobeFireflyProvider({
apiKey: userJwt,
providerSpecificData: {},
fetchImpl: fetchImpl as typeof fetch,
});
assert.equal(result.valid, false);
assert.notEqual((result as { unsupported?: boolean }).unsupported, true);
assert.ok(result.error && result.error.length > 0);
});
test("#10522: a genuine credits balance payload reports valid:true", async () => {
const fetchImpl = async (url: string | URL) => {
assert.equal(String(url), ADOBE_FIREFLY_CREDITS_BALANCE_URL);
return jsonResponse(200, {
total: { quota: { total: 100, used: 10, available: 90 } },
credits: {
firefly_free_credit: { quota: { total: 50, used: 5, available: 45 } },
firefly_plan_credit: { quota: { total: 50, used: 5, available: 45 } },
},
});
};
const result = await validateAdobeFireflyProvider({
apiKey: userJwt,
providerSpecificData: {},
fetchImpl: fetchImpl as typeof fetch,
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
});