import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { getEnv } from "@/lib/env"; import { createOpaqueToken, createStableId, sha256 } from "@/lib/password"; import { createAdminSessionRecord, createUserSessionRecord, deleteAdminSessionByHash, deleteUserSessionByHash, getAdminSessionByHash, getUserSessionByHash, touchAdminSession, touchUserSession, } from "@/lib/store"; const ADMIN_COOKIE_NAME = "hy2_panel_admin_session"; const USER_COOKIE_NAME = "hy2_panel_user_session"; function shouldUseSecureCookies() { const env = getEnv(); if (env.SESSION_COOKIE_SECURE === "true") { return true; } if (env.SESSION_COOKIE_SECURE === "false") { return false; } return new URL(env.APP_URL).protocol === "https:"; } export async function createAdminSession(adminId: number) { const env = getEnv(); const token = createOpaqueToken(32); const expiresAt = new Date( Date.now() + env.SESSION_TTL_HOURS * 60 * 60 * 1000, ).toISOString(); await createAdminSessionRecord({ id: createStableId(), adminId, sessionHash: sha256(`${env.SESSION_SECRET}:${token}`), expiresAt, }); const cookieStore = await cookies(); cookieStore.set(ADMIN_COOKIE_NAME, token, { httpOnly: true, sameSite: "lax", secure: shouldUseSecureCookies(), path: "/", expires: new Date(expiresAt), }); } export async function createUserSession(userId: number) { const env = getEnv(); const token = createOpaqueToken(32); const expiresAt = new Date( Date.now() + env.SESSION_TTL_HOURS * 60 * 60 * 1000, ).toISOString(); await createUserSessionRecord({ id: createStableId(), userId, sessionHash: sha256(`${env.SESSION_SECRET}:${token}`), expiresAt, }); const cookieStore = await cookies(); cookieStore.set(USER_COOKIE_NAME, token, { httpOnly: true, sameSite: "lax", secure: shouldUseSecureCookies(), path: "/", expires: new Date(expiresAt), }); } export async function destroyAdminSession() { const cookieStore = await cookies(); const raw = cookieStore.get(ADMIN_COOKIE_NAME)?.value; if (raw) { await deleteAdminSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`)); } cookieStore.delete(ADMIN_COOKIE_NAME); } export async function destroyUserSession() { const cookieStore = await cookies(); const raw = cookieStore.get(USER_COOKIE_NAME)?.value; if (raw) { await deleteUserSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`)); } cookieStore.delete(USER_COOKIE_NAME); } export async function getAdminSession() { const raw = (await cookies()).get(ADMIN_COOKIE_NAME)?.value; if (!raw) return null; const session = await getAdminSessionByHash( sha256(`${getEnv().SESSION_SECRET}:${raw}`), ); if (!session) { return null; } if (new Date(session.expires_at).getTime() <= Date.now()) { await deleteAdminSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`)); return null; } if (Date.now() - new Date(session.last_seen_at).getTime() > 5 * 60 * 1000) { await touchAdminSession(session.id); } return session; } export async function getUserSession() { const raw = (await cookies()).get(USER_COOKIE_NAME)?.value; if (!raw) return null; const session = await getUserSessionByHash( sha256(`${getEnv().SESSION_SECRET}:${raw}`), ); if (!session) { return null; } if (new Date(session.expires_at).getTime() <= Date.now()) { await deleteUserSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`)); return null; } if (Date.now() - new Date(session.last_seen_at).getTime() > 5 * 60 * 1000) { await touchUserSession(session.id); } return session; } export async function requireAdminSession() { const session = await getAdminSession(); if (!session) { redirect("/login"); } return session; } export async function requireAdminAccess() { const adminSession = await getAdminSession(); if (adminSession) { return { username: adminSession.username, source: "admin" as const, }; } const userSession = await getUserSession(); if (userSession?.is_admin) { return { username: userSession.username, source: "user" as const, }; } redirect("/login"); } export async function requireUserSession() { const session = await getUserSession(); if (!session) { redirect("/login"); } return session; }