| 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; |
| } |
|
|