import { cookies } from "next/headers"; import crypto from "node:crypto"; export interface Session { accessToken: string; expiresAt: number; // epoch ms user: { name: string; // username fullName?: string; avatarUrl?: string; }; } const COOKIE_NAME = "sf_session"; function sessionKey(): Buffer { const secret = process.env.SESSION_SECRET || process.env.OAUTH_CLIENT_SECRET || "spacefactory-dev-secret"; return crypto.createHash("sha256").update(secret).digest(); } export function seal(data: unknown): string { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv("aes-256-gcm", sessionKey(), iv); const plaintext = Buffer.from(JSON.stringify(data), "utf8"); const enc = Buffer.concat([cipher.update(plaintext), cipher.final()]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, enc]).toString("base64url"); } export function unseal(sealed: string): T | null { try { const raw = Buffer.from(sealed, "base64url"); const iv = raw.subarray(0, 12); const tag = raw.subarray(12, 28); const enc = raw.subarray(28); const decipher = crypto.createDecipheriv("aes-256-gcm", sessionKey(), iv); decipher.setAuthTag(tag); const dec = Buffer.concat([decipher.update(enc), decipher.final()]); return JSON.parse(dec.toString("utf8")) as T; } catch { return null; } } export async function getSession(): Promise { const jar = await cookies(); const cookie = jar.get(COOKIE_NAME); if (!cookie?.value) return null; const session = unseal(cookie.value); if (!session) return null; if (session.expiresAt && session.expiresAt < Date.now()) return null; return session; } export function sessionCookie(session: Session) { return { name: COOKIE_NAME, value: seal(session), httpOnly: true, secure: process.env.NODE_ENV === "production", // The Space may be embedded in an iframe on huggingface.co; SameSite=None // (with Secure) keeps the session usable there. sameSite: (process.env.NODE_ENV === "production" ? "none" : "lax") as | "none" | "lax", path: "/", maxAge: Math.max(60, Math.floor((session.expiresAt - Date.now()) / 1000)), }; } export function clearedSessionCookie() { return { name: COOKIE_NAME, value: "", httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: (process.env.NODE_ENV === "production" ? "none" : "lax") as | "none" | "lax", path: "/", maxAge: 0, }; }