|
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, |
|
}); |
|
|
|
|
|
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"); |
|
} |
|
|
|
|
|
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, |
|
}); |
|
|
|
|
|
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"); |
|
|
|
|
|
const { accessToken, userInfo } = await huggingFaceOAuth.completeOAuthFlow( |
|
code, |
|
codeVerifier |
|
); |
|
|
|
console.log("β
HuggingFace access token received:", accessToken); |
|
|
|
console.log("β
HuggingFace OAuth successful for user:", userInfo.username); |
|
|
|
|
|
const session = await getSession(request.headers.get("Cookie")); |
|
let userSession = session.get("user") || { isLinked: false }; |
|
|
|
|
|
userSession.huggingface = userInfo; |
|
|
|
|
|
if (userSession.github) { |
|
const linkCheck = accountLinkingService.canLink( |
|
userSession.github.userId, |
|
userInfo.username |
|
); |
|
|
|
if (linkCheck.canLink) { |
|
|
|
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); |
|
|
|
|
|
const existingLink = accountLinkingService.findByHuggingFaceUser(userInfo.username); |
|
if (existingLink) { |
|
|
|
accountLinkingService.updateAccessToken(existingLink.githubUserId, accessToken); |
|
console.log(`π Updated access token for existing link: ${existingLink.githubLogin} β ${userInfo.username}`); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
session.set("user", userSession); |
|
|
|
|
|
const response = redirect(returnTo); |
|
|
|
|
|
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}` |
|
); |
|
|
|
|
|
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)}` |
|
); |
|
} |
|
} |
|
|