import { cookies } from "next/headers"; import { db, schema } from "./db"; import { eq, and, gt } from "drizzle-orm"; import { v4 as uuidv4 } from "uuid"; import bcrypt from "bcryptjs"; const SESSION_COOKIE = "qt_session"; const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days export interface AuthUser { id: string; email: string; name: string | null; isAdmin: boolean; } export async function hashPassword(password: string): Promise { return bcrypt.hash(password, 10); } export async function verifyPassword( password: string, hash: string ): Promise { return bcrypt.compare(password, hash); } export async function createSession(userId: string): Promise { const sessionId = uuidv4(); const now = Date.now(); await db.insert(schema.sessions).values({ id: sessionId, userId, expiresAt: now + SESSION_DURATION_MS, createdAt: now, }); return sessionId; } export async function setSessionCookie(sessionId: string) { const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE, sessionId, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: SESSION_DURATION_MS / 1000, path: "/", }); } export async function clearSessionCookie() { const cookieStore = await cookies(); cookieStore.delete(SESSION_COOKIE); } export async function getSession(): Promise { const cookieStore = await cookies(); const sessionId = cookieStore.get(SESSION_COOKIE)?.value; if (!sessionId) return null; const now = Date.now(); const result = await db .select({ sessionId: schema.sessions.id, userId: schema.users.id, email: schema.users.email, name: schema.users.name, isAdmin: schema.users.isAdmin, expiresAt: schema.sessions.expiresAt, }) .from(schema.sessions) .innerJoin(schema.users, eq(schema.sessions.userId, schema.users.id)) .where( and(eq(schema.sessions.id, sessionId), gt(schema.sessions.expiresAt, now)) ) .limit(1); if (result.length === 0) return null; return { id: result[0].userId, email: result[0].email, name: result[0].name, isAdmin: result[0].isAdmin === 1, }; } export async function deleteSession(sessionId: string) { await db.delete(schema.sessions).where(eq(schema.sessions.id, sessionId)); } export async function registerUser( email: string, password: string, name?: string ): Promise<{ user?: AuthUser; error?: string }> { const existing = await db .select() .from(schema.users) .where(eq(schema.users.email, email.toLowerCase())) .limit(1); if (existing.length > 0) { return { error: "Email already registered" }; } const userId = uuidv4(); const passwordHash = await hashPassword(password); const now = Date.now(); await db.insert(schema.users).values({ id: userId, email: email.toLowerCase(), passwordHash, name: name || null, createdAt: now, }); return { user: { id: userId, email: email.toLowerCase(), name: name || null, isAdmin: false, }, }; } export async function loginUser( email: string, password: string ): Promise<{ user?: AuthUser; sessionId?: string; error?: string }> { const users = await db .select() .from(schema.users) .where(eq(schema.users.email, email.toLowerCase())) .limit(1); if (users.length === 0) { return { error: "Invalid email or password" }; } const user = users[0]; const valid = await verifyPassword(password, user.passwordHash); if (!valid) { return { error: "Invalid email or password" }; } const sessionId = await createSession(user.id); return { user: { id: user.id, email: user.email, name: user.name, isAdmin: user.isAdmin === 1, }, sessionId, }; }