import { SignJWT, jwtVerify } from "jose"; import { cookies, headers } from "next/headers"; import type { NextResponse } from "next/server"; export const SESSION_COOKIE = "ll_session"; const SESSION_TTL_SECONDS = 60 * 60 * 24 * 7; function secret() { const s = process.env.AUTH_SECRET; if (!s) throw new Error("AUTH_SECRET is not set"); return new TextEncoder().encode(s); } export type SessionPayload = { hfId: string; hfUsername: string; email?: string; avatarUrl?: string; nativeLang?: string; targetLang?: string; targetLangs?: string[]; level?: string; accessToken?: string; streakCount?: number; lastActiveDate?: string; }; export async function signSession(payload: SessionPayload): Promise { return new SignJWT({ ...payload }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime(`${SESSION_TTL_SECONDS}s`) .sign(secret()); } export async function verifySession(token: string): Promise { try { const { payload } = await jwtVerify(token, secret(), { algorithms: ["HS256"] }); if (!payload.hfId) return null; return { hfId: payload.hfId, hfUsername: payload.hfUsername, email: payload.email, avatarUrl: payload.avatarUrl, nativeLang: payload.nativeLang, targetLang: payload.targetLang, targetLangs: payload.targetLangs, level: payload.level, accessToken: payload.accessToken, streakCount: payload.streakCount, lastActiveDate: payload.lastActiveDate, }; } catch { return null; } } export async function getSession(): Promise { const authHeader = (await headers()).get("authorization"); if (authHeader?.toLowerCase().startsWith("bearer ")) { const token = authHeader.slice(7).trim(); if (token) return verifySession(token); } const token = (await cookies()).get(SESSION_COOKIE)?.value; if (!token) return null; return verifySession(token); } export function sessionCookieOptions(maxAge: number) { const isProd = process.env.NODE_ENV === "production"; return { httpOnly: true, secure: isProd, sameSite: (isProd ? "none" : "lax") as "none" | "lax", path: "/", maxAge, }; } export function setSessionCookie(res: NextResponse, token: string) { res.cookies.set(SESSION_COOKIE, token, sessionCookieOptions(SESSION_TTL_SECONDS)); } export function clearSessionCookie(res: NextResponse) { res.cookies.set(SESSION_COOKIE, "", sessionCookieOptions(0)); } export const SESSION_MAX_AGE = SESSION_TTL_SECONDS;