UNPKG

@visionfi/server-cli

Version:

Command-line interface for VisionFI Server SDK

303 lines (302 loc) 13.2 kB
/** * Authentication commands for VisionFI CLI * Copyright (c) 2024-2025 VisionFI. All Rights Reserved. */ import { Display } from '../utils/display.js'; import { loadConfig, saveConfig, createClient, getConfigPath } from '../utils/config.js'; import inquirer from 'inquirer'; import * as fs from 'fs'; /** * Verify authentication with the API */ export async function verifyAuth() { const spinner = Display.spinner('Verifying authentication...'); const config = loadConfig(); try { const client = createClient(); const result = await client.authentication.verify(); spinner.succeed('Authentication verified'); if (result.authenticated) { Display.success('Successfully authenticated with VisionFI API'); // Show current auth method if configured if (config.authMethod === 'impersonate' && config.impersonateServiceAccount) { Display.info(`Using impersonation: ${config.impersonateServiceAccount}`); } else if (config.serviceAccountPath) { Display.info(`Using service account: ${config.serviceAccountPath}`); } else { Display.info('Using Application Default Credentials'); } } else { Display.warning('Authentication check returned false'); } } catch (error) { spinner.fail('Authentication failed'); // Check for common authentication errors and provide helpful guidance const errorMessage = error.message || ''; if (errorMessage.includes('Could not load the default credentials')) { Display.error('Could not load Application Default Credentials'); Display.info(''); if (config.authMethod === 'impersonate') { Display.info('For impersonation mode, you need to authenticate first:'); Display.info(' 1. Install gcloud CLI: https://cloud.google.com/sdk/docs/install'); Display.info(' 2. Run: gcloud auth application-default login'); Display.info(' 3. Ensure you have IAM role: roles/iam.serviceAccountTokenCreator'); Display.info(` 4. On service account: ${config.impersonateServiceAccount}`); } else { Display.info('Please authenticate using one of these methods:'); Display.info(' 1. Run: gcloud auth application-default login'); Display.info(' 2. Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json'); Display.info(' 3. Use: visionfi auth configure'); } } else if (errorMessage.includes('Permission') || errorMessage.includes('403')) { Display.error(errorMessage); Display.info(''); if (config.authMethod === 'impersonate') { Display.info('Make sure you have the required IAM permissions:'); Display.info(' - roles/iam.serviceAccountTokenCreator'); Display.info(` - On service account: ${config.impersonateServiceAccount}`); Display.info(''); Display.info('To grant permissions, run:'); Display.info(` gcloud iam service-accounts add-iam-policy-binding \\`); Display.info(` ${config.impersonateServiceAccount} \\`); Display.info(` --member="user:YOUR_EMAIL@example.com" \\`); Display.info(` --role="roles/iam.serviceAccountTokenCreator"`); } } else { Display.error(errorMessage); } process.exit(1); } } /** * Configure authentication */ export async function configureAuth(options) { const config = loadConfig(); // If options are provided, use non-interactive mode if (options?.method) { if (options.method === 'file') { if (!options.serviceAccountPath) { Display.error('Service account path is required when using file method'); process.exit(1); } if (!fs.existsSync(options.serviceAccountPath)) { Display.error(`File does not exist: ${options.serviceAccountPath}`); process.exit(1); } config.authMethod = 'file'; config.serviceAccountPath = options.serviceAccountPath; delete config.impersonateServiceAccount; saveConfig(config); Display.success('Authentication configured with service account file'); return; } else if (options.method === 'adc') { // Clear any existing service account path config.authMethod = 'adc'; delete config.serviceAccountPath; delete config.impersonateServiceAccount; saveConfig(config); Display.success('Authentication configured to use Application Default Credentials'); Display.info('Make sure you have run: gcloud auth application-default login'); return; } else if (options.method === 'impersonate') { if (!options.impersonateServiceAccount) { Display.error('Service account email is required when using impersonate method'); Display.info('Usage: visionfi auth configure --method=impersonate --impersonate-service-account=EMAIL'); process.exit(1); } if (!options.impersonateServiceAccount.includes('@') || !options.impersonateServiceAccount.endsWith('.iam.gserviceaccount.com')) { Display.error('Invalid service account email format'); Display.info('Expected format: account-name@project-id.iam.gserviceaccount.com'); process.exit(1); } config.authMethod = 'impersonate'; config.impersonateServiceAccount = options.impersonateServiceAccount; delete config.serviceAccountPath; saveConfig(config); Display.success('Authentication configured for service account impersonation'); Display.info(`Impersonating: ${options.impersonateServiceAccount}`); Display.info('Make sure you have:'); Display.info(' 1. Authenticated with: gcloud auth application-default login'); Display.info(' 2. IAM role: roles/iam.serviceAccountTokenCreator'); return; } else if (options.method === 'env') { Display.info('Set one of the following environment variables:'); Display.keyValue('GOOGLE_APPLICATION_CREDENTIALS', '/path/to/service-account.json'); Display.keyValue('VISIONFI_IMPERSONATE_SERVICE_ACCOUNT', 'account@project.iam.gserviceaccount.com'); return; } else { Display.error(`Invalid authentication method: ${options.method}`); Display.info('Valid methods are: file, adc, impersonate, env'); process.exit(1); } } // Interactive mode const answers = await inquirer.prompt([ { type: 'list', name: 'authMethod', message: 'Select authentication method:', choices: [ { name: 'Service Account JSON file', value: 'file' }, { name: 'Application Default Credentials (ADC)', value: 'adc' }, { name: 'Service Account Impersonation (for internal devs)', value: 'impersonate' }, { name: 'Environment Variable', value: 'env' } ] } ]); if (answers.authMethod === 'file') { const fileAnswer = await inquirer.prompt([ { type: 'input', name: 'path', message: 'Enter path to service account JSON file:', validate: (input) => { if (!fs.existsSync(input)) { return 'File does not exist'; } return true; } } ]); config.authMethod = 'file'; config.serviceAccountPath = fileAnswer.path; delete config.impersonateServiceAccount; saveConfig(config); Display.success('Authentication configured with service account file'); } else if (answers.authMethod === 'adc') { // Clear any existing service account path config.authMethod = 'adc'; delete config.serviceAccountPath; delete config.impersonateServiceAccount; saveConfig(config); Display.success('Authentication configured to use Application Default Credentials'); Display.info('Make sure you have run: gcloud auth application-default login'); } else if (answers.authMethod === 'impersonate') { const impersonateAnswer = await inquirer.prompt([ { type: 'input', name: 'serviceAccount', message: 'Enter service account email to impersonate:', validate: (input) => { if (!input.includes('@') || !input.endsWith('.iam.gserviceaccount.com')) { return 'Must be a valid service account email (account@project.iam.gserviceaccount.com)'; } return true; } } ]); config.authMethod = 'impersonate'; config.impersonateServiceAccount = impersonateAnswer.serviceAccount; delete config.serviceAccountPath; saveConfig(config); Display.success('Authentication configured for service account impersonation'); Display.info(`Impersonating: ${impersonateAnswer.serviceAccount}`); Display.info('Make sure you have:'); Display.info(' 1. Authenticated with: gcloud auth application-default login'); Display.info(' 2. IAM role: roles/iam.serviceAccountTokenCreator'); } else { Display.info('Set one of the following environment variables:'); Display.keyValue('GOOGLE_APPLICATION_CREDENTIALS', '/path/to/service-account.json'); Display.keyValue('VISIONFI_IMPERSONATE_SERVICE_ACCOUNT', 'account@project.iam.gserviceaccount.com'); } } /** * Get and display the JWT token */ export async function getToken() { const spinner = Display.spinner('Retrieving authentication token...'); spinner.fail('getToken method not available in new SDK'); Display.error('ID tokens are automatically handled by the SDK'); process.exit(1); } /** * Logout and clear authentication configuration */ export async function logout() { const config = loadConfig(); const configPath = getConfigPath(); // Check if config exists if (!fs.existsSync(configPath)) { Display.warning('No configuration file found'); Display.info(`Config path: ${configPath}`); return; } // Show what will be cleared if (config.authMethod === 'impersonate' && config.impersonateServiceAccount) { Display.info(`Current auth: Impersonating ${config.impersonateServiceAccount}`); } else if (config.serviceAccountPath) { Display.info(`Current auth: Service account file ${config.serviceAccountPath}`); } else { Display.info('Current auth: Application Default Credentials'); } // Ask for confirmation const answers = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Are you sure you want to clear the authentication configuration?', default: false } ]); if (!answers.confirm) { Display.info('Logout cancelled'); return; } // Clear the config file try { fs.unlinkSync(configPath); Display.success('Authentication configuration cleared'); Display.info('You will need to run "visionfi auth configure" to authenticate again'); } catch (error) { Display.error(`Failed to clear configuration: ${error.message}`); process.exit(1); } } /** * Show current authentication configuration */ export async function showAuth() { const config = loadConfig(); const configPath = getConfigPath(); if (!fs.existsSync(configPath)) { Display.warning('No configuration file found'); Display.info('Run "visionfi auth configure" to set up authentication'); return; } Display.info('Current Authentication Configuration:'); Display.info(''); if (config.authMethod === 'impersonate' && config.impersonateServiceAccount) { Display.keyValue('Method', 'Service Account Impersonation'); Display.keyValue('Service Account', config.impersonateServiceAccount); } else if (config.serviceAccountPath) { Display.keyValue('Method', 'Service Account File'); Display.keyValue('Path', config.serviceAccountPath); } else { Display.keyValue('Method', 'Application Default Credentials'); } if (config.apiUrl) { Display.keyValue('API URL', config.apiUrl); } Display.info(''); Display.info(`Config file: ${configPath}`); }