import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; import { resolve } from 'node:path'; import { homedir } from 'node:os'; const SESSION_DIR = resolve(homedir(), '.mosaic'); const SESSION_FILE = resolve(SESSION_DIR, 'session.json'); interface StoredSession { gatewayUrl: string; cookie: string; userId: string; email: string; expiresAt: string; } export interface AuthResult { cookie: string; userId: string; email: string; } /** * Sign in to the gateway and return the session cookie. */ export async function signIn( gatewayUrl: string, email: string, password: string, ): Promise { const res = await fetch(`${gatewayUrl}/api/auth/sign-in/email`, { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: gatewayUrl }, body: JSON.stringify({ email, password }), redirect: 'manual', }); if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`Sign-in failed (${res.status}): ${body}`); } // Extract set-cookie header const setCookieHeader = res.headers.getSetCookie?.() ?? []; const sessionCookie = setCookieHeader .map((c) => c.split(';')[0]!) .filter((c) => c.startsWith('better-auth.session_token=')) .join('; '); if (!sessionCookie) { throw new Error('No session cookie returned from sign-in'); } // Parse the response body for user info const data = (await res.json()) as { user?: { id: string; email: string } }; const userId = data.user?.id ?? 'unknown'; const userEmail = data.user?.email ?? email; return { cookie: sessionCookie, userId, email: userEmail }; } /** * Save session to ~/.mosaic/session.json */ export function saveSession(gatewayUrl: string, auth: AuthResult): void { if (!existsSync(SESSION_DIR)) { mkdirSync(SESSION_DIR, { recursive: true }); } const session: StoredSession = { gatewayUrl, cookie: auth.cookie, userId: auth.userId, email: auth.email, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days }; writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), 'utf-8'); } /** * Load a saved session. Returns null if no session, expired, or wrong gateway. */ export function loadSession(gatewayUrl: string): AuthResult | null { if (!existsSync(SESSION_FILE)) return null; try { const raw = readFileSync(SESSION_FILE, 'utf-8'); const session = JSON.parse(raw) as StoredSession; if (session.gatewayUrl !== gatewayUrl) return null; if (new Date(session.expiresAt) < new Date()) return null; return { cookie: session.cookie, userId: session.userId, email: session.email, }; } catch { return null; } } /** * Validate that a stored session is still active by hitting get-session. */ export async function validateSession(gatewayUrl: string, cookie: string): Promise { try { const res = await fetch(`${gatewayUrl}/api/auth/get-session`, { headers: { Cookie: cookie, Origin: gatewayUrl }, }); return res.ok; } catch { return false; } }