import { initializeApp, getApp } from 'firebase/app'; import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'; import { IndemnClient } from './client.js'; import type { Environment, EnvironmentHosts } from './types.js'; // Firebase client configs per environment — public configuration (not secrets), // bundled the same way the Copilot Dashboard bundles them via envsubst const FIREBASE_CONFIGS: Record> = { dev: { apiKey: 'AIzaSyB8bA3w8oaMbyMxYA7lyQy_OvrPcPfW7x8', authDomain: 'agent-449107.firebaseapp.com', projectId: 'gmail-agent-449107', storageBucket: 'gmail-agent-449107.firebasestorage.app', messagingSenderId: '1066107427076', appId: '1:1066107427076:web:f57925047f40a6a76a5851', }, production: { apiKey: 'AIzaSyBkj3ny3Gm7DapoeiTi3wt_9eG8hnlqqyc', authDomain: 'prod-gemini-470505.firebaseapp.com', projectId: 'prod-gemini-470505', storageBucket: 'prod-gemini-470505.firebasestorage.app', messagingSenderId: '334963688118', appId: '1:334963688118:web:21670b3a97547d4b231c17', }, }; export async function login(email: string, password: string, env: Environment = 'dev', opts?: { mfaCodePrompt?: () => Promise; }): Promise<{ apiKey: string; orgId: string; userId: string; environment: Environment; }> { // Step 1: Firebase client SDK auth (environment-specific config, separate app per env) const firebaseConfig = FIREBASE_CONFIGS[env]; const appName = `indemn-cli-${env}`; let app; try { app = getApp(appName); } catch { app = initializeApp(firebaseConfig, appName); } const auth = getAuth(app); const userCredential = await signInWithEmailAndPassword(auth, email, password); const idToken = await userCredential.user.getIdToken(); // Step 2: Exchange Firebase ID token for Copilot Server JWT const client = new IndemnClient({ environment: env }); const signinResponse = await client.copilotPostUnauth<{ success: boolean; requiresMfa: boolean; mfaSessionId?: string; mfaEmail?: string; token?: string; user?: { _id: string; email: string }; }>('/auth/firebase/signin', { idToken }); let jwt: string; let userId: string; if (signinResponse.requiresMfa) { // MFA required — prompt for code and verify if (!opts?.mfaCodePrompt) { throw new Error('MFA is required but no code prompt handler was provided.'); } const code = await opts.mfaCodePrompt(); const mfaResponse = await client.copilotPostUnauth<{ success: boolean; token: string; user: { _id: string; email: string }; }>('/auth/firebase/mfa/verify', { sessionId: signinResponse.mfaSessionId, code, }); jwt = mfaResponse.token.replace(/^JWT\s+/i, ''); userId = mfaResponse.user._id; } else { // No MFA — direct JWT jwt = signinResponse.token!.replace(/^JWT\s+/i, ''); userId = signinResponse.user!._id; } // Step 3: Generate persistent API key using the JWT const apiKeyResponse = await client.copilotPostUnauth<{ _id: string; key: string; name: string; org_id: string; }>('/auth/api-keys', { name: `indemn-cli-${Date.now()}` }, { 'Authorization': `jwt ${jwt}`, }); if (!apiKeyResponse.org_id) { throw new Error('Login succeeded but no organization found. Contact your admin to be added to an organization.'); } // Step 3.5: Fetch the org name so the banner can display it without a lookup on every command. let orgName: string | undefined; try { const orgRes = await fetch(`${client.getHosts().copilot}/organization/${apiKeyResponse.org_id}`, { headers: { 'Authorization': `Bearer ${apiKeyResponse.key}`, 'Content-Type': 'application/json', }, }); if (orgRes.ok) { const data = await orgRes.json() as { organization?: { name?: string } }; orgName = data.organization?.name; } } catch { // Non-fatal — banner falls back to org ID if name unavailable } const config = { api_key: apiKeyResponse.key, org_id: apiKeyResponse.org_id, org_name: orgName, user_id: userId, environment: env, }; // Step 4: Save to config file IndemnClient.saveConfig(config); return { apiKey: config.api_key, orgId: config.org_id, userId: config.user_id, environment: env, }; } export function logout(): void { IndemnClient.clearConfig(); } export function whoami(): { apiKey: string; orgId: string; userId: string; environment: Environment; hosts: EnvironmentHosts; } | null { const config = IndemnClient.loadConfig(); if (!config || !config.api_key) return null; return { apiKey: config.api_key.substring(0, 12) + '...', orgId: config.org_id, userId: config.user_id, environment: config.environment, hosts: config.hosts[config.environment], }; }