forked from BRETT/BRETT
78 lines
2.6 KiB
TypeScript
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: '/' })
|
|
}
|