Files
money.sori.studio/server/utils/auth.ts
2026-08-13 15:58:42 +09:00

54 lines
1.8 KiB
TypeScript

import { createHmac, timingSafeEqual } from 'node:crypto'
import type { H3Event } from 'h3'
import { getCookie, setCookie, deleteCookie, createError } from 'h3'
import { prisma } from './db'
const cookieName = 'money_session'
function secret() {
return useRuntimeConfig().sessionSecret
}
function sign(value: string) {
return createHmac('sha256', secret()).update(value).digest('hex')
}
export function createSessionCookie(userId: string) {
const payload = Buffer.from(JSON.stringify({ userId })).toString('base64url')
return `${payload}.${sign(payload)}`
}
export function setSession(event: H3Event, userId: string) {
setCookie(event, cookieName, createSessionCookie(userId), {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 180,
path: '/'
})
}
export function clearAuthSession(event: H3Event) {
deleteCookie(event, cookieName, { path: '/' })
}
export async function requireUser(event: H3Event) {
const raw = getCookie(event, cookieName)
if (!raw) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
const [payload, signature] = raw.split('.')
if (!payload || !signature) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
const expected = sign(payload)
const isValid = timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
if (!isValid) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as { userId: string }
const user = await prisma.user.findUnique({
where: { id: parsed.userId },
select: { id: true, email: true, name: true }
})
if (!user) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
return user
}