rename CLI args, save output to new dir (closes #166)

v3
robinmoisson 2023-03-29 17:15:06 +02:00
rodzic 92691a1994
commit f8f11067ea
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 9419716500078583
2 zmienionych plików z 98 dodań i 71 usunięć

Wyświetl plik

@ -1,7 +1,8 @@
const path = require("path");
const fs = require("fs"); const fs = require("fs");
const readline = require('readline');
const { generateRandomSalt } = require("../lib/cryptoEngine.js"); const { generateRandomSalt } = require("../lib/cryptoEngine.js");
const path = require("path");
const {renderTemplate} = require("../lib/formater.js"); const {renderTemplate} = require("../lib/formater.js");
const Yargs = require("yargs"); const Yargs = require("yargs");
@ -51,24 +52,46 @@ function isOptionSetByUser(option, yargs) {
exports.isOptionSetByUser = isOptionSetByUser; exports.isOptionSetByUser = isOptionSetByUser;
/** /**
* Get the password from the command arguments * Prompts the user for input on the CLI.
* *
* @param {string[]} positionalArguments * @param {string} question
* @returns {string} * @returns {Promise<string>}
*/ */
function getPassword(positionalArguments) { function prompt (question) {
let password = process.env.STATICRYPT_PASSWORD; const rl = readline.createInterface({
const hasEnvPassword = password !== undefined && password !== ""; input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
return rl.question(question, (answer) => {
rl.close();
return resolve(answer);
});
});
}
/**
* Get the password from the command arguments or environment variables.
*
* @param {string} passwordArgument - password from the command line
* @returns {Promise<string>}
*/
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) { if (hasEnvPassword) {
return password; return envPassword;
} }
if (positionalArguments.length < 2) { // try to get the password from the command line arguments
exitWithError("missing password, please provide an argument or set the STATICRYPT_PASSWORD environment variable in the environment or .env file"); if (passwordArgument !== null) {
return passwordArgument;
} }
return positionalArguments[1].toString(); // prompt the user for their password
return prompt('Enter your long, unusual password: ');
} }
exports.getPassword = getPassword; exports.getPassword = getPassword;
@ -140,11 +163,12 @@ exports.convertCommonJSToBrowserJS = convertCommonJSToBrowserJS;
* @param {string} errorName * @param {string} errorName
* @returns {string} * @returns {string}
*/ */
function readFile(filePath, errorName = file) { function readFile(filePath, errorName = "file") {
try { try {
return fs.readFileSync(filePath, "utf8"); return fs.readFileSync(filePath, "utf8");
} catch (e) { } catch (e) {
exitWithError(`could not read ${errorName}!`); console.error(e);
exitWithError(`could not read ${errorName} at path "${filePath}"`);
} }
} }
@ -160,10 +184,17 @@ function genFile(data, outputFilePath, templateFilePath) {
const renderedTemplate = renderTemplate(templateContents, data); const renderedTemplate = renderTemplate(templateContents, data);
// create output directory if it does not exist
const dirname = path.dirname(outputFilePath);
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true });
}
try { try {
fs.writeFileSync(outputFilePath, renderedTemplate); fs.writeFileSync(outputFilePath, renderedTemplate);
} catch (e) { } catch (e) {
exitWithError("could not generate output file!"); console.error(e);
exitWithError("could not generate output file");
} }
} }
exports.genFile = genFile; exports.genFile = genFile;
@ -179,64 +210,32 @@ function isCustomPasswordTemplateDefault(templatePathParameter) {
exports.isCustomPasswordTemplateDefault = isCustomPasswordTemplateDefault; exports.isCustomPasswordTemplateDefault = isCustomPasswordTemplateDefault;
function parseCommandLineArguments() { function parseCommandLineArguments() {
return Yargs.usage("Usage: staticrypt <filename> [<password>] [options]") return Yargs.usage("Usage: staticrypt <filename> [options]")
.option("c", { .option("c", {
alias: "config", alias: "config",
type: "string", type: "string",
describe: 'Path to the config file. Set to "false" to disable.', describe: 'Path to the config file. Set to "false" to disable.',
default: ".staticrypt.json", default: ".staticrypt.json",
}) })
.option("decrypt-button", {
type: "string",
describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
default: "DECRYPT",
})
.option("f", {
alias: "file-template",
type: "string",
describe: "Path to custom HTML template with password prompt.",
default: PASSWORD_TEMPLATE_DEFAULT_PATH,
})
.option("i", {
alias: "instructions",
type: "string",
describe: "Special instructions to display to the user.",
default: "",
})
.option("label-error", {
type: "string",
describe: "Error message to display on entering wrong password.",
default: "Bad password!",
})
.option("noremember", {
type: "boolean",
describe: 'Set this flag to remove the "Remember me" checkbox.',
default: false,
})
.option("o", { .option("o", {
alias: "output", alias: "output",
type: "string", type: "string",
describe: "File name/path for the generated encrypted file.", describe: "Name of the directory where the encrypted files will be saved.",
default: "encrypted/",
})
.option("p", {
alias: "password",
type: "string",
describe: "The password to encrypt your file with.",
default: null, default: null,
}) })
.option("passphrase-placeholder", { .option("remember", {
type: "string",
describe: "Placeholder to use for the password input.",
default: "Password",
})
.option("r", {
alias: "remember",
type: "number", type: "number",
describe: describe:
'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) password ' + 'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) password ' +
'in localStorage when entered by the user. Default: "0", no expiration.', 'in localStorage when entered by the user. Set to "false" to hide the box. Default: "0", no expiration.',
default: 0, default: 0,
}) })
.option("remember-label", {
type: "string",
describe: 'Label to use for the "Remember me" checkbox.',
default: "Remember me",
})
// do not give a default option to this parameter - we want to see when the flag is included with no // do not give a default option to this parameter - we want to see when the flag is included with no
// value and when it's not included at all // value and when it's not included at all
.option("s", { .option("s", {
@ -261,7 +260,37 @@ function parseCommandLineArguments() {
default: false, default: false,
}) })
.option("t", { .option("t", {
alias: "title", alias: "template",
type: "string",
describe: "Path to custom HTML template with password prompt.",
default: PASSWORD_TEMPLATE_DEFAULT_PATH,
})
.option("template-button", {
type: "string",
describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
default: "DECRYPT",
})
.option("template-instructions", {
type: "string",
describe: "Special instructions to display to the user.",
default: "",
})
.option("template-error", {
type: "string",
describe: "Error message to display on entering wrong password.",
default: "Bad password!",
})
.option("template-placeholder", {
type: "string",
describe: "Placeholder to use for the password input.",
default: "Password",
})
.option("template-remember", {
type: "string",
describe: 'Label to use for the "Remember me" checkbox.',
default: "Remember me",
})
.option("template-title", {
type: "string", type: "string",
describe: "Title for the output HTML page.", describe: "Title for the output HTML page.",
default: "Protected Page", default: "Protected Page",

Wyświetl plik

@ -27,18 +27,18 @@ async function runStatiCrypt() {
// validate the number of arguments // validate the number of arguments
const positionalArguments = namedArgs._; const positionalArguments = namedArgs._;
if (positionalArguments.length > 2 || positionalArguments.length === 0) { if (positionalArguments.length === 0) {
yargs.showHelp(); yargs.showHelp();
process.exit(1); process.exit(1);
} }
// parse input // parse input
const inputFilepath = positionalArguments[0].toString(), const inputFilepath = positionalArguments[0].toString(),
password = getPassword(positionalArguments); password = await getPassword(namedArgs.password);
if (password.length < 16 && !namedArgs.short) { if (password.length < 16 && !namedArgs.short) {
console.log( console.log(
`WARNING: Your password is less than 16 characters (length: ${password.length}). Brute-force attacks are easy to ` `\nWARNING: Your password is less than 16 characters (length: ${password.length}). Brute-force attacks are easy to `
+ `try on public files, and you are most safe when using a long password.\n\n` + `try on public files, and you are most safe when using a long password.\n\n`
+ `👉️ Here's a strong generated password you could use: ` + `👉️ Here's a strong generated password you could use: `
+ generateRandomString(21) + generateRandomString(21)
@ -88,25 +88,23 @@ async function runStatiCrypt() {
const encryptedMessage = await encode(contents, password, salt); const encryptedMessage = await encode(contents, password, salt);
const data = { const data = {
decrypt_button: namedArgs.decryptButton, decrypt_button: namedArgs.templateButton,
encrypted: encryptedMessage, encrypted: encryptedMessage,
instructions: namedArgs.instructions, instructions: namedArgs.templateInstructions,
is_remember_enabled: namedArgs.noremember ? "false" : "true", is_remember_enabled: namedArgs.remember === "false" ? "false" : "true",
js_codec: convertCommonJSToBrowserJS("lib/codec"), js_codec: convertCommonJSToBrowserJS("lib/codec"),
js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine"), js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine"),
label_error: namedArgs.labelError, label_error: namedArgs.templateError,
passphrase_placeholder: namedArgs.passphrasePlaceholder, passphrase_placeholder: namedArgs.templatePlaceholder,
remember_duration_in_days: namedArgs.remember, remember_duration_in_days: namedArgs.remember,
remember_me: namedArgs.rememberLabel, remember_me: namedArgs.templateRemember,
salt: salt, salt: salt,
title: namedArgs.title, title: namedArgs.templateTitle,
}; };
const outputFilepath = namedArgs.output !== null const outputFilepath = namedArgs.output.replace(/\/+$/, '') + "/" + inputFilepath;
? namedArgs.output
: inputFilepath.replace(/\.html$/, "") + "_encrypted.html";
genFile(data, outputFilepath, namedArgs.f); genFile(data, outputFilepath, namedArgs.template);
} }
runStatiCrypt(); runStatiCrypt();