import "server-only"; import { and, eq } from "drizzle-orm"; import { headers } from "next/headers"; import { db } from "@/lib/db"; import { user } from "@/lib/db/schema"; type ManagedUserType = "guest" | "regular" | "admin" | "agent_admin"; type ManagedSessionUser = { id: string; email: string; name: string | null; role: ManagedUserType; isActive: boolean; activeSessionKey: string | null; }; function normalizeClientIp(value: string | null | undefined) { if (!value) { return null; } const firstHop = value.split(",")[0]?.trim(); return firstHop || null; } function normalizeUserAgent(value: string | null | undefined) { const normalized = String(value || "").trim(); if (!normalized) { return null; } return normalized.slice(0, 512); } async function readRequestSessionMetadata() { const requestHeaders = await headers(); const forwardedFor = requestHeaders.get("x-forwarded-for"); const realIp = requestHeaders.get("x-real-ip"); return { ip: normalizeClientIp(forwardedFor) || normalizeClientIp(realIp), userAgent: normalizeUserAgent(requestHeaders.get("user-agent")), }; } export function isManagedSessionUserType( type: string | null | undefined ): type is ManagedUserType { return ( type === "guest" || type === "regular" || type === "admin" || type === "agent_admin" ); } export function requiresSingleActiveSession(type: string | null | undefined) { return type === "regular" || type === "admin" || type === "agent_admin"; } export async function establishSingleActiveSession(params: { userId: string }) { const { userId } = params; const sessionKey = crypto.randomUUID(); const metadata = await readRequestSessionMetadata(); const now = new Date(); await db .update(user) .set({ activeSessionKey: sessionKey, activeSessionCreatedAt: now, activeSessionIp: metadata.ip, activeSessionUserAgent: metadata.userAgent, activeSessionLastSeenAt: now, updatedAt: now, }) .where(eq(user.id, userId)); return { sessionKey }; } export async function getValidatedSessionUser(params: { userId: string; sessionKey: string; }) { const { userId, sessionKey } = params; const rows = await db .select({ id: user.id, email: user.email, name: user.name, role: user.role, isActive: user.isActive, activeSessionKey: user.activeSessionKey, }) .from(user) .where(eq(user.id, userId)) .limit(1); const currentUser = rows[0] as ManagedSessionUser | undefined; if (!currentUser) { return null; } if (!currentUser.isActive) { return null; } if (!requiresSingleActiveSession(currentUser.role)) { return currentUser; } if ( !currentUser.activeSessionKey || currentUser.activeSessionKey !== sessionKey ) { return null; } return currentUser; } export async function revokeSingleActiveSession(params: { userId: string; sessionKey?: string | null; }) { const { userId, sessionKey } = params; const now = new Date(); const whereClause = sessionKey ? and(eq(user.id, userId), eq(user.activeSessionKey, sessionKey)) : eq(user.id, userId); const revokedUsers = await db .update(user) .set({ activeSessionKey: null, activeSessionCreatedAt: null, activeSessionIp: null, activeSessionUserAgent: null, activeSessionLastSeenAt: null, updatedAt: now, }) .where(whereClause) .returning({ id: user.id }); return revokedUsers.length > 0; }