Files
aigen/server/utils/session.ts
T
TowstyandCursor bc0ea613c1 Add a password-gated private instance mode for xaigen.
Keep Authentik on aigen, isolate library data per instance, and purge Comfy outputs from the desktop after they are saved on the server.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 19:24:00 -05:00

132 lines
3.8 KiB
TypeScript

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'
const LIBRARY_COOKIE = 'aigen_library'
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<T>(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<SessionUser>(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() {
return passwordAuthEnabled() || oidcAuthEnabled()
}
interface LibraryUnlock {
version?: string
folders?: Record<string, string>
}
function readUnlock(event: H3Event): LibraryUnlock {
return unseal<LibraryUnlock>(getCookie(event, LIBRARY_COOKIE)) || {}
}
export function getFolderUnlocks(event: H3Event) {
return { ...(readUnlock(event).folders || {}) }
}
export function setFolderUnlock(event: H3Event, folderId: string, version: string) {
const folders = getFolderUnlocks(event)
folders[folderId] = version
setCookie(event, LIBRARY_COOKIE, seal({ folders }), cookieOpts(event))
}
export function setFolderUnlocks(event: H3Event, entries: Record<string, string>) {
const folders = { ...getFolderUnlocks(event), ...entries }
setCookie(event, LIBRARY_COOKIE, seal({ folders }), cookieOpts(event))
}
export function clearFolderUnlock(event: H3Event, folderId?: string) {
if (!folderId) {
deleteCookie(event, LIBRARY_COOKIE, { path: '/' })
return
}
const folders = getFolderUnlocks(event)
delete folders[folderId]
setCookie(event, LIBRARY_COOKIE, seal({ folders }), cookieOpts(event))
}
export function setLibraryUnlock(event: H3Event, version: string) {
setFolderUnlock(event, '*', version)
}
export function getLibraryUnlockVersion(event: H3Event) {
return getFolderUnlocks(event)['*'] || readUnlock(event).version || null
}
export function clearLibraryUnlock(event: H3Event) {
clearFolderUnlock(event)
}
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}`
}