@nestbox-ai/cli
Version:
The cli tools that helps developers to build agents
185 lines (163 loc) • 7.22 kB
text/typescript
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');
}
});
}