UNPKG

@nestbox-ai/cli

Version:

The cli tools that helps developers to build agents

185 lines (163 loc) 7.22 kB
import { Command } from 'commander'; import chalk from 'chalk'; import inquirer from 'inquirer'; import open from 'open'; import ora from 'ora'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { AuthApi, Configuration, OAuthLoginRequestDTOTypeEnum } from '@nestbox-ai/admin'; import axios from 'axios'; export function registerLoginCommand(program: Command): void { program .command('login <nestbox-domain>') .description('Login using Google SSO') .action(async (domain: string) => { console.log('Login command triggered for domain:', domain); const spinner = ora('Initiating Google login...').start(); try { // Determine the protocol and construct the auth URL based on the provided domain let authUrl; if (domain.includes('localhost')) { // Use HTTP for localhost and specific port authUrl = `http://${domain}/cli/auth`; } else { // Use HTTPS for all other domains authUrl = `https://${domain}/cli/auth`; } spinner.text = 'Opening browser for Google authentication...'; // Open the browser for authentication await open(authUrl); spinner.succeed('Browser opened for authentication'); // Prompt user to paste the combined token and API URL const { combinedInput } = await inquirer.prompt<{ combinedInput: string }>([ { type: 'input', name: 'combinedInput', message: 'After authenticating, please paste the data here:', validate: (input) => input.trim().length > 0 || 'Input is required' } ]); // Split the input by comma const [accessToken, apiServerUrl] = combinedInput.split(',').map(item => item.trim()); if (!accessToken || !apiServerUrl) { spinner.fail('Invalid input format. Expected: token,apiServerUrl'); return; } console.log(chalk.green('Credentials received. Extracting user information...')); // Fetch user data from the token let email = ''; let name = ''; let picture = ''; try { // Try to decode JWT to get user data (email, name, picture, etc.) const tokenParts = accessToken.split('.'); if (tokenParts.length === 3) { // Base64 decode the payload part of JWT const base64Payload = tokenParts[1].replace(/-/g, '+').replace(/_/g, '/'); const decodedPayload = Buffer.from(base64Payload, 'base64').toString('utf-8'); const tokenPayload = JSON.parse(decodedPayload); // Extract user information email = tokenPayload.email || ''; name = tokenPayload.name || ''; picture = tokenPayload.picture || ''; } } catch (e) { console.log(chalk.yellow('Could not decode token payload. Will prompt for email.')); } // If email couldn't be extracted from token, prompt user if (!email) { const response = await inquirer.prompt<{ email: string }>([ { type: 'input', name: 'email', message: 'Enter your email address:', validate: (input) => /\S+@\S+\.\S+/.test(input) || 'Please enter a valid email' } ]); email = response.email; } spinner.start('Verifying access token...'); if (apiServerUrl && email && accessToken) { // Verify the access token const configuration = new Configuration({ basePath: apiServerUrl, accessToken: accessToken, }); const authApi = new AuthApi(configuration); try { const response = await authApi.authControllerOAuthLogin({ providerId: accessToken, type: OAuthLoginRequestDTOTypeEnum.Google, email, profilePictureUrl: picture || '', }); const authResponse = response.data; // Save credentials to file try { // Create directory structure const configDir = path.join(os.homedir(), '.config', '.nestbox'); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } // Create the file path const fileName = `${email.replace('@', '_at_')}_${domain}.json`; const filePath = path.join(configDir, fileName); // Create credentials object const credentials = { domain, email, token: authResponse.token, accessToken, // Save the original accessToken apiServerUrl, name, picture, timestamp: new Date().toISOString() }; // Write to file fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2)); spinner.succeed('Authentication successful'); console.log(chalk.green(`Successfully logged in as ${email}`)); console.log(chalk.blue(`Credentials saved to: ${filePath}`)); } catch (fileError) { spinner.warn('Authentication successful, but failed to save credentials file'); console.error(chalk.yellow('File error:'), fileError instanceof Error ? fileError.message : 'Unknown error'); } } catch (authError) { spinner.fail('Failed to verify access token'); if (axios.isAxiosError(authError) && authError.response) { if (authError.response.data.message === "user.not_found") { console.error(chalk.red('Authentication Error:'), "You need to register your email with the Nestbox platform"); const { openSignup } = await inquirer.prompt<{ openSignup: boolean }>([ { type: 'confirm', name: 'openSignup', message: 'Would you like to open the signup page to register?', default: true } ]); if (openSignup) { // Construct signup URL with the same protocol logic as login let signupUrl; if (domain.includes('localhost')) { signupUrl = `http://${domain}`; } else { signupUrl = `https://${domain}`; } console.log(chalk.blue(`Opening signup page: ${signupUrl}`)); await open(signupUrl); } } } else { console.error(chalk.red('Authentication Error:'), authError instanceof Error ? authError.message : 'Unknown error'); } } } else { spinner.fail('Missing required information for authentication'); } } catch (error) { spinner.fail('Authentication failed'); console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error'); } }); }