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

1,022 lines (1,021 loc) 48.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isDirectoryWritable = exports.makeRandomId = exports.isUnix = exports.isSudoUser = exports.isOnboardbaseServiceInstalled = exports.getPathToServiceFiles = exports.createProjectLogTable = exports.getLatestCliVersion = exports.downloadTarballs = exports.parseEnvContentToObject = exports.parseObjectToEnv = exports.uploadSecretsToOnboardbase = exports.duplicateExistingSecretAndUpdateEnvironment = exports.removeDuplicateSecrete = exports.formatDate = exports.encryptWithAESAndRSA = exports.rsaEncryptSecret = exports.aesDecryptSecret = exports.getFrontendEncryptionKey = exports.rsaDecryptSecret = exports.generateRsaKeys = exports.checkProjectCacheStatus = exports.recheckUserAccess = exports.handleSocketConnection = exports.deleteFallbackSecret = exports.deleteRecursiveFiles = exports.getFallbackSecret = exports.createFallbackSecret = exports.decryptSecrets = exports.decryptRawSecretsAndReturnAllProperties = exports.decryptGenericSecret = exports.decryptPlainSecretAndReturnPlainValue = exports.encryptSecrets = exports.getFallbackDirectory = exports.getMachineID = exports.updateLocalSecrets = exports.getLocalSecrets = exports.parseLocalSecrets = exports.getCurrentWorkingDirectory = exports.getHomeDirectory = exports.addPrefixToEnvs = exports.downloadSecretsForRecommendation = exports.downloadSecrets = exports.throwSecretsNotAccessibleError = exports.fetchRawSecrets = exports.fetchRawSecretsIncludingSecretProperties = exports.getEnvironmentId = exports.isExist = exports.executeCommand = exports.executeCommandUsingChildProcessExec = void 0; exports.isJSON = exports.isAPartialUrl = exports.isValidURL = exports.getShellRc = void 0; const child_process_1 = require("child_process"); const fs_1 = require("fs"); const path_1 = require("path"); const node_machine_id_1 = require("node-machine-id"); const configuration_1 = require("../configuration"); const os_1 = require("os"); const CryptoJS = require("crypto-js"); // @ts-ignore const io = require("socket.io-client"); const config_1 = require("../configuration/config"); const http_1 = require("../http"); const NodeRSA = require("node-rsa"); const YAML = require("yaml"); const chalk = require("chalk"); const jwt_decode_1 = require("jwt-decode"); const axios_1 = require("axios"); const isDocker_1 = require("./isDocker"); const uploadSecretsActionTypes_enum_1 = require("../enums/uploadSecretsActionTypes.enum"); const util_1 = require("util"); const cli_ux_1 = require("cli-ux"); const executeAsync = (0, util_1.promisify)(child_process_1.exec); const executeCommandUsingChildProcessExec = async (command, env) => { const daemon = await executeAsync(command, { env: Object.assign(process.env, { env }), windowsHide: false, cwd: process.cwd(), }); return daemon; }; exports.executeCommandUsingChildProcessExec = executeCommandUsingChildProcessExec; const executeCommand = (command, envs, logProcess, // Can only accept false since STDIO will be enabled by default if this object is not passed stdio) => { //let socketClient = SyncSocketClient.getInstance(); const daemon = (0, child_process_1.spawn)(command, { env: Object.assign(envs, { FORCE_COLOR: true }), cwd: process.cwd(), stdio: "inherit", windowsHide: false, shell: true, }); return daemon; }; exports.executeCommand = executeCommand; const isExist = (filePath) => { return (0, fs_1.existsSync)(filePath); }; exports.isExist = isExist; const getEnvironmentId = async (project, environment, accessToken) => { const environments = await (0, http_1.fetchSingleProject)(accessToken, project); const environmentId = environments === null || environments === void 0 ? void 0 : environments[0].environments.list.find(({ title }) => title === environment).id; return environmentId; }; exports.getEnvironmentId = getEnvironmentId; const fetchRawSecretsIncludingSecretProperties = async (project, environment, deviceToken) => { var _a, _b, _c, _d, _e; const { accessToken, user } = await (0, http_1.generateAccessToken)(deviceToken); let secrets = await (0, http_1.fetchSecrets)(project, environment, accessToken); //secrets is empty because it is a new account secrets = (_a = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _a === void 0 ? void 0 : _a.generalSecrets.list; const userCanFetchSecretUnderEnvironment = Boolean((_b = secrets === null || secrets === void 0 ? void 0 : secrets[0]) === null || _b === void 0 ? void 0 : _b.member); if (secrets.length > 0 && !userCanFetchSecretUnderEnvironment) { console.log(`Error: ${chalk.red(`You don't have enough permission to update/upload/delete secrets under the ${chalk.bold.greenBright(environment)} environment.`)}`); process.exit(1); } const environmentId = (_e = (_d = (_c = secrets === null || secrets === void 0 ? void 0 : secrets[0]) === null || _c === void 0 ? void 0 : _c.environment) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : (await (0, exports.getEnvironmentId)(project, environment, accessToken)); if (Array.isArray(secrets) && secrets.length) { const secretKeyAndValue = secrets.map(({ key, value, id }) => { return { key, value, id }; }); const aesSecret = await (0, exports.decryptRawSecretsAndReturnAllProperties)(secretKeyAndValue, config_1.default.getEncryptionSecretKey()); return { env: aesSecret, user, accessToken, environmentId, }; } return { env: {}, user, accessToken, environmentId, }; }; exports.fetchRawSecretsIncludingSecretProperties = fetchRawSecretsIncludingSecretProperties; const fetchRawSecrets = async (project, environment, cliToken) => { var _a, _b; const { accessToken, user } = await (0, http_1.generateAccessToken)(cliToken); let secrets = await (0, http_1.fetchSecrets)(project, environment, accessToken); secrets = (_a = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _a === void 0 ? void 0 : _a.generalSecrets.list; const userCanFetchSecretUnderEnvironment = Boolean((_b = secrets === null || secrets === void 0 ? void 0 : secrets[0]) === null || _b === void 0 ? void 0 : _b.member); if (!userCanFetchSecretUnderEnvironment) { console.log(`Error: ${chalk.red(`You don't have enough permission to update/upload/delete secrets under the ${chalk.bold.greenBright(environment)} environment.`)}`); process.exit(1); } const environmentId = secrets === null || secrets === void 0 ? void 0 : secrets[0].environment.id; if (secrets) { const secretKeyAndValue = secrets.map(({ key, value, id }) => { return { key, value, id }; }); const aesSecret = await (0, exports.aesDecryptSecret)(secretKeyAndValue); return { env: aesSecret, user, accessToken, environmentId, }; } return { env: {}, user, accessToken, environmentId, }; }; exports.fetchRawSecrets = fetchRawSecrets; const throwSecretsNotAccessibleError = (project, environment, logProcess) => { console.log(`There was an error fetching secrets for ${chalk.greenBright.bold(project)} with the environment: ${chalk.greenBright.bold(environment)}`); console.log(` ${chalk.greenBright.bold("Possible causes")} > Invalid Device / Service Token > Not enough permission to access the project you want to fetch secrets for. ${chalk.greenBright.bold("Possible solutions")} > Run onboardbase login command again so you can get a fresh auth token. > Run onboardbase setup command again to update your .onboardbase.yaml file > Contact an admin to give you access to the project.`); console.log(""); if (logProcess) { //const socketClient = ConfigManager.getSynSocketClient() } }; exports.throwSecretsNotAccessibleError = throwSecretsNotAccessibleError; /** * * @param project string * @param environment string * @param passphrase string * * This function retrive an access token for this request and fetches the token * from onboardbase. It also create the backup file for caching purposed. * * This function returns a merged version of both local and online secrets. * */ const downloadSecrets = async (project, environment, passphrase, noSystemSecrets = false, logProcess, verbose, shouldUseCache, sourcePath, deviceToken) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; // Fetch localSecrets if any exist const localSecrets = (_a = (0, exports.getLocalSecrets)()) !== null && _a !== void 0 ? _a : {}; let finalEnvs = {}; let finalBackupSecrets = {}; let teamName; if (sourcePath) { try { /** * That is read from stdin */ if (sourcePath === "-") { const stdin = (0, fs_1.readFileSync)(0, "utf-8"); const parsedSecrets = JSON.parse(Object(stdin)); return { env: Object.assign(parsedSecrets, localSecrets, noSystemSecrets ? {} : process.env), user: {}, }; } const isUrl = (0, exports.isAPartialUrl)(sourcePath); /** * That is if a JSON string is passed */ if (!isUrl) { const parsedSecrets = JSON.parse(sourcePath); return { env: Object.assign(parsedSecrets, localSecrets, noSystemSecrets ? {} : process.env), user: {}, }; } /** * if a path to a local file is passed */ if (isUrl) { const secrets = (0, fs_1.readFileSync)(sourcePath, { encoding: "utf8" }); const isJson = (0, exports.isJSON)(secrets); let finalSecrets = {}; if (isJson) finalSecrets = JSON.parse(secrets); if (!isJson) { const newEnvs = secrets.split("\n"); newEnvs.map((keyAndValue) => { const splittedKeyAndValue = keyAndValue.split("="); const key = splittedKeyAndValue[0].toUpperCase(); const value = splittedKeyAndValue[1]; // if (value === undefined) { // throw new Error( // `value for ${chalk.greenBright.bold(key)} was not specified.` // ); // } if (value !== undefined) finalSecrets[key] = value; }); } return { env: Object.assign(finalSecrets, localSecrets, noSystemSecrets ? {} : process.env), user: {}, }; } } catch (error) { console.log(error.message); } } if (shouldUseCache) { try { const fallbackSecrets = await (0, exports.getFallbackSecret)(project, environment, passphrase); if (verbose) { let updatedLocalSecret = {}; const recommendationSecrets = []; const aesSecret = fallbackSecrets; if (Object.keys(localSecrets).length) { Object.keys(localSecrets).map((localSecretkey) => { const key = localSecretkey; const value = localSecrets[key]; if (aesSecret[key] && aesSecret[key] === value) { console.log(`Duplicate secret found: ${chalk.greenBright.bold(`${key}=${value}`)}`, chalk.greenBright("...removing the secret from your local file")); } else updatedLocalSecret[key] = value; if (!aesSecret[key]) recommendationSecrets.push(key); }); if (verbose && recommendationSecrets.length) { let formattedMessage = "We noticed you have some new secret which have not been added to Onboardbase and suggest you create a recommendation to avoid having sensitive secrets locally. The secret keynames are:\n\n"; recommendationSecrets.map((secretKey) => (formattedMessage += `-${secretKey}\n`)); console.log(formattedMessage); console.log(`Run ${chalk.greenBright("onboardbase recommendation:create --from-local-secrets")} to upload your local secrets to onboardbase.`); } config_1.default.updateLocalProjectSecrets(updatedLocalSecret); } } return { env: Object.assign(fallbackSecrets, localSecrets, noSystemSecrets ? {} : process.env), user: {}, }; } catch (error) { const currentVersion = await (0, http_1.checkLiveProjectAndEnvironmentVersion)(project, environment, deviceToken); config_1.default.updateProjectAndEnvironmentVersion(project, environment, currentVersion); return await (0, exports.downloadSecrets)(project, environment, passphrase, false, true, verbose, false, "", deviceToken); } } try { const { accessToken, user } = await (0, http_1.generateAccessToken)(deviceToken); // socketClient.emitEvent("SYNC::LOGS", { // data: "Downloading secrets from onboardbase servers...", // }); let secrets = await (0, http_1.fetchSecrets)(project, environment, accessToken); const userCanFetchSecretUnderEnvironment = Boolean((_e = (_d = (_c = (_b = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _b === void 0 ? void 0 : _b.generalSecrets) === null || _c === void 0 ? void 0 : _c.list) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.member); if (!userCanFetchSecretUnderEnvironment) { // socketClient.emitEvent("SYNC::LOGS", { // data: `Error: You don't have enough permission to fetch secrets under the ${chalk.bold.greenBright( // environment // )} environment.`, // }); console.log(`Error: ${chalk.red(`You don't have enough permission to fetch secrets under the ${chalk.bold.greenBright(environment)} environment.`)}`); // socketClient.emitEvent("SYNC::LOGS", { // data: "Process Exited with exit code of 1", // }); process.exit(1); } /** * save the current user's auth details & project ID to our configManager */ config_1.default.setAuthSessionDetails(Object.assign((0, jwt_decode_1.default)(accessToken), { project: { id: (_k = (_j = (_h = (_g = (_f = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _f === void 0 ? void 0 : _f.generalSecrets) === null || _g === void 0 ? void 0 : _g.list) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.project) === null || _k === void 0 ? void 0 : _k.id }, })); const { team } = config_1.default.getAuthSessionDetails(); teamName = team.name; finalEnvs = Object.assign({}, (noSystemSecrets ? {} : process.env)); /** * Check for errors coming from onboardbase itself. * E.g: unauthorized error and revert back to cache if any exist */ if (secrets.errors) { (0, exports.throwSecretsNotAccessibleError)(project, environment); /** * if CLI failed to fetch secrets from onboardbase, do not create * a project log for that session */ config_1.default.shouldCreateProjectLog = false; const fallbackSecrets = await (0, exports.getFallbackSecret)(project, environment, passphrase); const mergedEnvs = Object.assign(fallbackSecrets, localSecrets); return { env: mergedEnvs, user: {} }; } else { secrets = (_l = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _l === void 0 ? void 0 : _l.generalSecrets.list; // secrets = JSON.parse(secrets[0].key); const secretArray = []; if (secrets && Array.isArray(secrets)) { // socketClient.emitEvent("SYNC::LOGS", { // data: "Decrypting secrets using RSA Encryption...", // }); // socketClient.emitEvent("SYNC::LOGS", { // data: "Decrypting secrets using AES Encryption...", // }); const secretKeyAndValue = secrets.map(({ key, value }) => { return { key, value }; }); const aesSecret = await (0, exports.aesDecryptSecret)(secretKeyAndValue); /** * check localSecrets to see if it has a secret key * value from * what's on onboardbase, remove the secret to avoid duplicate */ let updatedLocalSecret = {}; const recommendationSecrets = []; if (Object.keys(localSecrets).length) { Object.keys(localSecrets).map((localSecretkey) => { const key = localSecretkey; const value = localSecrets[key]; if (aesSecret[key] && aesSecret[key] === value) { console.log(`Duplicate secret found: ${chalk.greenBright.bold(`${key}=${value}`)}`, chalk.greenBright("...removing the secret from your local file")); } else updatedLocalSecret[key] = value; if (!aesSecret[key]) recommendationSecrets.push(key); }); if (verbose && recommendationSecrets.length) { let formattedMessage = "We noticed you have some new secret which have not been added to Onboardbase and suggest you create a merge requests to avoid having sensitive secrets locally. The secret keynames are:\n\n"; recommendationSecrets.map((secretKey) => (formattedMessage += `-${secretKey}\n`)); console.log(formattedMessage); console.log(`Run ${chalk.greenBright("onboardbase mr:create --from-local-secrets")} to upload your local secrets to onboardbase.`); } config_1.default.updateLocalProjectSecrets(updatedLocalSecret); } finalEnvs = Object.assign(finalEnvs, Object.assign(Object.assign({}, aesSecret), localSecrets)); finalBackupSecrets = aesSecret; } // Create fallback secrets // socketClient.emitEvent("SYNC::LOGS", { // data: "Creating a 24hrs Fallback Secrets file", // }); await (0, exports.createFallbackSecret)(project, JSON.stringify(finalBackupSecrets), environment, passphrase); return { env: finalEnvs, user }; } } catch (error) { /** * if CLI failed to fetch secrets from onboardbase, do not create * a project log for that session */ config_1.default.shouldCreateProjectLog = false; (0, exports.throwSecretsNotAccessibleError)(project, environment); console.log("Reverting to fallback...", `${chalk.greenBright.bold(project)}/${chalk.greenBright.bold(environment)}`); // cache network error here and fallback to cache } }; exports.downloadSecrets = downloadSecrets; const downloadSecretsForRecommendation = async (project, environment, accessToken) => { var _a, _b, _c, _d, _e, _f; try { const localSecrets = (_a = (0, exports.getLocalSecrets)()) !== null && _a !== void 0 ? _a : {}; let secrets = await (0, http_1.fetchSecrets)(project, environment, accessToken); const userCanFetchSecretUnderEnvironment = Boolean((_e = (_d = (_c = (_b = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _b === void 0 ? void 0 : _b.generalSecrets) === null || _c === void 0 ? void 0 : _c.list) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.member); if (!userCanFetchSecretUnderEnvironment) { console.log(`Error: ${chalk.red(`You don't have enough permission to fetch secrets under the ${chalk.bold.greenBright(environment)} environment.`)}`); process.exit(1); } if (secrets.errors) { console.error("Error fetching secrets"); process.exit(1); } else { secrets = (_f = secrets === null || secrets === void 0 ? void 0 : secrets.data) === null || _f === void 0 ? void 0 : _f.generalSecrets.list; if (secrets && Array.isArray(secrets)) { const secretKeyAndValue = secrets.map(({ key, value }) => { return { key, value }; }); const aesSecret = await (0, exports.aesDecryptSecret)(secretKeyAndValue); const recommendationSecrets = []; if (Object.keys(localSecrets).length) { Object.keys(localSecrets).map((localSecretkey) => { const key = localSecretkey; const value = localSecrets[key]; if (!aesSecret[key]) recommendationSecrets.push(key); }); return recommendationSecrets; } } } } catch (error) { console.error("Error fetching secrets", error === null || error === void 0 ? void 0 : error.message); process.exit(1); } }; exports.downloadSecretsForRecommendation = downloadSecretsForRecommendation; const addPrefixToEnvs = (prefix, envs) => { let modifiedEnvs = {}; Object.keys(envs).map((key) => { const envKey = `${prefix}_${key}`; const envValue = envs[key]; modifiedEnvs[envKey] = envValue; }); return modifiedEnvs; }; exports.addPrefixToEnvs = addPrefixToEnvs; const getHomeDirectory = () => { return (0, os_1.homedir)(); }; exports.getHomeDirectory = getHomeDirectory; const getCurrentWorkingDirectory = () => { return process.cwd(); }; exports.getCurrentWorkingDirectory = getCurrentWorkingDirectory; const parseLocalSecrets = (secrets) => { let secretStore = {}; if (Array.isArray(secrets)) { secrets.map((secret) => { secretStore[Object.keys(secret)[0]] = Object.values(secret)[0]; }); } if (!Array.isArray(secrets)) { Object.keys(secrets).map((secret) => (secretStore[secret] = secrets[secret])); } return secretStore; }; exports.parseLocalSecrets = parseLocalSecrets; const getLocalSecrets = () => { var _a, _b, _c; const workingDirectory = (0, exports.getCurrentWorkingDirectory)(); const localEnvironmentFile = config_1.default.projectConfigFile; const pathToSecret = localEnvironmentFile; if ((0, exports.isExist)(pathToSecret)) { try { const parsedSecrets = (_c = (_b = (_a = YAML.parse((0, fs_1.readFileSync)(pathToSecret, "utf8"))) === null || _a === void 0 ? void 0 : _a.secrets) === null || _b === void 0 ? void 0 : _b.local) !== null && _c !== void 0 ? _c : {}; return (0, exports.parseLocalSecrets)(parsedSecrets); } catch (error) { console.log(chalk.red.bold(`${localEnvironmentFile} file is not valid`)); process.exit(1); } } return; }; exports.getLocalSecrets = getLocalSecrets; const updateLocalSecrets = (localSecrets) => { var _a, _b; const workingDirectory = (0, exports.getCurrentWorkingDirectory)(); const localEnvironmentFile = config_1.default.projectConfigFile; const pathToSecret = (0, path_1.join)(workingDirectory, localEnvironmentFile); const data = Object.assign(Object.assign({}, ((0, exports.isExist)(pathToSecret) ? YAML.parse((0, fs_1.readFileSync)(pathToSecret, "utf8")) : {})), { secrets: { local: Object.assign({}, ((0, exports.isExist)(pathToSecret) ? (_b = (_a = YAML.parse((0, fs_1.readFileSync)(pathToSecret, "utf8"))) === null || _a === void 0 ? void 0 : _a.secrets) === null || _b === void 0 ? void 0 : _b.local : {})), } }); Object.keys(localSecrets).map((key) => { data.secrets.local[key] = localSecrets[key]; }); (0, fs_1.writeFileSync)(pathToSecret, YAML.stringify(data)); }; exports.updateLocalSecrets = updateLocalSecrets; const getMachineID = async () => { return await (0, node_machine_id_1.machineId)(); }; exports.getMachineID = getMachineID; const getFallbackDirectory = () => { return (0, path_1.join)((0, exports.getHomeDirectory)(), ".onboardbase", "fallback"); }; exports.getFallbackDirectory = getFallbackDirectory; const encryptSecrets = async (secrets, passphrase) => { const encryptionPassphrase = await (0, configuration_1.getEncryptionPassphrase)(); const bytes = CryptoJS.AES.encrypt(secrets, passphrase || encryptionPassphrase); return bytes.toString(); }; exports.encryptSecrets = encryptSecrets; const decryptPlainSecretAndReturnPlainValue = async (secret, passphrase) => { const encryptionPassphrase = passphrase !== null && passphrase !== void 0 ? passphrase : (await (0, configuration_1.getEncryptionPassphrase)()); let decryptedSecret = CryptoJS.AES.decrypt(secret, encryptionPassphrase).toString(CryptoJS.enc.Utf8); return decryptedSecret; }; exports.decryptPlainSecretAndReturnPlainValue = decryptPlainSecretAndReturnPlainValue; const decryptGenericSecret = async (genericSecrets, passphrase) => { const encryptionPassphrase = passphrase !== null && passphrase !== void 0 ? passphrase : (await (0, configuration_1.getEncryptionPassphrase)()); let decryptedSecrets = CryptoJS.AES.decrypt(genericSecrets, encryptionPassphrase).toString(CryptoJS.enc.Utf8); return JSON.parse(decryptedSecrets); }; exports.decryptGenericSecret = decryptGenericSecret; const decryptRawSecretsAndReturnAllProperties = async (secrets, passphrase) => { let secretMap = {}; try { secrets.map((secret) => { const secretKey = CryptoJS.AES.decrypt(secret.key, passphrase).toString(CryptoJS.enc.Utf8); const secretValue = CryptoJS.AES.decrypt(secret.value, passphrase).toString(CryptoJS.enc.Utf8); secretMap[secretKey.toUpperCase()] = { value: secretValue, id: secret.id, }; }); return secretMap; } catch (error) { // Silent this error and just delete the config files from the user's device console.error("Invalid passprase.. pls check your passphrase and try again"); process.exit(1); } }; exports.decryptRawSecretsAndReturnAllProperties = decryptRawSecretsAndReturnAllProperties; const decryptSecrets = async (secrets, passphrase) => { const encryptionPassphrase = await (0, configuration_1.getEncryptionPassphrase)(); let secretMap = {}; try { secrets.map(({ key, value }) => { const secretKey = CryptoJS.AES.decrypt(key, passphrase).toString(CryptoJS.enc.Utf8); const secretValue = CryptoJS.AES.decrypt(value, passphrase).toString(CryptoJS.enc.Utf8); secretMap[secretKey] = secretValue; }); return secretMap; } catch (error) { // Silent this error and just delete the config files from the user's device console.error("Invalid passprase.. pls check your passphrase and try again"); process.exit(1); } }; exports.decryptSecrets = decryptSecrets; const createFallbackSecret = async (project, secrets, environment = "development", passphrase) => { const projectDirectory = (0, path_1.join)((0, exports.getFallbackDirectory)(), project); const encryptedSecrets = await (0, exports.encryptSecrets)(secrets, passphrase); (0, fs_1.writeFileSync)(`${projectDirectory}_${environment}`, encryptedSecrets); }; exports.createFallbackSecret = createFallbackSecret; const getFallbackSecret = async (project, environment = "development", passphrase) => { const fallbackPath = (0, path_1.join)((0, exports.getFallbackDirectory)(), project); if (!(0, exports.isExist)(`${fallbackPath.concat(`_${environment}`)}`)) { console.log("Could not access project and no fallback exist for %s with the environment %s", chalk.green.bold(project), chalk.green.bold(environment)); throw new Error("Error while trying to fetch secrets from fallback directory"); } const fallbackFile = (0, fs_1.readFileSync)(`${fallbackPath}_${environment}`, { encoding: "utf8", }); const decryptedSecrets = (await (0, exports.decryptGenericSecret)(fallbackFile, passphrase)); return decryptedSecrets; }; exports.getFallbackSecret = getFallbackSecret; const deleteRecursiveFiles = async (file) => { const fallbackDirectory = (0, path_1.join)((0, os_1.homedir)(), ".onboardbase", "fallback"); const directoryFiles = (0, fs_1.readdirSync)(fallbackDirectory); const pattern = file; directoryFiles.map((fileName) => { if (pattern.test(fileName)) (0, fs_1.unlinkSync)((0, path_1.join)(fallbackDirectory, fileName)); }); }; exports.deleteRecursiveFiles = deleteRecursiveFiles; const deleteFallbackSecret = async (project) => { const regex = RegExp(`^${project}+`); await (0, exports.deleteRecursiveFiles)(regex); }; exports.deleteFallbackSecret = deleteFallbackSecret; const handleSocketConnection = (project, environment, userEmail) => { var _a, _b, _c, _d; const allConfigs = config_1.default.getConfigs(); const socket = io.connect((_d = (_b = (_a = allConfigs[process.cwd()]) === null || _a === void 0 ? void 0 : _a["api-host"]) !== null && _b !== void 0 ? _b : (_c = allConfigs["/"]) === null || _c === void 0 ? void 0 : _c["api-host"]) !== null && _d !== void 0 ? _d : "http://localhost:3000", { transports: ["websocket"], query: `project=${project}_${environment}&email=${userEmail}`, }); return socket; }; exports.handleSocketConnection = handleSocketConnection; const recheckUserAccess = async (project, environment) => { const ONE_DAY_IN_MS = 86400000; const accessInterval = setInterval(async () => { const isOnline = await (0, http_1.isUserOnline)(); if (isOnline) { // try to fetch secrets again to see if the user still have access to the project try { const { accessToken } = await (0, http_1.generateAccessToken)(await config_1.default.getToken()); await (0, http_1.fetchSecrets)(project, environment, accessToken); } catch (error) { await (0, exports.deleteFallbackSecret)(project); config_1.default.closeRunningProcess(); clearInterval(accessInterval); } } if (!isOnline) { // Force the user to restart their server and delete the fallback secrets console.log("Server due for a restart..."); await (0, exports.deleteFallbackSecret)(project); config_1.default.closeRunningProcess(); clearInterval(accessInterval); } }, ONE_DAY_IN_MS); }; exports.recheckUserAccess = recheckUserAccess; const checkProjectCacheStatus = (project, environment) => { return new Promise((resolve) => { const ONE_DAY_IN_MS = 86400000; const fallbackFile = (0, path_1.join)((0, os_1.homedir)(), ".onboardbase", "fallback", `${project}_${environment}`); if ((0, exports.isExist)(fallbackFile)) { const fileStats = (0, fs_1.statSync)(fallbackFile); const now = new Date().getTime(); const fileDuration = new Date(fileStats.ctime).getTime() + ONE_DAY_IN_MS; if (now > fileDuration) { (0, fs_1.unlinkSync)(fallbackFile); resolve(true); } else resolve(false); } resolve(true); }); }; exports.checkProjectCacheStatus = checkProjectCacheStatus; const generateRsaKeys = () => { const key = new NodeRSA({ b: 512 }); const keys = key.generateKeyPair(); const publicKey = keys.exportKey("public"); const privateKey = keys.exportKey("private"); return { publicKey, privateKey }; }; exports.generateRsaKeys = generateRsaKeys; const rsaDecryptSecret = (data, privateKey) => { try { const rsaPrivateKey = new NodeRSA(privateKey); return rsaPrivateKey.decrypt(data, "utf8"); } catch (e) { return ""; } }; exports.rsaDecryptSecret = rsaDecryptSecret; const getFrontendEncryptionKey = () => { return "e8%!w%yM!eAewzdsegg8%!walmart%yelpMUSIC!eggAPPLEeggwalmartzip_H@BQF3J^2Bjqum3d2.4L@y/FCK3=~pTf~?s(}r8H</4gFnXn@8`wL^gWTVP26tD"; }; exports.getFrontendEncryptionKey = getFrontendEncryptionKey; const aesDecryptSecret = (secrets) => { var _a; //const passphrase = getFrontendEncryptionKey(); const passphrase = (_a = config_1.default.getEncryptionSecretKey()) !== null && _a !== void 0 ? _a : (0, exports.getFrontendEncryptionKey)(); return (0, exports.decryptSecrets)(secrets, passphrase); }; exports.aesDecryptSecret = aesDecryptSecret; const rsaEncryptSecret = (data, publicKey) => { try { const rsaPublicKey = new NodeRSA(publicKey); return rsaPublicKey.encrypt(data, "base64"); } catch (e) { return ""; } }; exports.rsaEncryptSecret = rsaEncryptSecret; const encryptWithAESAndRSA = async (rsaPublicKey, secrets) => { const aesEncryptionKey = (0, exports.getFrontendEncryptionKey)(); const encryptedAES = await (0, exports.encryptSecrets)(secrets, aesEncryptionKey); const encryptedRSA = (0, exports.rsaEncryptSecret)(encryptedAES, rsaPublicKey); return encryptedRSA; }; exports.encryptWithAESAndRSA = encryptWithAESAndRSA; const formatDate = (date, sec) => { let day = "", month = "", convMonth = "", year = "", time = ""; const months = [ "Jan", "Feb", "Mar", "April", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec", ]; const getMonth = (val) => months[Number(val) - 1]; let split; if (date) { split = date.split("-"); day = `${split[2][0]}${split[2][1]}`; month = split[1]; year = split[0]; time = split[2].slice(3, sec ? 11 : 8); convMonth = getMonth(month); return `${convMonth} ${day}, ${year} | ${time} GMT`; } }; exports.formatDate = formatDate; const removeDuplicateSecrete = (data) => { const comparisonMap = {}; const cleanArr = []; data.map((currentSecret) => { const { key = null, value = null } = comparisonMap[currentSecret.key] || {}; if (!key || (key && value !== currentSecret.value)) { comparisonMap[currentSecret.key] = currentSecret; } }); Object.keys(comparisonMap).map((key) => cleanArr.push(comparisonMap[key])); return cleanArr; }; exports.removeDuplicateSecrete = removeDuplicateSecrete; const duplicateExistingSecretAndUpdateEnvironment = async (project, environmentToDuplicateFrom, environmentToUpdate, cliToken) => { const { env } = await (0, exports.fetchRawSecrets)(project, environmentToDuplicateFrom, cliToken); let parsedJSON = env; cli_ux_1.cli.action.start("Uploading secrets"); await (0, exports.uploadSecretsToOnboardbase)(project, environmentToUpdate, parsedJSON, cliToken, [], uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DUPLICATE); cli_ux_1.cli.action.stop(); }; exports.duplicateExistingSecretAndUpdateEnvironment = duplicateExistingSecretAndUpdateEnvironment; const uploadSecretsToOnboardbase = async (currentProject, currentEnvironment, parsedJSON, deviceToken, excludeFromExistingSecrets, action, accessToken) => { let env; let environmentId; if (action !== uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DUPLICATE) { const secretProperties = await (0, exports.fetchRawSecretsIncludingSecretProperties)(currentProject, currentEnvironment, deviceToken); env = secretProperties.env; accessToken = secretProperties.accessToken; environmentId = secretProperties.environmentId; } /** * if action is duplicate, environment */ if (action === uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DUPLICATE) { if (!accessToken) { const { accessToken: duplicateQueryAccessToken, user } = await (0, http_1.generateAccessToken)(deviceToken); accessToken = duplicateQueryAccessToken; } environmentId = await (0, exports.getEnvironmentId)(currentProject, currentEnvironment, accessToken); } const encryptionPassphrase = config_1.default.getEncryptionSecretKey(); let secretUpdateStore = []; if (action && action === uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DELETE) { const secretsToDelete = []; excludeFromExistingSecrets.map((secretKey) => { if (env[secretKey]) secretsToDelete.push(env[secretKey]); else console.log(chalk.greenBright(`The Secret: ${secretKey} doesn't exist on onboardbase...`)); }); await Promise.all(secretsToDelete.map(async (singleSecret) => { await (0, http_1.deleteSecret)(accessToken, singleSecret.id); })); return; } if (action === uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.UPDATE) { await Promise.all(Object.keys(parsedJSON).map(async (secretKey) => { if (!env[secretKey]) { console.log(chalk.greenBright(`${secretKey} doesnt exist on onboardbase.. you're trying to update a secret that doesnt exist on onboardbase`)); return; } const encryptedSecretKey = await (0, exports.encryptSecrets)(secretKey, encryptionPassphrase); const encryptedSecretValue = await (0, exports.encryptSecrets)(parsedJSON[secretKey], encryptionPassphrase); secretUpdateStore.push({ key: encryptedSecretKey, value: encryptedSecretValue, // @ts-ignore id: env[secretKey].id, }); })); await Promise.all(secretUpdateStore.map(async (secret) => { await (0, http_1.updateSecret)(accessToken, secret.id, { key: secret.key, value: secret.value, }); })); } if (action === uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.CREATE || action === uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DUPLICATE) { await Promise.all(Object.keys(parsedJSON).map(async (secretKey) => { /** * check for duplicate and skip */ if (action !== uploadSecretsActionTypes_enum_1.UploadSecretsActionTypes.DUPLICATE) { if (env[secretKey]) { console.log(chalk.greenBright(`Duplicate Secret key found: ${secretKey}.. you can use the ${chalk.bold("onboardbase secrets:update")} command to update the secret value`)); return; } } const encryptedSecretKey = await (0, exports.encryptSecrets)(secretKey.toUpperCase(), encryptionPassphrase); const encryptedSecretValue = await (0, exports.encryptSecrets)(parsedJSON[secretKey], encryptionPassphrase); secretUpdateStore.push({ key: encryptedSecretKey, value: encryptedSecretValue, comment: await (0, exports.encryptSecrets)("", encryptionPassphrase), environmentId, }); })); if (secretUpdateStore && secretUpdateStore.length) await (0, http_1.addSecrets)(accessToken, secretUpdateStore); } }; exports.uploadSecretsToOnboardbase = uploadSecretsToOnboardbase; const parseObjectToEnv = (env) => { let parsedData = ""; Object.keys(env).map((secretKey) => { const data = `${secretKey}=${env[secretKey]}\n`; parsedData += data; }); return parsedData; }; exports.parseObjectToEnv = parseObjectToEnv; const parseEnvContentToObject = (envFileContent) => { const parsedJSON = {}; let certKeys = []; let currentCertKey = { name: "", value: "", exists: false }; const modifiedData = envFileContent.split("\n").map((keyAndValue) => { if (keyAndValue.includes("-----BEGIN")) { currentCertKey.exists = true; const nameBegin = keyAndValue.split("="); currentCertKey.name = nameBegin[0]; currentCertKey.value = nameBegin[1]; return ""; } else if (keyAndValue.includes("-----END")) { certKeys.push({ name: currentCertKey.name, value: `${currentCertKey.value}\n${keyAndValue}`, }); currentCertKey = { name: "", value: "", exists: false }; return ""; } else if (currentCertKey.exists) { currentCertKey.value = `${currentCertKey.value}\n${keyAndValue}`; return ""; } return keyAndValue; }); const restructuredData = modifiedData .join("\n") .trim() .split(/\r?\n/) .filter((x) => x.trim() !== ""); restructuredData.map((data) => { const keyAndValue = data.split("="); if (!keyAndValue[0].trim().startsWith("#") && keyAndValue[1] !== undefined) { parsedJSON[keyAndValue[0]] = keyAndValue[1]; } }); certKeys = certKeys.filter((x) => { var _a, _b; return ((_a = x.value) === null || _a === void 0 ? void 0 : _a.trim()) !== "" && ((_b = x.key) === null || _b === void 0 ? void 0 : _b.trim()) !== ""; }); certKeys.map((data) => { if (!data.name.trim().startsWith("#") && data.value !== undefined) { parsedJSON[data.name] = data.value; } }); return parsedJSON; }; exports.parseEnvContentToObject = parseEnvContentToObject; const downloadTarballs = async (url, filePath) => { try { const { data } = await (0, axios_1.default)({ method: "get", url, responseType: "stream", }); const readable = data.pipe((0, fs_1.createWriteStream)(filePath, { encoding: "utf8" })); // Check if stream has ended before returning a response return new Promise((resolve, reject) => { readable.on("finish", async () => { resolve(filePath); }); }); } catch (error) { console.log(error); } }; exports.downloadTarballs = downloadTarballs; const getLatestCliVersion = async () => { try { const registryUrl = "https://registry.npmjs.org/@onboardbase/cli"; const { data } = await axios_1.default.get(registryUrl); return data["dist-tags"]; } catch (error) { if (error.response.data) { console.error(error.response.data); } else console.error("Could not check for updates... Please check your Network connection and try again"); } }; exports.getLatestCliVersion = getLatestCliVersion; /** * Keep a record of the current project information, the current path, the team that * owns the project, the user info & the last time they ran the `onboardbase build` command */ const createProjectLogTable = (user, team, project) => { /** * @todo * * Encrypt log files before saving to the DB * rename onboardbase.json to onboardbase.db */ const logTableDirectory = (0, path_1.join)(config_1.default.onboardbaseDirectory, "db"); if (!(0, fs_1.existsSync)(logTableDirectory)) (0, fs_1.mkdirSync)(logTableDirectory); const logTableFile = (0, path_1.join)(logTableDirectory, "onboardbase.json"); const logFiles = (0, fs_1.existsSync)(logTableFile) ? JSON.parse((0, fs_1.readFileSync)(logTableFile, "utf8")) : []; /** * Check our LOG DB to see if we have a record for the current * teamId, projectId && user's email */ const logWithTeamAndProjectId = logFiles.find((log) => { var _a, _b; return ((_a = log === null || log === void 0 ? void 0 : log.team) === null || _a === void 0 ? void 0 : _a.id) === team.id && ((_b = log === null || log === void 0 ? void 0 : log.project) === null || _b === void 0 ? void 0 : _b.id) === project.id && log.user.email === user.email; }); const otherLogs = logFiles.filter((log) => { var _a, _b; return ((_a = log === null || log === void 0 ? void 0 : log.team) === null || _a === void 0 ? void 0 : _a.id) !== team.id && ((_b = log === null || log === void 0 ? void 0 : log.project) === null || _b === void 0 ? void 0 : _b.id) !== project.id && log.user.email !== user.email; }); let finalData; if (logWithTeamAndProjectId) { finalData = [ ...otherLogs, Object.assign(Object.assign({}, logWithTeamAndProjectId), { updatedAt: Date.now(), projectPath: process.cwd() }), ]; } else { finalData = [ ...otherLogs, { team, user, project, projectPath: process.cwd(), createdAt: Date.now(), updatedAt: Date.now(), }, ]; } (0, fs_1.writeFileSync)(logTableFile, JSON.stringify(finalData), "utf8"); }; exports.createProjectLogTable = createProjectLogTable; const getPathToServiceFiles = () => { return process.platform === "darwin" ? (0, path_1.join)((0, os_1.homedir)(), "Library", "LaunchAgents", "onboardbase.application.plist") : (0, path_1.join)("/etc", "systemd", "system", "onboardbase.service"); }; exports.getPathToServiceFiles = getPathToServiceFiles; const isOnboardbaseServiceInstalled = () => { return (0, exports.isExist)((0, exports.getPathToServiceFiles)()); }; exports.isOnboardbaseServiceInstalled = isOnboardbaseServiceInstalled; const isSudoUser = () => { return process.getuid() === 0 || process.env.SUDO_UID ? true : false; }; exports.isSudoUser = isSudoUser; const isUnix = () => { return ["darwin", "linux"].includes(process.platform) && !(0, isDocker_1.isDocker)(); }; exports.isUnix = isUnix; const makeRandomId = (length) => { let result = ""; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result.toLowerCase(); }; exports.makeRandomId = makeRandomId; const isDirectoryWritable = (path) => { return new Promise((resolve) => { (0, fs_1.access)(path, fs_1.constants.W_OK, (err) => { if (err) resolve(false); resolve(true); }); }); }; exports.isDirectoryWritable = isDirectoryWritable; const getShellRc = () => { const shell = process.env.SHELL; const split = shell.split("/"); const fileName = `.${split[split.length - 1]}rc`; return (0, path_1.join)(process.env.HOME, fileName); }; exports.getShellRc = getShellRc; const isValidURL = (url) => { let isValid = true; try { new URL(url); } catch (err) { isValid = false; } return isValid; }; exports.isValidURL = isValidURL; const isAPartialUrl = (sourcePath) => { const isAPartialUrl = (0, exports.isValidURL)(sourcePath) || sourcePath.endsWith("json") || sourcePath.endsWith("env"); return isAPartialUrl; }; exports.isAPartialUrl = isAPartialUrl; const isJSON = (data) => { let isJson = false; try { JSON.parse(data); isJson = true; } catch (error) { isJson = false; } return isJson; }; exports.isJSON = isJSON;