import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto' import type { H3Event } from 'h3' export interface SessionUser { sub: string email?: string name?: string } const COOKIE = 'aigen_session' const STATE_COOKIE = 'aigen_oauth_state' function secret() { const config = useRuntimeConfig() return config.sessionSecret || 'dev-only-change-me' } function sign(value: string) { return createHmac('sha256', secret()).update(value).digest('base64url') } function seal(payload: unknown) { const data = Buffer.from(JSON.stringify(payload)).toString('base64url') return `${data}.${sign(data)}` } function unseal(token: string | undefined): T | null { if (!token || !token.includes('.')) return null const [data, sig] = token.split('.') const expected = sign(data) const a = Buffer.from(sig) const b = Buffer.from(expected) if (a.length !== b.length || !timingSafeEqual(a, b)) return null try { return JSON.parse(Buffer.from(data, 'base64url').toString('utf8')) as T } catch { return null } } function cookieOpts(event: H3Event) { const proto = getRequestHeader(event, 'x-forwarded-proto') || getRequestProtocol(event) return { httpOnly: true, sameSite: 'lax' as const, path: '/', secure: proto === 'https', maxAge: 60 * 60 * 24 * 14 } } export function getSessionUser(event: H3Event): SessionUser | null { return unseal(getCookie(event, COOKIE)) } export function setSessionUser(event: H3Event, user: SessionUser) { setCookie(event, COOKIE, seal(user), cookieOpts(event)) } export function clearAuthSession(event: H3Event) { deleteCookie(event, COOKIE, { path: '/' }) } export function createOauthState(event: H3Event) { const state = randomBytes(24).toString('hex') setCookie(event, STATE_COOKIE, state, { ...cookieOpts(event), maxAge: 600 }) return state } export function consumeOauthState(event: H3Event, incoming: string | undefined) { const stored = getCookie(event, STATE_COOKIE) deleteCookie(event, STATE_COOKIE, { path: '/' }) return Boolean(stored && incoming && stored === incoming) } export function authEnabled() { const config = useRuntimeConfig() return Boolean(config.public.authEnabled && config.oidcClientId && config.oidcClientSecret) } export function publicBaseUrl(event: H3Event) { const proto = getRequestHeader(event, 'x-forwarded-proto') || getRequestProtocol(event) const host = getRequestHeader(event, 'x-forwarded-host') || getRequestHost(event) return `${proto}://${host}` }