UNPKG

@onboardbase/cli

Version:

[![Version](https://img.shields.io/npm/v/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![Downloads/week](https://img.shields.io/npm/dw/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![License](https://img

216 lines (215 loc) 9.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RunCommandService = void 0; const utils_1 = require("../utils"); const chalk = require("chalk"); const inquirer = require("inquirer"); const kill = require("tree-kill"); const base_service_1 = require("../common/base.service"); const child_process_1 = require("child_process"); const secrets_1 = require("./secrets"); const socket_service_1 = require("./socket.service"); const access_manager_1 = require("./access-manager"); const types_1 = require("../common/types"); const errors_1 = require("../common/errors"); class RunCommandService extends base_service_1.BaseService { constructor(configManager) { super(configManager); this.reInitializeReason = "remote updates"; this.shouldReInitialize = false; this.currentSecrets = null; this.secretService = new secrets_1.SecretsService(configManager); this.socketService = new socket_service_1.SocketService(configManager); this.accessManager = new access_manager_1.AccessManager(configManager); } _getPrefix(userPrefix) { const prefix = userPrefix !== null && userPrefix !== void 0 ? userPrefix : this.configManager.getFromProject("setup.prefix"); return prefix; } _executeCommandAsAProcess(cmd, envs) { this.currentDaemon = (0, child_process_1.spawnSync)(cmd, { env: Object.assign(envs, { FORCE_COLOR: true }), cwd: process.cwd(), stdio: "inherit", windowsHide: false, shell: true, }); } async initialize({ args, flags }) { const commandToExecute = this._getFromProjectConfigOrThrow({ userConfig: args.command || flags.command, configPath: "setup.start_script", errMsg: "Please specify the command to run", }); const config = this.getConfigForCurrentScope(); if (config.requirePasswordForCurrentSession) { const passwordVerified = await this.verifyPassword(flags.password, config.password); if (!passwordVerified) { throw errors_1.BadInputError.from("password not verified"); } } const currentPrefix = this.configManager.getEnvPrefix(flags.prefix); const genericSecret = flags["secrets"]; const sourcePath = flags["source-path"]; const verbose = flags["verbose"]; if (genericSecret) { const envs = this.secretService.parseUserGeneratedSecrets(genericSecret); const mergedEnvs = this.secretService.generateEnvs(envs, currentPrefix, verbose); this._executeCommandAsAProcess(commandToExecute, mergedEnvs); return; } if (sourcePath) { const envs = this.secretService.getSecretsFromSourcePath(sourcePath); const mergedEnvs = this.secretService.generateEnvs(envs, currentPrefix); this._executeCommandAsAProcess(commandToExecute, mergedEnvs); return; } const { currentProject, currentEnvironment } = this._handleGetProjectAndEnvironment(flags); const sync = flags["sync"]; const shouldHandleLiveReload = flags["live-reload"]; const shouldAcceptSecretShare = flags["accept-secret-sharing"]; const cliToken = this._getFromGlobalConfigOrThrow({ configPath: "token", envName: types_1.ENV_NAMES.TOKEN, }); await this.handleUnauthorizedEnvironmentAccess(cliToken, currentProject, currentEnvironment); // Although a project and an environment might not exist in // the current team, // the API does respond with an empty array list of secrets. // in the event that a project does not exist or does not have secrets // or the user does not have access to the project. // We have to prevent secret leaks which is assumed to be best done in the // API layer. // // Force Sync only if the last successful project known in the global config // is not the same with the provided project however it was provided. const forceSync = this.shouldForceSync(currentProject); const resolvedSecrets = await this.secretService.getSecrets(cliToken, currentProject, currentEnvironment, forceSync || sync // ); const mergedEnvs = this.secretService.generateEnvs(resolvedSecrets, currentPrefix, verbose); await this._setupSideEffects({ cliToken, project: currentProject, environment: currentEnvironment, shouldHandleLiveReload, shouldAcceptSecretShare, }); this._executeCommandAsAProcess(commandToExecute, mergedEnvs); if (this.shouldReInitialize) { await this._reInitialize(args, flags); } this._runCleanups(); } shouldForceSync(suppliedProjectName) { const configProjectName = this.getConfigForCurrentScope("project-name"); return suppliedProjectName && configProjectName !== suppliedProjectName; } async handleUnauthorizedEnvironmentAccess(cliToken, currentProject, currentEnvironment) { // always ensure user has access to this project and environment const isAuthorized = await this.accessManager.checkUserAccess(cliToken, currentProject, currentEnvironment); if (!isAuthorized) { this.secretService.clearCache(currentProject, currentEnvironment); throw errors_1.BadNetworkError.from(`You do not have access to this project and/or environment`); } } async _reInitialize(args, flags) { this.shouldReInitialize = false; // Recursively re-initializes this command, // this is used for restarts console.log("Re-starting session...", this.reInitializeReason ? `due to ${this.reInitializeReason}` : ""); this._stopCurrentDaemon(); await this.initialize({ args, flags: Object.assign({}, flags) }); } async _setupSideEffects({ cliToken, project, environment, shouldHandleLiveReload, shouldAcceptSecretShare, }) { const { user } = await this.accessManager.getAuthInfoFromDeviceToken(cliToken); if (!user) { console.log("Please authenticate your CLI"); return; } await this.socketService.createSocketInstance({ project, environment, userEmail: user.email, }); if (shouldHandleLiveReload) { this.socketService.onUpdateEventEmitted(this._handleLiveReload); } if (shouldAcceptSecretShare) { this.socketService.onSecretShareEventEmitted(user.email, project, this._handleSecretShareEvent); } } async _handleLiveReload() { this.reInitializeReason = `${chalk.green("√")} ${chalk.bold(chalk.gray("Fetched new secrets.. and restarting your command"))}`; this.shouldReInitialize = true; await this._stopCurrentDaemon(); } async _handleSecretShareEvent(data) { // Load the .onboardbase file or create new one if it does not exist // const localSecrets = getLocalSecrets() ?? {}; // // Add the new suggestions to the .onboardbase file // if (data.secrets && data.secrets.length) { // await Promise.all( // data.secrets.map(async (encryptedSecret) => { // const decryptedSecret = await decryptPlainSecretAndReturnPlainValue( // encryptedSecret, // data.email // ); // const secret = JSON.parse(decryptedSecret); // localSecrets[secret.key] = secret.value; // }) // ); // } // // Update the onboardbase file // updateLocalSecrets(localSecrets); this.shouldReInitialize = true; this.reInitializeReason = chalk.green(`!A new secret has been suggested by ${chalk.bold.green(data.email)}...`); await this._stopCurrentDaemon(); } async _runCleanups() { this.socketService.closeSocket(); } async _stopCurrentDaemon() { try { kill(-this.currentDaemon.pid); } catch (error) { process.exit(0); } } async getPasswordFromUser() { const { sessionPassword } = await inquirer.prompt([ { message: "Password Required", type: "password", name: "sessionPassword", }, ]); if (!sessionPassword) { throw errors_1.BadInputError.from("Password required for this session"); } return sessionPassword; } async validatePassword(userPassword, configPasswordCipher) { const configPassword = await (0, utils_1.decryptGenericSecret)(configPasswordCipher); return userPassword.trim() === configPassword.toString(); } async verifyPassword(plainPassword, encryptedConfigPassword) { if (!plainPassword) { plainPassword = await this.getPasswordFromUser(); } const validatonResult = await this.validatePassword(plainPassword, encryptedConfigPassword); return validatonResult; } _registerProjectAccessLog(project, environment) { // * Only a create a project log when CLI is able to fetch secrets successfully. // */ // if (ConfigManager.shouldCreateProjectLog) { // createProjectLogTable( // { email: ConfigManager.getAuthSessionDetails().email }, // ConfigManager.getAuthSessionDetails().team, // ConfigManager.getAuthSessionDetails().project // ); // } } } exports.RunCommandService = RunCommandService;