Update readme and tests
This commit is contained in:
7
.changeset/fix-anthropic-subscription-fragment-login.md
Normal file
7
.changeset/fix-anthropic-subscription-fragment-login.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Fix Anthropic subscription login when pasted callback URLs contain fragment OAuth parameters.
|
||||||
|
category: fix
|
||||||
|
dev: Normalizes pasted OAuth callback fragments before resolving dashboard manual-code login prompts.
|
||||||
@@ -251,6 +251,66 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
|
|||||||
return providerId === "github-copilot";
|
return providerId === "github-copilot";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeManualOAuthInputForProvider(providerId: string, input: string): string {
|
||||||
|
if (providerId !== "anthropic" && providerId !== "openai-codex") {
|
||||||
|
return input.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(trimmed);
|
||||||
|
const searchCode = url.searchParams.get("code");
|
||||||
|
if (searchCode) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = url.hash.replace(/^#/, "").replace(/^\?/, "");
|
||||||
|
if (!hash) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
const hashParams = new URLSearchParams(hash);
|
||||||
|
const hashCode = hashParams.get("code");
|
||||||
|
if (!hashCode) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:ProviderAuth 2026-07-04-00:00:
|
||||||
|
Anthropic subscription and Codex pasted-login flows must accept the exact browser address bar after redirect, including providers/browsers that place OAuth `code` and `state` in the URL fragment.
|
||||||
|
The upstream CLI parser treats a syntactically valid URL as search-only, so normalize fragment callbacks to query-param text before resolving the pending manual-code prompt.
|
||||||
|
*/
|
||||||
|
const normalized = new URLSearchParams();
|
||||||
|
normalized.set("code", hashCode);
|
||||||
|
const hashState = hashParams.get("state");
|
||||||
|
if (hashState) {
|
||||||
|
normalized.set("state", hashState);
|
||||||
|
}
|
||||||
|
return normalized.toString();
|
||||||
|
} catch {
|
||||||
|
const queryStart = trimmed.indexOf("?");
|
||||||
|
if (queryStart >= 0) {
|
||||||
|
const queryEnd = trimmed.indexOf("#", queryStart);
|
||||||
|
const query = trimmed.slice(queryStart + 1, queryEnd >= 0 ? queryEnd : undefined);
|
||||||
|
const params = new URLSearchParams(query);
|
||||||
|
const code = params.get("code");
|
||||||
|
if (code) {
|
||||||
|
const normalized = new URLSearchParams();
|
||||||
|
normalized.set("code", code);
|
||||||
|
const state = params.get("state");
|
||||||
|
if (state) {
|
||||||
|
normalized.set("state", state);
|
||||||
|
}
|
||||||
|
return normalized.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectOauthOption(
|
function selectOauthOption(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
prompt: { options: Array<{ id: string; label?: string }> },
|
prompt: { options: Array<{ id: string; label?: string }> },
|
||||||
@@ -1158,7 +1218,7 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeLogin.inputSubmitted = true;
|
activeLogin.inputSubmitted = true;
|
||||||
activeLogin.resolveInput(code.trim());
|
activeLogin.resolveInput(normalizeManualOAuthInputForProvider(toOauthLoginProviderId(provider), code));
|
||||||
res.json({ success: true, submitted: true });
|
res.json({ success: true, submitted: true });
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof ApiError) {
|
if (err instanceof ApiError) {
|
||||||
|
|||||||
Reference in New Issue
Block a user