import { cookies } from "next/headers"; import { NextResponse } from "next/server"; import { hfExchangeCode, hfFetchUser, hfOriginFromRequest } from "@/lib/auth/hf"; import { signSession, setSessionCookie } from "@/lib/auth/session"; const OAUTH_STATE_COOKIES = ["hf_oauth_state", "hf_oauth_verifier", "hf_oauth_client"] as const; function clearOAuthStateCookies(res: NextResponse) { const isProd = process.env.NODE_ENV === "production"; const opts = { httpOnly: true, secure: isProd, sameSite: (isProd ? "none" : "lax") as "none" | "lax", path: "/", maxAge: 0, }; for (const name of OAUTH_STATE_COOKIES) { res.cookies.set(name, "", opts); } } export async function GET(req: Request) { const url = new URL(req.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const clientFlag = url.searchParams.get("client"); const cookieJar = await cookies(); const storedState = cookieJar.get("hf_oauth_state")?.value; const verifier = cookieJar.get("hf_oauth_verifier")?.value; const clientKind = clientFlag === "mobile" ? "mobile" : cookieJar.get("hf_oauth_client")?.value ?? "web"; if (!code || !state || !verifier || state !== storedState) { const res = NextResponse.json({ error: "invalid_state" }, { status: 400 }); clearOAuthStateCookies(res); return res; } const origin = hfOriginFromRequest(req); const redirectUri = clientKind === "mobile" ? `${origin}/api/auth/callback/huggingface?client=mobile` : `${origin}/api/auth/callback/huggingface`; let tokens; try { tokens = await hfExchangeCode(redirectUri, code, verifier); } catch (err) { console.error("hf token exchange failed", err); const res = NextResponse.json({ error: "token_exchange_failed" }, { status: 400 }); clearOAuthStateCookies(res); return res; } const accessToken = tokens.accessToken(); const hfUser = await hfFetchUser(accessToken); const jwt = await signSession({ hfId: hfUser.sub, hfUsername: hfUser.preferred_username, email: hfUser.email, avatarUrl: hfUser.picture, accessToken, }); if (clientKind === "mobile") { const res = NextResponse.redirect(`langlearn://auth/callback?token=${encodeURIComponent(jwt)}`); clearOAuthStateCookies(res); return res; } const res = NextResponse.redirect(`${origin}/practice`); setSessionCookie(res, jwt); clearOAuthStateCookies(res); return res; }