namastejs
Version:
A spiritual greeting from your JavaScript code. Because every function deserves a 'Namaste ๐'
239 lines (202 loc) โข 7.24 kB
JavaScript
const readline = require("readline");
const crypto = require("crypto");
const { encrypt, decrypt, hashPassword } = require("./vaultCrypto");
const { readVault, writeVault } = require("./vaultStorage");
let sessionKey = null;
let sessionExpiry = 0;
const SESSION_DURATION = 60 * 1000; // 60 sec
function prompt(question, hide = false) {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: hide ? undefined : process.stdout,
terminal: true,
});
if (hide) {
process.stdout.write(question);
process.stdin.setRawMode(true);
let input = "";
process.stdin.on("data", (char) => {
char = char.toString();
if (char === "\n" || char === "\r" || char === "\u0004") {
process.stdin.setRawMode(false);
process.stdout.write("\n");
rl.close();
resolve(input);
} else if (char === "\u0003") {
process.exit();
} else {
input += char;
}
});
} else {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
}
});
}
// Get or set master password
async function getMasterKey(vault) {
const now = Date.now();
if (sessionKey && now < sessionExpiry) return sessionKey;
// If no master password yet, set it
if (!vault.__master) {
console.log("๐ No master password set. Please create one.");
const password = await prompt("Enter new master password: ", true);
const confirm = await prompt("Confirm master password: ", true);
if (password !== confirm) {
console.log("โ Passwords do not match. Aborting.");
process.exit(1);
}
vault.__master = hashPassword(password);
writeVault(vault);
const salt = crypto.createHash("sha256").update("namastejs-vault").digest();
sessionKey = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256");
sessionExpiry = now + SESSION_DURATION;
console.log("โ
Master password set!");
return sessionKey;
}
// Ask for master password
const password = await prompt("๐ Enter master password: ", true);
if (hashPassword(password) !== vault.__master) {
console.log("โ Wrong master password.");
process.exit(1);
}
// Derive session key
const salt = crypto.createHash("sha256").update("namastejs-vault").digest();
sessionKey = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256");
sessionExpiry = now + SESSION_DURATION;
return sessionKey;
}
async function vaultCLI(command, keyArg) {
const vault = readVault();
switch (command) {
case "add": {
const key = await prompt("๐ Service name (e.g., github): ");
const username = await prompt("๐ค Username: ");
const password = await prompt(
"๐ Password (leave blank to auto-generate): "
);
const pwd =
password ||
Math.random().toString(36).slice(-12) +
Date.now().toString(36).slice(-4);
const masterKey = await getMasterKey(vault);
if (vault[key]) {
const confirm = await prompt(
`โ ๏ธ '${key}' exists. Overwrite? (yes/no): `
);
if (confirm.toLowerCase() !== "yes") {
console.log("โ Cancelled.");
return;
}
}
vault[key] = {
username,
password: encrypt(pwd, masterKey),
};
writeVault(vault);
console.log(`โ
Saved: ${key}`);
break;
}
case "get": {
const key = keyArg || (await prompt("๐ Service name: "));
if (!vault[key]) return console.log("โ Not found.");
const masterKey = await getMasterKey(vault);
const { username, password } = vault[key];
console.log(`\n๐ค Username: ${username}`);
console.log(`๐ Password: ${decrypt(password, masterKey)}\n`);
break;
}
case "list": {
const keys = Object.keys(vault).filter((k) => k !== "__master");
if (!keys.length) return console.log("๐ญ Vault is empty.");
console.log("๐ Stored services:\n");
keys.forEach((key) => console.log("โข", key));
console.log();
break;
}
case "delete": {
const key = keyArg || (await prompt("๐ Service to delete: "));
if (!vault[key]) return console.log("โ Not found.");
await getMasterKey(vault);
const confirm = await prompt(
`โ ๏ธ Are you sure you want to delete '${key}'? (yes/no): `
);
if (confirm.toLowerCase() === "yes") {
delete vault[key];
writeVault(vault);
console.log(`โ
'${key}' deleted.`);
} else {
console.log("โ Cancelled.");
}
break;
}
case "clear": {
const confirm1 = await prompt(
"โ ๏ธ This will erase everything. Type 'confirm': "
);
const confirm2 = await prompt("โ ๏ธ Are you really sure? Type 'yes': ");
if (confirm1 === "confirm" && confirm2 === "yes") {
await getMasterKey(vault); // Ensure password check
writeVault({ __master: vault.__master });
console.log("๐งจ Vault cleared.");
} else {
console.log("โ Cancelled.");
}
break;
}
case "reset-master": {
const masterKeyOld = await getMasterKey(vault); // validate current password
console.log("๐ Set your new master password ๐");
const newPassword = await prompt("Enter new master password: ", true);
const confirm = await prompt("Confirm new master password: ", true);
if (newPassword !== confirm) {
console.log("โ Passwords do not match. Aborting.");
return;
}
// Derive new session key
const salt = crypto
.createHash("sha256")
.update("namastejs-vault")
.digest();
const masterKeyNew = crypto.pbkdf2Sync(
newPassword,
salt,
100000,
32,
"sha256"
);
// Re-encrypt all entries
for (const key of Object.keys(vault)) {
if (key === "__master") continue;
const entry = vault[key];
const plain = decrypt(entry.password, masterKeyOld);
entry.password = encrypt(plain, masterKeyNew);
}
// Update master password hash
vault.__master = hashPassword(newPassword);
writeVault(vault);
// Update session
sessionKey = masterKeyNew;
sessionExpiry = Date.now() + SESSION_DURATION;
console.log("โ
Master password has been reset successfully!");
break;
}
default:
console.log("๐ Commands:");
console.log("add โ Add new credential");
console.log("get KEY โ Show credential for a service");
console.log("list โ List all saved services");
console.log("delete KEY โ Remove a service");
console.log("clear โ Wipe entire vault\n");
break;
}
}
process.on("SIGINT", () => {
sessionKey = null;
process.exit();
});
module.exports = { vaultCLI };