mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 13:42:09 +03:00
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login
POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but that service looks up the provider by
slug in TOKEN_EXTRACTION_CONFIGS. The lookup always missed and returned
"No extraction config" without launching a browser — so the VibeProxy
"Sign in" button for Adobe Firefly (and every other web-cookie provider)
never opened a browser.
Adobe Firefly additionally had no extraction config because its IMS JWT
is never in cookies/localStorage — it only rides on the Authorization:
Bearer header of firefly-3p.ff.adobe.io XHRs.
- Resolve the provider slug from the connection row and pass the slug
(not the DB id) to inAppLoginService.startLogin.
- Add open-sse/services/adobeFireflyBrowserLogin.ts: a Playwright
service that launches a visible browser at firefly.adobe.com and
intercepts firefly-3p requests to capture the IMS JWT + sherlockToken
cookie. Wire it into the /login route for the adobe-firefly slug.
- Fix latent bug: updateProviderConnection reads camelCase keys
(apiKey, providerSpecificData), so the previous snake_case call never
persisted extracted credentials.
* fix(adobe-firefly): open browser sign-in and resolve provider slug in /login
POST /api/providers/[id]/login passed the connection DB id to
inAppLoginService.startLogin, but TOKEN_EXTRACTION_CONFIGS is keyed by
provider slug — so browser login never launched for web-cookie providers.
Adobe Firefly also cannot use cookie extraction: the IMS JWT only appears
on Authorization headers to firefly-3p.ff.adobe.io. Add a dedicated
Playwright interceptor and persist credentials with camelCase keys that
updateProviderConnection actually reads.
* fix(adobe-firefly): use system Chrome/Edge CDP for browser sign-in
Playwright is not available inside the pkg-packaged VibeProxyServices.exe,
so import('playwright') always failed with 'Playwright not installed' and
never opened a window. Launch Chrome/Edge with --remote-debugging-port and
capture the firefly-3p Authorization Bearer via pure CDP WebSocket instead.
* fix(adobe-firefly): live x-arp-session-id / Arkose wire (stop 408 under load)
Browser generate-async requires x-arp-session-id as base64({sid,ark,ftr}) with a
real Arkose blob (sherlockToken). JWT alone frequently returns colligo HTTP 408
system under load while credits still work.
- Match live ftr magic __UDF43-m4_31ck + Arkose pk in synthetic ARP fallback
- Ranked extract of sherlockToken / x-arp from Cookie, HAR, fetch() paste, and
space-joined JWT+ARP (PasswordBox newline collapse)
- Reuse one ARP for storage upload + generate-async
- Clearer 408 errors when browser ARP is missing vs stale
- Unit suite 42/42
* fix(adobe-firefly): durable session ARP rebuild and aux_sid false-positive
Rebuild x-arp-session-id from forterToken/arkose/ff_session_guid instead of
ranking long Cookie pairs (e.g. aux_sid=…) as opaque ARP, which caused colligo
HTTP 408. Cache IMS JWT + cookie sessions, rotate ARP on 408 retries, and keep
Playwright warm-up opt-in only (headless Forter is rejected).
Also expand synthetic ARP shape with bfp/fpjs to match live successful captures.
* fix(adobe-firefly): durable session, off-screen Chrome recovery, browser sign-in
Rebuild x-arp-session-id from Cookie pieces (sid/ark/forter) so aux_sid is never
sent as ARP. Sticky ARP + submit spacing reduce mid-batch colligo 408 thrash.
Add optional managed Chrome warm (off-screen headed by default; Forter rejects
headless) and POST /api/providers/{id}/login browser sign-in that returns JWT+Cookie
after a fresh SSO. Visible sign-in resets off-screen window placement and clears
prior Adobe session when adding another account.
* fix(adobe-firefly): renew sessions through durable CDP
* fix(adobe-firefly): isolate browser sessions per account
* fix(adobe-firefly): make account login fresh and deterministic
* chore(adobe-firefly): remove obsolete browser fallback
* docs(adobe-firefly): document renewal controls
* fix(adobe-firefly): harden CDP warm, risk session, and browser sign-in
Stop colligo 408 thrash from stale Forter and frozen Google login during
Sign in with browser:
- CDP warm: clear Firefly origin storage + risk cookies (keep SSO); require
forter age under 10 minutes on loop and timeout paths; dual CDP queues;
await Runtime.runIfWaitingForDebugger; profile-lock launch retries
- Session: connectionId fingerprint; write-back JWT+Cookie; warm-fail
cooldown; fail closed risk_session_stale when forter is known-stale
- Client: submit gate around generate-async; max 2 attempts when forter
known-stale; poll 401 one refresh; pass sessionBrowserKey through handlers
- Login route: pure system Chrome/Edge CDP only; camelCase credential persist
- Unit: browser-login + firefly suites green (60)
---------
Co-authored-by: artickc <artur1992123@mail.ru>
This commit is contained in:
committed by
GitHub
parent
5eba045175
commit
57fb90d734
@@ -20,7 +20,142 @@ function resolveProviderSlug(connection: Record<string, unknown> | null): string
|
||||
return "";
|
||||
}
|
||||
|
||||
// ─── POST: Start login flow ────────────────────────────────────────────────
|
||||
function isAdobeFireflyProvider(
|
||||
connection: { provider?: unknown } | null,
|
||||
providerSlug: string
|
||||
): boolean {
|
||||
const raw = String(connection?.provider || "").trim();
|
||||
return ADOBE_FIREFLY_SLUGS.has(raw) || ADOBE_FIREFLY_SLUGS.has(providerSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist JWT + Cookie the way desktop clients (and generate) expect:
|
||||
* multi-line api_key, plus camelCase providerSpecificData for updateProviderConnection.
|
||||
*/
|
||||
async function persistAdobeFireflyCredentials(
|
||||
connectionId: string,
|
||||
opts: {
|
||||
accessToken?: string;
|
||||
cookie?: string;
|
||||
account?: string;
|
||||
arpSessionId?: string;
|
||||
}
|
||||
): Promise<{
|
||||
accessToken: string;
|
||||
cookie: string;
|
||||
credential: string;
|
||||
account: string;
|
||||
}> {
|
||||
const accessToken = String(opts.accessToken || "").trim();
|
||||
const cookie = String(opts.cookie || "").trim();
|
||||
const account = String(opts.account || "").trim();
|
||||
const credential =
|
||||
accessToken && cookie
|
||||
? `${accessToken}\n${cookie}`
|
||||
: accessToken ||
|
||||
cookie ||
|
||||
JSON.stringify({
|
||||
mode: "browser-profile",
|
||||
account,
|
||||
signedInAt: Date.now(),
|
||||
});
|
||||
|
||||
const marker = {
|
||||
mode: "browser-profile",
|
||||
account,
|
||||
signedInAt: Date.now(),
|
||||
arpSessionId: String(opts.arpSessionId || ""),
|
||||
};
|
||||
|
||||
try {
|
||||
// camelCase only — updateProviderConnection / encryptConnectionFields read apiKey +
|
||||
// providerSpecificData (snake_case keys are silently ignored and never persisted).
|
||||
await updateProviderConnection(connectionId, {
|
||||
apiKey: credential,
|
||||
providerSpecificData: {
|
||||
...marker,
|
||||
cookie: cookie || credential,
|
||||
access_token: accessToken || undefined,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
/* non-fatal — return credentials to the host app either way */
|
||||
}
|
||||
|
||||
return { accessToken, cookie, credential, account };
|
||||
}
|
||||
|
||||
function adobeFireflySuccessResponse(data: {
|
||||
accessToken: string;
|
||||
cookie: string;
|
||||
credential: string;
|
||||
account: string;
|
||||
arpSessionId?: string;
|
||||
via: "pure-cdp";
|
||||
}): NextResponse {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
account: data.account || undefined,
|
||||
accessToken: data.accessToken || undefined,
|
||||
cookie: data.cookie || undefined,
|
||||
arpSessionId: data.arpSessionId || undefined,
|
||||
credential: data.credential,
|
||||
credentials: {
|
||||
access_token: data.accessToken || undefined,
|
||||
cookie: data.cookie || undefined,
|
||||
},
|
||||
via: data.via,
|
||||
persisted: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adobe Firefly browser sign-in:
|
||||
* pure system Chrome/Edge CDP only (packaged-safe, no Playwright/browser bundle).
|
||||
*/
|
||||
async function loginAdobeFirefly(
|
||||
connectionId: string,
|
||||
body: { timeout?: unknown; freshSession?: unknown }
|
||||
): Promise<NextResponse> {
|
||||
const timeout = typeof body.timeout === "number" ? body.timeout : undefined;
|
||||
const freshSession = typeof body.freshSession === "boolean" ? body.freshSession : true;
|
||||
|
||||
// Pure system-browser CDP is the packaged-safe implementation. Do not open a second browser
|
||||
// after failure: it creates ambiguous success/error races and the packaged runtime has no
|
||||
// reliable Playwright browser bundle.
|
||||
// startAdobeFireflyBrowserLogin always kills its Chrome tree in `finally` (no orphans).
|
||||
try {
|
||||
const { startAdobeFireflyBrowserLogin } =
|
||||
await import("@omniroute/open-sse/services/adobeFireflyBrowserLogin.ts");
|
||||
const pure = await startAdobeFireflyBrowserLogin(timeout, {
|
||||
sessionKey: connectionId,
|
||||
freshSession,
|
||||
});
|
||||
if (pure.success && pure.credentials?.accessToken) {
|
||||
const persisted = await persistAdobeFireflyCredentials(connectionId, {
|
||||
accessToken: pure.credentials.accessToken,
|
||||
cookie: pure.credentials.cookie,
|
||||
account: pure.account,
|
||||
});
|
||||
return adobeFireflySuccessResponse({
|
||||
...persisted,
|
||||
via: "pure-cdp",
|
||||
});
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: pure.error || "Adobe Firefly sign-in did not capture an authenticated IMS JWT.",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
} catch (err) {
|
||||
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : err);
|
||||
return NextResponse.json({ success: false, error: msg }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
// --- POST: Start login flow -------------------------------------------------
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
@@ -35,69 +170,35 @@ export async function POST(
|
||||
return NextResponse.json({ success: false, error: "Provider not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const timeout = typeof body.timeout === "number" ? body.timeout : undefined;
|
||||
const body = (await req.json().catch(() => ({}))) as {
|
||||
timeout?: unknown;
|
||||
freshSession?: unknown;
|
||||
};
|
||||
const providerSlug = resolveProviderSlug(provider as Record<string, unknown>);
|
||||
|
||||
try {
|
||||
// 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);
|
||||
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Adobe Firefly: dedicated JWT capture (never cookies/localStorage alone).
|
||||
if (isAdobeFireflyProvider(provider as { provider?: unknown }, providerSlug)) {
|
||||
try {
|
||||
return await loginAdobeFirefly(id, body);
|
||||
} catch (err) {
|
||||
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : err);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: fireflyResult.error || "Adobe Firefly sign-in failed" },
|
||||
{ status: 400 }
|
||||
{ success: false, error: `Adobe Firefly sign-in error: ${msg}` },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 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 });
|
||||
const result = await inAppLoginService.startLogin(providerSlug || id, {
|
||||
timeout: typeof body.timeout === "number" ? body.timeout : undefined,
|
||||
});
|
||||
|
||||
// Persist credentials if extraction succeeded
|
||||
if (result.success && result.credentials) {
|
||||
|
||||
Reference in New Issue
Block a user