import type { Request, RequestHandler } from "express"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { toNodeHandler } from "better-auth/node"; import type { Db } from "@paperclip/db"; import { authAccounts, authSessions, authUsers, authVerifications, } from "@paperclip/db"; import type { Config } from "../config.js"; export type BetterAuthSessionUser = { id: string; email?: string | null; name?: string | null; }; export type BetterAuthSessionResult = { session: { id: string; userId: string } | null; user: BetterAuthSessionUser | null; }; type BetterAuthInstance = ReturnType; function headersFromExpressRequest(req: Request): Headers { const headers = new Headers(); for (const [key, raw] of Object.entries(req.headers)) { if (!raw) continue; if (Array.isArray(raw)) { for (const value of raw) headers.append(key, value); continue; } headers.set(key, raw); } return headers; } export function createBetterAuthInstance(db: Db, config: Config): BetterAuthInstance { const baseUrl = config.authBaseUrlMode === "explicit" ? config.authPublicBaseUrl : undefined; const secret = process.env.BETTER_AUTH_SECRET ?? process.env.PAPERCLIP_AGENT_JWT_SECRET ?? "paperclip-dev-secret"; const authConfig = { baseURL: baseUrl, secret, database: drizzleAdapter(db, { provider: "pg", schema: { user: authUsers, session: authSessions, account: authAccounts, verification: authVerifications, }, }), emailAndPassword: { enabled: true, requireEmailVerification: false, }, }; if (!baseUrl) { delete (authConfig as { baseURL?: string }).baseURL; } return betterAuth(authConfig); } export function createBetterAuthHandler(auth: BetterAuthInstance): RequestHandler { const handler = toNodeHandler(auth); return (req, res, next) => { void Promise.resolve(handler(req, res)).catch(next); }; } export async function resolveBetterAuthSession( auth: BetterAuthInstance, req: Request, ): Promise { const api = (auth as unknown as { api?: { getSession?: (input: unknown) => Promise } }).api; if (!api?.getSession) return null; const sessionValue = await api.getSession({ headers: headersFromExpressRequest(req), }); if (!sessionValue || typeof sessionValue !== "object") return null; const value = sessionValue as { session?: { id?: string; userId?: string } | null; user?: { id?: string; email?: string | null; name?: string | null } | null; }; const session = value.session?.id && value.session.userId ? { id: value.session.id, userId: value.session.userId } : null; const user = value.user?.id ? { id: value.user.id, email: value.user.email ?? null, name: value.user.name ?? null, } : null; if (!session || !user) return null; return { session, user }; }