File size: 2,588 Bytes
dfa877e 0b4d29f dfa877e a0a94bd dfa877e a0a94bd 16fde87 a0a94bd 16fde87 dfa877e a0a94bd 16fde87 a0a94bd 16fde87 a0a94bd dfa877e 0b4d29f 5aa0d89 0b4d29f dfa877e 5aa0d89 0b4d29f dfa877e 0b4d29f dfa877e 0b4d29f dfa877e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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<string> {
return new SignJWT({ ...payload })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(`${SESSION_TTL_SECONDS}s`)
.sign(secret());
}
export async function verifySession(token: string): Promise<SessionPayload | null> {
try {
const { payload } = await jwtVerify<SessionPayload>(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<SessionPayload | null> {
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;
|