const pathModule = require("path"); const fs = require("fs"); const readline = require("readline"); const { generateRandomSalt, generateRandomString } = require("../lib/cryptoEngine.js"); const { renderTemplate } = require("../lib/formater.js"); const Yargs = require("yargs"); const PASSWORD_TEMPLATE_DEFAULT_PATH = pathModule.join(__dirname, "..", "lib", "password_template.html"); const OUTPUT_DIRECTORY_DEFAULT_PATH = "encrypted"; exports.OUTPUT_DIRECTORY_DEFAULT_PATH = OUTPUT_DIRECTORY_DEFAULT_PATH; /** * @param {string} message */ function exitWithError(message) { console.log("ERROR: " + message); process.exit(1); } exports.exitWithError = exitWithError; /** * Check if a particular option has been set by the user. Useful for distinguishing default value with flag without * parameter. * * Ex use case: '-s' means "give me a salt", '-s 1234' means "use 1234 as salt" * * From https://github.com/yargs/yargs/issues/513#issuecomment-221412008 * * @param {string} option * @param yargs * @returns {boolean} */ function isOptionSetByUser(option, yargs) { function searchForOption(option) { return process.argv.indexOf(option) > -1; } if (searchForOption(`-${option}`) || searchForOption(`--${option}`)) { return true; } // Handle aliases for same option for (let aliasIndex in yargs.parsed.aliases[option]) { const alias = yargs.parsed.aliases[option][aliasIndex]; if (searchForOption(`-${alias}`) || searchForOption(`--${alias}`)) { return true; } } return false; } exports.isOptionSetByUser = isOptionSetByUser; /** * Prompts the user for input on the CLI. * * @param {string} question * @returns {Promise} */ function prompt(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { return rl.question(question, (answer) => { rl.close(); return resolve(answer); }); }); } /** * @param {string} password * @param {boolean} isShortAllowed * @returns {Promise} */ async function validatePassword(password, isShortAllowed) { if (password.length < 14 && !isShortAllowed) { const shouldUseShort = await prompt( `WARNING: Your password is less than 14 characters (length: ${password.length})` + " and it's easy to try brute-forcing on public files, so we recommend using a longer one. Here's a generated one: " + generateRandomString(21) + "\nYou can hide this warning by increasing your password length or adding the '--short' flag." + "\nDo you want to still want to use the shorter password? [y/N] " ); if (!shouldUseShort.match(/^\s*(y|yes)\s*$/i)) { console.log("Aborting."); process.exit(0); } } } exports.validatePassword = validatePassword; /** * Get the config from the config file. * * @param {string|null} configPath * @returns {{}|object} */ function getConfig(configPath) { if (configPath && fs.existsSync(configPath)) { return JSON.parse(fs.readFileSync(configPath, "utf8")); } return {}; } exports.getConfig = getConfig; function writeConfig(configPath, config) { if (configPath) { fs.writeFileSync(configPath, JSON.stringify(config, null, 4)); } } exports.writeConfig = writeConfig; /** * Get the password from the command arguments or environment variables. * * @param {string} passwordArgument - password from the command line * @returns {Promise} */ async function getPassword(passwordArgument) { // try to get the password from the environment variable const envPassword = process.env.STATICRYPT_PASSWORD; const hasEnvPassword = envPassword !== undefined && envPassword !== ""; if (hasEnvPassword) { return envPassword; } // try to get the password from the command line arguments if (passwordArgument !== null) { return passwordArgument; } // prompt the user for their password return prompt("Enter your long, unusual password: "); } exports.getPassword = getPassword; /** * @param {string} filepath * @returns {string} */ function getFileContent(filepath) { try { return fs.readFileSync(filepath, "utf8"); } catch (e) { exitWithError(`input file '${filepath}' does not exist!`); } } exports.getFileContent = getFileContent; /** * @param {object} namedArgs * @param {object} config * @returns {string} */ function getValidatedSalt(namedArgs, config) { const salt = getSalt(namedArgs, config); // validate the salt if (salt.length !== 32 || /[^a-f0-9]/.test(salt)) { exitWithError( "the salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)" + "\nDetected salt: " + salt ); } return salt; } exports.getValidatedSalt = getValidatedSalt; /** * @param {object} namedArgs * @param {object} config * @returns {string} */ function getSalt(namedArgs, config) { // either a salt was provided by the user through the flag --salt if (!!namedArgs.salt) { return String(namedArgs.salt).toLowerCase(); } // or try to read the salt from config file if (config.salt) { return config.salt; } return generateRandomSalt(); } /** * A dead-simple alternative to webpack or rollup for inlining simple * CommonJS modules in a browser