import { createInterface } from 'node:readline'; import { loadSession, validateSession, signIn, saveSession } from '../../auth.js'; import { readMeta, writeMeta } from './daemon.js'; import { getGatewayUrl } from './login.js'; interface MintedToken { id: string; label: string; plaintext: string; } /** * Call POST /api/admin/tokens with the session cookie and return the minted token. * Exits the process on network or auth errors. */ export async function mintAdminToken( gatewayUrl: string, cookie: string, label: string, ): Promise { let res: Response; try { res = await fetch(`${gatewayUrl}/api/admin/tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie, Origin: gatewayUrl, }, body: JSON.stringify({ label, scope: 'admin' }), }); } catch (err) { console.error( `Could not reach gateway at ${gatewayUrl}: ${err instanceof Error ? err.message : String(err)}`, ); process.exit(1); } if (res.status === 401 || res.status === 403) { console.error( `Session rejected by the gateway (${res.status.toString()}) — your session may be expired.`, ); console.error('Run: mosaic gateway login'); process.exit(2); } if (!res.ok) { const body = await res.text().catch(() => ''); console.error( `Gateway rejected token creation (${res.status.toString()}): ${body.slice(0, 200)}`, ); process.exit(3); } const data = (await res.json()) as { id: string; label: string; plaintext: string }; return { id: data.id, label: data.label, plaintext: data.plaintext }; } /** * Persist the new token into meta.json and print the confirmation banner. */ export function persistToken(gatewayUrl: string, minted: MintedToken): void { const meta = readMeta() ?? { version: 'unknown', installedAt: new Date().toISOString(), entryPoint: '', host: new URL(gatewayUrl).hostname, port: parseInt(new URL(gatewayUrl).port || '14242', 10), }; writeMeta({ ...meta, adminToken: minted.plaintext }); const preview = `${minted.plaintext.slice(0, 8)}...`; console.log(); console.log(`Token minted: ${minted.label}`); console.log(`Preview: ${preview}`); console.log('Token saved to meta.json. Use it with admin endpoints.'); } /** * Require a valid session for the given gateway URL. * Returns the session cookie or exits if not authenticated. */ export async function requireSession(gatewayUrl: string): Promise { const session = loadSession(gatewayUrl); if (session) { const valid = await validateSession(gatewayUrl, session.cookie); if (valid) return session.cookie; } console.error('Not signed in or session expired.'); console.error('Run: mosaic gateway login'); process.exit(2); } /** * Ensure a valid session for the gateway, prompting for credentials if needed. * On sign-in failure, prints the error and exits non-zero. * Returns the session cookie. */ export async function ensureSession(gatewayUrl: string): Promise { // Try the stored session first const session = loadSession(gatewayUrl); if (session) { const valid = await validateSession(gatewayUrl, session.cookie); if (valid) return session.cookie; console.log('Stored session is invalid or expired. Please sign in again.'); } else { console.log(`No session found for ${gatewayUrl}. Please sign in.`); } // Prompt for credentials const rl = createInterface({ input: process.stdin, output: process.stdout }); const ask = (q: string): Promise => new Promise((resolve) => rl.question(q, resolve)); const email = (await ask('Email: ')).trim(); const password = (await ask('Password: ')).trim(); rl.close(); const auth = await signIn(gatewayUrl, email, password).catch((err: unknown) => { console.error(err instanceof Error ? err.message : String(err)); process.exit(2); }); saveSession(gatewayUrl, auth); console.log(`Signed in as ${auth.email}`); return auth.cookie; } /** * `mosaic gateway config rotate-token` — requires an existing valid session. */ export async function runRotateToken(gatewayUrl?: string): Promise { const url = getGatewayUrl(gatewayUrl); const cookie = await requireSession(url); const label = `CLI rotated token (${new Date().toISOString().slice(0, 10)})`; const minted = await mintAdminToken(url, cookie, label); persistToken(url, minted); } /** * `mosaic gateway config recover-token` — prompts for login if no session exists. */ export async function runRecoverToken(gatewayUrl?: string): Promise { const url = getGatewayUrl(gatewayUrl); const cookie = await ensureSession(url); const label = `CLI recovery token (${new Date().toISOString().slice(0, 16).replace('T', ' ')})`; const minted = await mintAdminToken(url, cookie, label); persistToken(url, minted); }