Files
2026-10-03 20:07:49 +02:00

78 lines
2.6 KiB
TypeScript

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: '/' })
}