← back to Govarbitrage
src/lib/session.ts
43 lines
import { SignJWT, jwtVerify } from "jose";
// JWT session tokens (HS256) signed with AUTH_SECRET. Pure-JS (jose / Web
// Crypto) so this module works in BOTH the Edge middleware and Node handlers.
export const SESSION_COOKIE = "ga_session";
export const SESSION_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
export interface SessionPayload {
sub: string; // user id
email: string;
role: string; // ADMIN | ANALYST | VIEWER
}
function secret(): Uint8Array {
const s = process.env.AUTH_SECRET;
// The dev fallback is in the repo — a production deploy that forgot to set
// AUTH_SECRET would mint forge-trivial sessions. Refuse to run instead.
if (!s && process.env.NODE_ENV === "production") {
throw new Error("AUTH_SECRET must be set in production — refusing to sign sessions with the dev fallback.");
}
return new TextEncoder().encode(s || "dev-only-change-me");
}
export async function createSession(payload: SessionPayload): Promise<string> {
return new SignJWT({ email: payload.email, role: payload.role })
.setProtectedHeader({ alg: "HS256" })
.setSubject(payload.sub)
.setIssuedAt()
.setExpirationTime(`${SESSION_MAX_AGE}s`)
.sign(secret());
}
export async function verifySession(token: string): Promise<SessionPayload | null> {
try {
const { payload } = await jwtVerify(token, secret());
if (!payload.sub || !payload.email) return null;
return { sub: payload.sub, email: payload.email as string, role: (payload.role as string) ?? "VIEWER" };
} catch {
return null;
}
}