import { cookies } from "next/headers"; import { SignJWT, jwtVerify } from "jose"; export const SESSION_COOKIE_NAME = "auth_session"; export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 8; // 8 hours function getSessionSecretKey() { const secret = process.env.SESSION_SECRET; if (!secret) { throw new Error("SESSION_SECRET environment variable is not set"); } return new TextEncoder().encode(secret); } function resolveCookieSecureFlag() { const raw = (process.env.SESSION_COOKIE_SECURE || "").trim().toLowerCase(); if (raw === "true") return true; if (raw === "false") return false; return process.env.NODE_ENV === "production"; } function normalizeEmailOrNull(value) { if (typeof value !== "string") return null; const s = value.trim(); if (!s) return null; // Email is not case-sensitive; keep it normalized for UI consistency. return s.toLowerCase(); } /** * Create a signed session JWT and store it in a HTTP-only cookie. */ export async function createSession({ userId, role, branchId, email }) { if (!userId || !role) { throw new Error("createSession requires userId and role"); } const payload = { userId, role, branchId: branchId ?? null, email: normalizeEmailOrNull(email), }; const jwt = await new SignJWT(payload) .setProtectedHeader({ alg: "HS256", typ: "JWT" }) .setIssuedAt() .setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`) .sign(getSessionSecretKey()); const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE_NAME, jwt, { httpOnly: true, secure: resolveCookieSecureFlag(), sameSite: "lax", path: "/", maxAge: SESSION_MAX_AGE_SECONDS, }); return jwt; } /** * Read the current session from the HTTP-only cookie. */ export async function getSession() { const cookieStore = await cookies(); const cookie = cookieStore.get(SESSION_COOKIE_NAME); if (!cookie?.value) { return null; } const secretKey = getSessionSecretKey(); try { const { payload } = await jwtVerify(cookie.value, secretKey); const { userId, role, branchId, email } = payload; if (typeof userId !== "string" || typeof role !== "string") { return null; } return { userId, role, branchId: typeof branchId === "string" ? branchId : null, email: typeof email === "string" ? email : null, }; } catch { const store = await cookies(); store.set(SESSION_COOKIE_NAME, "", { httpOnly: true, secure: resolveCookieSecureFlag(), sameSite: "lax", path: "/", maxAge: 0, }); return null; } } export async function destroySession() { const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE_NAME, "", { httpOnly: true, secure: resolveCookieSecureFlag(), sameSite: "lax", path: "/", maxAge: 0, }); }