-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
138 lines (122 loc) · 4.12 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// modules
import { noDataRetrievalDialogue, retrievePasswords, getMasterPassword, mainMenu } from "./src/interactions.js";
import {
errorPrefix,
warnPrefix,
infoPrefix,
debugPrefix,
prompt,
fetchFile,
saveFile,
createFile,
importData,
decryptData,
helpMessage,
parseArgs,
initTimeout,
resetTimeout,
terminate
} from "./src/util.js";
/**
* @typedef {{ file: string?, charset: string, verbose: boolean, outputMethod: "CLIPBOARD" | "STDOUT", help: boolean, passwordVisibility: "HIDDEN" | "MASKED" | "CLEAR", quick: boolean, createFile: string?, timeout: number, import: string? }} args The command line args
* @typedef {{ data: data, version: number, checksum: string }} fileContents
*/
/**
* Runs the program
*/
async function main() {
// parse argv
let args;
try {
args = parseArgs();
} catch (e) {
if (process.argv.find(v => /^-[\w]*v[\w]*/.test(v)) || process.argv.includes("--verbose")) {
console.log(errorPrefix, "Couldn't parse arguments:", e);
} else {
console.log(errorPrefix, "Couldn't parse arguments:", e.message);
}
process.exitCode = 1;
return;
}
// help flag
if (args.help) {
helpMessage();
return;
}
// create-file flag
if (args.createFile) {
createFile(args);
return;
}
// main block
try {
// get data if available
if (args.verbose)
console.log(debugPrefix, "Fetching file...");
/**
* version is set if the file exists
* forceRehash is true if the hash in the file is invalid
* dataDamaged is true if the data property contains unparsable data
* dataCleared is true if the user decided to clear the data because it was damaged
*/
const { version, checksum: fetchedChecksum, data: encryptedData, forceRehash, dataDamaged, dataCleared } = await fetchFile(args);
// ask master password
if (args.verbose)
console.log(debugPrefix, "Asking for master password...");
const { password, checksum } = await getMasterPassword(args, fetchedChecksum, forceRehash, version !== null);
// init timeout counter
initTimeout(args.timeout * 1000);
// fix bad data
// can only be true if a file was loaded
// no excryption key is needed for this saveFile call, as the data is already encrypted
if (checksum !== fetchedChecksum || dataCleared) {
if (args.verbose)
console.log(debugPrefix, "Saving updated data...");
saveFile(args, { checksum, data: encryptedData });
}
// decrypt data
const data = await decryptData(args, encryptedData, password, dataDamaged, version !== null, version);
// import data if flag is set and if the file was loaded (terminates the program)
if (args.import && version !== null) {
console.log(infoPrefix, "Importing from", args.import);
await importData(args, password, data);
terminate(true);
}
// main loop
await loop(args, data, password);
} catch (e) {
if (args.verbose) {
console.log(errorPrefix, "Fatal Error:", e);
} else {
console.log(errorPrefix, "Fatal Error:", e.message);
}
terminate(true);
}
}
/**
* The main loop
* @param {args} args
* @param {data} data The file data
* @param {string} password The master password
*/
async function loop(args, data, password) {
while (true) {
// call interactions
if (data) {
await mainMenu(args, data, password);
} else {
await retrievePasswords(args, noDataRetrievalDialogue(args), password);
}
// allow user to exit gracefully
const { loop } = await prompt([{
type: "list",
name: "loop",
message: "Do you want to perform another action?",
choices: [{ name: "Yes", value: true }, { name: "No", value: false }]
}]);
if (!loop)
terminate(true);
}
}
// run program
main();