fix(adobe-firefly): open browser sign-in and resolve provider slug in /login (#9097)

Validated in local merge-train T5 (base49+contributors+pacocartones)
This commit is contained in:
NOXX - Commiter
2026-08-06 06:09:30 +03:00
committed by GitHub
parent 754782f620
commit a072ff4552
5 changed files with 643 additions and 8 deletions

View File

@@ -1,7 +1,7 @@
/**
* POST /api/providers/[id]/login
*
* Web-cookie provider login endpoint. Launches a Playwright browser,
* Web-cookie provider login endpoint. Launches a browser,
* navigates to the provider's login page, polls for session tokens,
* and persists extracted credentials to the provider connection.
*/
@@ -11,6 +11,15 @@ import { getCachedProviderConnectionById, updateProviderConnection } from "@/lib
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
const ADOBE_FIREFLY_SLUGS = new Set(["adobe-firefly", "firefly"]);
/** Resolve the provider slug (e.g. "claude-web", "adobe-firefly") from the connection row. */
function resolveProviderSlug(connection: Record<string, unknown> | null): string {
const raw = connection?.provider;
if (typeof raw === "string" && raw.trim()) return raw.trim();
return "";
}
// ─── POST: Start login flow ────────────────────────────────────────────────
export async function POST(
@@ -28,22 +37,75 @@ export async function POST(
const body = await req.json().catch(() => ({}));
const timeout = typeof body.timeout === "number" ? body.timeout : undefined;
const providerSlug = resolveProviderSlug(provider as Record<string, unknown>);
try {
// Dynamic import — InAppLoginService depends on Playwright (heavy)
const { inAppLoginService } = await import(
"@omniroute/open-sse/services/inAppLoginService.ts"
);
// Adobe Firefly is special: the IMS JWT is only ever in the Authorization
// header of firefly-3p.ff.adobe.io XHRs (never cookies/localStorage), so
// the generic cookie-extraction service cannot capture it. Use a dedicated
// Playwright service that intercepts that request instead.
if (ADOBE_FIREFLY_SLUGS.has(providerSlug)) {
const { startAdobeFireflyBrowserLogin } =
await import("@omniroute/open-sse/services/adobeFireflyBrowserLogin.ts");
const fireflyResult = await startAdobeFireflyBrowserLogin(timeout);
const result = await inAppLoginService.startLogin(id, { timeout });
if (fireflyResult.success && fireflyResult.credentials) {
const credentials = fireflyResult.credentials;
try {
// Store the JWT in apiKey (where resolveAdobeAccessToken looks first)
// and the cookie + access_token in providerSpecificData (camelCase —
// updateProviderConnection ignores snake_case keys).
const providerSpecificData: Record<string, string> = {};
if (credentials.accessToken) {
providerSpecificData.access_token = credentials.accessToken;
}
if (credentials.cookie) {
providerSpecificData.cookie = credentials.cookie;
}
await updateProviderConnection(id, {
apiKey: credentials.accessToken || "",
providerSpecificData,
});
return NextResponse.json({
success: true,
accessToken: credentials.accessToken || "",
cookie: credentials.cookie || "",
account: fireflyResult.account || "",
credentials: providerSpecificData,
persisted: true,
});
} catch (err) {
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : err);
return NextResponse.json(
{ success: false, error: `Extracted but failed to persist: ${msg}` },
{ status: 500 }
);
}
}
return NextResponse.json(
{ success: false, error: fireflyResult.error || "Adobe Firefly sign-in failed" },
{ status: 400 }
);
}
// Generic web-cookie path: pass the provider SLUG (not the DB id) so
// TOKEN_EXTRACTION_CONFIGS can find the extraction config.
// Bug: the previous code passed `id` (connection UUID), so the lookup always
// missed and returned "No extraction config" without launching a browser.
const { inAppLoginService } = await import("@omniroute/open-sse/services/inAppLoginService.ts");
const result = await inAppLoginService.startLogin(providerSlug || id, { timeout });
// Persist credentials if extraction succeeded
if (result.success && result.credentials) {
try {
const credentialsStr = JSON.stringify(result.credentials);
await updateProviderConnection(id, {
api_key: credentialsStr,
provider_specific_data: result.credentials,
apiKey: credentialsStr,
providerSpecificData: result.credentials,
});
return NextResponse.json({