feat(FN-3047): add cancellable auth flow with recoverable pending login UX

This merge implements a recoverable pending-login auth flow (FN-3047) across three phases: cancellable in-progress auth routes in the API, an extended auth client contract, and updated ModelOnboardingModal/SettingsModal UI with proper cancel-button visibility during logout. It also adds `dashboard-r

Fusion-Task-Id: FN-3047
This commit is contained in:
Fusion
2026-05-01 01:12:36 -07:00
committed by gsxdsm
parent bb154a33e9
commit dca83061ed
9 changed files with 380 additions and 35 deletions

View File

@@ -120,11 +120,19 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
const storage = getAuthStorage();
storage.reload();
const oauthProviders = storage.getOAuthProviders();
const providers: { id: string; name: string; authenticated: boolean; type: "oauth" | "api_key" | "cli"; keyHint?: string }[] = oauthProviders.map((p) => ({
const providers: {
id: string;
name: string;
authenticated: boolean;
type: "oauth" | "api_key" | "cli";
keyHint?: string;
loginInProgress?: boolean;
}[] = oauthProviders.map((p) => ({
id: p.id,
name: p.name,
authenticated: storage.hasAuth(p.id),
type: "oauth" as const,
loginInProgress: loginInProgress.has(p.id),
}));
// Include API-key-backed providers if supported
@@ -421,6 +429,36 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
}
});
/**
* POST /api/auth/cancel
* Cancel an in-progress OAuth login for a provider.
* Body: { provider: string }
* Response: { success: true, cancelled: boolean }
*/
router.post("/auth/cancel", (req, res) => {
try {
const { provider } = req.body;
if (!provider || typeof provider !== "string") {
throw badRequest("provider is required");
}
const activeLogin = loginInProgress.get(provider);
if (!activeLogin) {
res.json({ success: true, cancelled: false });
return;
}
loginInProgress.delete(provider);
activeLogin.abort();
res.json({ success: true, cancelled: true });
} catch (err: unknown) {
if (err instanceof ApiError) {
throw err;
}
rethrowAsApiError(err);
}
});
router.get("/auth/oauth-callback", async (req, res) => {
try {
const error = typeof req.query.error === "string" ? req.query.error : undefined;