import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto' import { eq } from 'drizzle-orm' import type { H3Event } from 'h3' import { sessions, users } from '../db/schema' const SESSION_COOKIE = 'session' const SESSION_TTL_MS = 1000 * 60 * 60 * 24 * 30 // 30 Tage export function hashPassword(password: string) { const salt = randomBytes(16).toString('hex') const hash = scryptSync(password, salt, 64).toString('hex') return `${salt}:${hash}` } export function verifyPassword(password: string, stored: string) { const [salt, hash] = stored.split(':') if (!salt || !hash) return false const candidate = scryptSync(password, salt, 64) const expected = Buffer.from(hash, 'hex') return candidate.length === expected.length && timingSafeEqual(candidate, expected) } export async function createSession(event: H3Event, userId: number) { const token = randomBytes(32).toString('hex') const expiresAt = new Date(Date.now() + SESSION_TTL_MS) await useDb().insert(sessions).values({ id: token, userId, expiresAt }) setCookie(event, SESSION_COOKIE, token, { httpOnly: true, sameSite: 'lax', path: '/', maxAge: SESSION_TTL_MS / 1000, }) } export async function getSessionUser(event: H3Event) { const token = getCookie(event, SESSION_COOKIE) if (!token) return null const db = useDb() const rows = await db .select({ user: users, session: sessions }) .from(sessions) .innerJoin(users, eq(sessions.userId, users.id)) .where(eq(sessions.id, token)) .limit(1) const row = rows[0] if (!row) return null if (row.session.expiresAt < new Date()) { await db.delete(sessions).where(eq(sessions.id, token)) deleteCookie(event, SESSION_COOKIE, { path: '/' }) return null } return { ...row.user, roles: await getUserRoles(row.user.id), permissions: await getUserPermissions(row.user.id) } } export async function requireUser(event: H3Event) { const user = await getSessionUser(event) if (!user) { throw createError({ statusCode: 401, statusMessage: 'Nicht angemeldet' }) } return user } export async function requirePermission(event: H3Event, permission: string) { const user = await requireUser(event) if (!user.roles.includes('admin') && !user.permissions.includes(permission)) { throw createError({ statusCode: 403, statusMessage: 'Kein Zugriff' }) } return user } export async function destroySession(event: H3Event) { const token = getCookie(event, SESSION_COOKIE) if (token) { await useDb().delete(sessions).where(eq(sessions.id, token)) } deleteCookie(event, SESSION_COOKIE, { path: '/' }) }