import { randomBytes } from 'node:crypto'; import type { NitroEvent } from './h3-event'; import { db } from './db'; export { hashPassword, verifyPassword } from '#shared/password'; export const SESSION_COOKIE = 'w4c_session'; const TTL_MS = 30 * 24 * 60 * 60 * 1000; export type SessionUser = { id: string; email: string; name: string | null; role: string; }; export async function createSession(event: NitroEvent, userId: string): Promise { const token = randomBytes(32).toString('hex'); const expires = new Date(Date.now() + TTL_MS); await db.session.create({ data: { token, userId, expiresAt: expires } }); setCookie(event, SESSION_COOKIE, token, { httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV === 'production', path: '/', expires, }); return token; } export async function destroySession(event: NitroEvent): Promise { const token = getCookie(event, SESSION_COOKIE); if (token) await db.session.deleteMany({ where: { token } }); deleteCookie(event, SESSION_COOKIE, { path: '/' }); } export async function getCurrentUser(event: NitroEvent): Promise { const token = getCookie(event, SESSION_COOKIE); if (!token) return null; const session = await db.session.findUnique({ where: { token }, include: { user: true } }); if (!session || session.expiresAt < new Date()) return null; const { id, email, name, role } = session.user; return { id, email, name, role }; } /** API guard: 401 when unauthenticated. */ export async function requireUser(event: NitroEvent): Promise { const user = await getCurrentUser(event); if (!user) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }); return user; } /** API guard: 403 unless the admin role. */ export async function requireAdmin(event: NitroEvent): Promise { const user = await requireUser(event); if (user.role !== 'admin') throw createError({ statusCode: 403, statusMessage: 'Forbidden' }); return user; }