import { redirect } from "@remix-run/node"; import type { LoaderFunctionArgs } from "@remix-run/node"; import { huggingFaceOAuth } from "~/lib/huggingface-oauth.server"; import { parseCookies } from "~/lib/oauth-utils.server"; import { getSession, commitSession } from "~/lib/session.server"; import { accountLinkingService } from "~/lib/account-linking.server"; export async function loader({ request }: LoaderFunctionArgs) { console.log("đŸ”Ĩ HuggingFace OAuth callback route hit"); const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const error = url.searchParams.get("error"); const errorDescription = url.searchParams.get("error_description"); console.log("Callback params:", { code: code ? `${code.substring(0, 4)}...` : null, state: state ? `${state.substring(0, 4)}...` : null, error, errorDescription, }); // Handle OAuth errors if (error) { console.error("HuggingFace OAuth error:", error); if (errorDescription) { console.error("Error description:", errorDescription); } const redirectUrl = `/?error=hf_oauth_failed&details=${encodeURIComponent( errorDescription ? `${error}: ${errorDescription}` : error )}`; console.log("Redirecting to:", redirectUrl); return redirect(redirectUrl); } if (!code || !state) { console.error("Missing code or state in HuggingFace OAuth callback"); return redirect("/?error=hf_missing_params"); } // Get stored PKCE data from cookies const cookieHeader = request.headers.get("Cookie"); const cookies = parseCookies(cookieHeader || ""); console.log("Cookie keys available:", Object.keys(cookies)); const storedState = cookies.hf_oauth_state; const codeVerifier = cookies.hf_oauth_code_verifier; const returnTo = cookies.hf_oauth_return_to ? decodeURIComponent(cookies.hf_oauth_return_to) : "/"; console.log("Cookie values:", { storedState: storedState ? `${storedState.substring(0, 4)}...` : null, codeVerifier: codeVerifier ? `length: ${codeVerifier.length}` : null, returnTo, }); // Verify state parameter (CSRF protection) if (!storedState || storedState !== state) { console.error("HuggingFace OAuth state mismatch"); console.error( "Stored state:", storedState ? storedState.substring(0, 10) + "..." : "null" ); console.error( "Received state:", state ? state.substring(0, 10) + "..." : "null" ); return redirect("/?error=hf_state_mismatch"); } if (!codeVerifier) { console.error("Missing code verifier for HuggingFace OAuth"); return redirect("/?error=hf_missing_verifier"); } try { console.log("🔄 Attempting to complete OAuth flow with code and verifier"); // Complete OAuth flow const { accessToken, userInfo } = await huggingFaceOAuth.completeOAuthFlow( code, codeVerifier ); console.log("✅ HuggingFace access token received:", accessToken); console.log("✅ HuggingFace OAuth successful for user:", userInfo.username); // Get existing session const session = await getSession(request.headers.get("Cookie")); let userSession = session.get("user") || { isLinked: false }; // Add HuggingFace info to session userSession.huggingface = userInfo; // Check if we can link accounts (if GitHub auth exists) if (userSession.github) { const linkCheck = accountLinkingService.canLink( userSession.github.userId, userInfo.username ); if (linkCheck.canLink) { // Create account link with access token const accountLink = accountLinkingService.createLink( userSession.github.userId, userSession.github.login, userInfo.username, accessToken ); userSession.isLinked = true; userSession.linkedAt = accountLink.linkedAt; console.log( `🔗 Accounts automatically linked: ${userSession.github.login} ↔ ${userInfo.username}` ); } else { console.warn("âš ī¸ Cannot link accounts:", linkCheck.reason); // Check if there's an existing link that needs token update const existingLink = accountLinkingService.findByHuggingFaceUser(userInfo.username); if (existingLink) { // Update the access token for the existing link accountLinkingService.updateAccessToken(existingLink.githubUserId, accessToken); console.log(`🔄 Updated access token for existing link: ${existingLink.githubLogin} ↔ ${userInfo.username}`); // If this is the same GitHub user, set session as linked if (existingLink.githubUserId === userSession.github.userId) { userSession.isLinked = true; userSession.linkedAt = existingLink.linkedAt; } else { userSession.isLinked = false; } } else { userSession.isLinked = false; } } } else { console.log( "â„šī¸ No GitHub authentication found, HuggingFace auth saved for later linking" ); userSession.isLinked = false; } // try { // const serverConfig = { // OAUTH2: { // // PROVIDER_URL: process.env.OAUTH2_PROVIDER_URL, // PROVIDER_URL: "https://huggingface.co", // CLIENT_ID: process.env.OAUTH2_CLIENT_ID, // CLIENT_SECRET: process.env.OAUTH2_CLIENT_SECRET, // CALLBACK_URL: process.env.OAUTH2_CALLBACK_URL, // }, // }; // // Exchange authorization code for access token // const tokenResponse = await fetch( // `${serverConfig.OAUTH2.PROVIDER_URL}/oauth/token`, // { // method: "POST", // headers: { // "Content-Type": "application/x-www-form-urlencoded", // Accept: "application/json", // }, // body: new URLSearchParams({ // grant_type: "authorization_code", // client_id: serverConfig.OAUTH2.CLIENT_ID!, // client_secret: serverConfig.OAUTH2.CLIENT_SECRET!, // code, // redirect_uri: "http://localhost:5173/api/auth/github/callback", // serverConfig.OAUTH2.CALLBACK_URL, // code_verifier: codeVerifier, // }), // } // ); // const tokenData = await tokenResponse.json(); // const accessToken = tokenData; // console.log( // "✅ Access token successfully exchanged for HuggingFace OAuth", accessToken // ); // } catch (tokenError) { // console.error( // "Failed to exchange authorization code for access token:", // tokenError // ); // return redirect( // `/?error=hf_token_exchange_failed&message=${encodeURIComponent( // "Failed to exchange authorization code for access token" // )}` // ); // } // Save updated session session.set("user", userSession); // Create response with updated session cookie const response = redirect(returnTo); // Clear OAuth temporary cookies const clearCookieOptions = "Path=/; HttpOnly; SameSite=Lax; Max-Age=0"; response.headers.append( "Set-Cookie", `hf_oauth_state=; ${clearCookieOptions}` ); response.headers.append( "Set-Cookie", `hf_oauth_code_verifier=; ${clearCookieOptions}` ); response.headers.append( "Set-Cookie", `hf_oauth_return_to=; ${clearCookieOptions}` ); // Set session cookie response.headers.append("Set-Cookie", await commitSession(session)); console.log("✅ Redirecting to:", returnTo); return response; } catch (error: any) { console.error("HuggingFace OAuth callback error:", error); const errorMessage = error.message || "Unknown error"; console.error("Error details:", errorMessage); return redirect( `/?error=hf_callback_failed&message=${encodeURIComponent(errorMessage)}` ); } }