spacefactory2 / src /lib /session.ts
mfn
SpaceFactory: agent-built Gradio Spaces from any HF model
9a3dc63
Raw History Blame Contribute Delete
2.55 kB
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<T>(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<Session | null> {
const jar = await cookies();
const cookie = jar.get(COOKIE_NAME);
if (!cookie?.value) return null;
const session = unseal<Session>(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,
};
}