UNPKG

11.5 kBJavaScriptView Raw
1'use strict';
2var AVAILABLE_FIELDS, DEBUG, DEFAULT_CONFIG, OUTPUTS_DIR, TEMPLATES_DIR, async, chalk, cli, config, exec, f, filterTemplates, fs, getChoicesFrom, getConfig, getConfigPath, getFrom, getHomeDir, getOutputDir, getQuestionFor, inquirer, log, logJson, meow, path, readOwnConfig, readTemplates, templates, toJson;
3
4inquirer = require('inquirer');
5
6exec = require('child_process').exec;
7
8async = require('async');
9
10chalk = require('chalk');
11
12meow = require('meow');
13
14path = require('path');
15
16fs = require('fs-extra');
17
18DEBUG = false;
19
20TEMPLATES_DIR = 'templates';
21
22OUTPUTS_DIR = 'outputs';
23
24DEFAULT_CONFIG = {
25 path: OUTPUTS_DIR,
26 extensions: '.enc.txt'
27};
28
29AVAILABLE_FIELDS = {
30 name: {},
31 website: {},
32 login: {},
33 password: {
34 type: 'password'
35 },
36 email: {},
37 seed: {
38 msg: 'Input 2FA seed'
39 }
40};
41
42log = (function(_this) {
43 return function() {
44 if (DEBUG) {
45 return console.log.apply(_this, arguments);
46 }
47 };
48})(this);
49
50toJson = function(obj) {
51 return JSON.stringify(obj, null, ' ');
52};
53
54logJson = function(obj) {
55 return log(toJson(obj));
56};
57
58getHomeDir = function() {
59 return process.env.HOME || process.env.USERPROFILE;
60};
61
62getConfigPath = function(name) {
63 if (name == null) {
64 name = 'savepass';
65 }
66 return path.join(getHomeDir(), "/.config/" + name + "/config.json");
67};
68
69getOutputDir = function() {
70 return path.resolve(!config.path ? OUTPUTS_DIR : config.path.replace(/^~/, getHomeDir()));
71};
72
73getQuestionFor = function(fieldType) {
74 var base, ref, ref1;
75 base = AVAILABLE_FIELDS[fieldType];
76 return {
77 type: (ref = base.type) != null ? ref : 'input',
78 name: fieldType,
79 message: (ref1 = base.msg) != null ? ref1 : 'Input your ' + fieldType
80 };
81};
82
83getChoicesFrom = function(templates) {
84 return templates.map(function(v, i) {
85 return {
86 name: v.name,
87 value: i
88 };
89 });
90};
91
92getFrom = function(templates) {
93 return function(i) {
94 return templates[i];
95 };
96};
97
98getConfig = function(name, cb) {
99 var ref;
100 if (name == null) {
101 name = 'keybase';
102 }
103 if (typeof name === 'function') {
104 ref = ['savepass', name], name = ref[0], cb = ref[1];
105 } else if (name === 'self') {
106 name = 'savepass';
107 }
108 return fs.readJson(getConfigPath(name), cb);
109};
110
111config = {};
112
113readOwnConfig = function(next) {
114 return getConfig(function(err, data) {
115 if (err && err.code === 'ENOENT') {
116 log('creating config file...');
117 config = DEFAULT_CONFIG;
118 log('config (new):', toJson(config));
119 return fs.outputJson(err.path, config, next);
120 } else {
121 config = data;
122 log('config (file):', toJson(config));
123 return next();
124 }
125 });
126};
127
128templates = [];
129
130filterTemplates = function(filter) {
131 return templates.filter(function(t) {
132 return -1 < t.fileName.indexOf(filter);
133 });
134};
135
136readTemplates = function(next) {
137 var readTemplate;
138 readTemplate = function(template, next) {
139 var tempPath;
140 tempPath = path.resolve(TEMPLATES_DIR, template);
141 return fs.readFile(tempPath, {
142 encoding: 'utf8'
143 }, function(err, fileContent) {
144 var af, m, re;
145 if (err) {
146 next(err);
147 return;
148 }
149 re = new RegExp('<(' + ((function() {
150 var results;
151 results = [];
152 for (af in AVAILABLE_FIELDS) {
153 results.push(af);
154 }
155 return results;
156 })()).join('|') + ')>', 'g');
157 templates.push({
158 file: tempPath,
159 fileName: template,
160 fileContent: fileContent,
161 fields: (function() {
162 var results;
163 results = [];
164 while (m = re.exec(fileContent)) {
165 results.push(m[1]);
166 }
167 return results;
168 })(),
169 name: template.replace(/\.temp$/, '').replace(/-/g, ' ').split(' ').map(function(word) {
170 return word.charAt(0).toUpperCase() + word.substr(1);
171 }).join(' ')
172 });
173 return next();
174 });
175 };
176 return fs.readdir(TEMPLATES_DIR, function(err, files) {
177 if (err) {
178 console.error('not even directory for templates exist.', toJson(err));
179 return;
180 }
181 return async.each(files, readTemplate, function(err) {
182 if (err) {
183 console.error('reading template file failed... apparently', err);
184 return;
185 }
186 log(templates.length + " templates read:", templates.map(function(t) {
187 return t.fileName;
188 }));
189 return next();
190 });
191 });
192};
193
194cli = meow({
195 help: [
196 'Usage: ' + chalk.bold('savepass <command>'), '', 'where <command> is one of:', ' add, new, list, ls,', ' remove*, rm*, get*', '', 'Example Usage:', ' savepass add [OPTIONAL <flags>]', ' savepass ls', '', 'Available ' + chalk.bold('add|new') + ' subcomand flags:', ' ' + chalk.bold('--template') + '=<templateName>', ' Specify template name to be used. Available templates can be', ' found in `templates/` folder.', ' ' + chalk.bold('--keybase-user') + '=<keybaseUsername>', ' Encrypt output file for a different user then the one logged in.', ' ' + ((function() {
197 var results;
198 results = [];
199 for (f in AVAILABLE_FIELDS) {
200 results.push(chalk.bold("--" + f));
201 }
202 return results;
203 })()).join(', '), ' Using those flags you can pass values, to be filled into a', ' template, directly from CLI. All flags accept strings or "null" to disable.', ' ' + chalk.bold('Flag --password can only be set to null'), '', 'Specify configs in the json-formatted file:', ' ' + getConfigPath()
204 ].join('\n')
205});
206
207log('cli flags:', cli.input, toJson(cli.flags));
208
209async.parallel([readTemplates, readOwnConfig], function(err) {
210 var matchingTemplates, step1, step2, step3, step4, step5, step6;
211 if (err) {
212 console.error(err);
213 return;
214 }
215 switch (cli.input[0]) {
216 case 'add':
217 case 'new':
218 case void 0:
219 step6 = function(path, content) {
220 console.log('saving...');
221 return fs.outputFile(path, content, function(err) {
222 if (err) {
223 return console.error(err);
224 }
225 return console.log('success!');
226 });
227 };
228 step5 = function(fileName, contents) {
229 var filePath, q, qs;
230 log('file name:', fileName);
231 q = {
232 name: 'fileName',
233 message: 'How do you want to name the file?'
234 };
235 if (fileName) {
236 q["default"] = fileName.replace('.', '-');
237 }
238 filePath = null;
239 qs = [
240 q, {
241 name: 'confirm',
242 type: 'confirm',
243 message: function(prevAnswer) {
244 var outputDir;
245 outputDir = getOutputDir();
246 log('Output files dir:', outputDir);
247 filePath = path.resolve(outputDir, prevAnswer.fileName + (config.extensions || DEFAULT_CONFIG.extensions));
248 return ['File will be saved as:', ' ' + filePath].join('\n');
249 }
250 }
251 ];
252 return inquirer.prompt(qs, function(answers) {
253 if (!answers.confirm) {
254 step5(answers.fileName, contents);
255 return;
256 }
257 return step6(filePath, contents);
258 });
259 };
260 step4 = function(name, text) {
261 var getKeybaseUser;
262 getKeybaseUser = function(cb) {
263 if (cli.flags.keybaseUser) {
264 cb(cli.flags.keybaseUser);
265 return;
266 }
267 return getConfig('keybase', function(err, data) {
268 return cb(data.user.name);
269 });
270 };
271 return getKeybaseUser(function(keybaseUser) {
272 console.log("Encrypting and signing for: " + keybaseUser);
273 return exec(['keybase encrypt', '-s', "-m '" + text + "'", keybaseUser].join(' '), function(err, stdout, stderr) {
274 if (err || stderr) {
275 if (err) {
276 console.error(err);
277 }
278 if (stderr) {
279 console.error(stderr);
280 }
281 return;
282 }
283 log('encrypted file:\n', stdout);
284 return step5(name, stdout);
285 });
286 });
287 };
288 step3 = function(template, data) {
289 var key, val;
290 log('Template and data before merge:\n', template, toJson(data));
291 for (key in data) {
292 val = data[key];
293 template = template.replace("<" + key + ">", val != null ? val : '');
294 }
295 return inquirer.prompt({
296 type: 'confirm',
297 name: 'proceed',
298 message: ['The following content will be encrypted and saved:', '', template, 'Do you want to proceed?'].join('\n')
299 }, function(answers) {
300 if (answers.proceed) {
301 return step4(data.website, template);
302 }
303 });
304 };
305 step2 = function(chosenTemplate) {
306 var encryptables, field, j, len, questions, ref, ref1, ref2;
307 log('chosen template:', toJson(chosenTemplate));
308 questions = [getQuestionFor('password')];
309 encryptables = {
310 password: null
311 };
312 ref = chosenTemplate.fields;
313 for (j = 0, len = ref.length; j < len; j++) {
314 field = ref[j];
315 if (!(field !== 'password')) {
316 continue;
317 }
318 encryptables[field] = (ref1 = cli.flags[field]) != null ? ref1 : null;
319 if (!encryptables[field]) {
320 questions.push(getQuestionFor(field));
321 } else if ((ref2 = encryptables[field]) === 'null' || ref2 === 'false') {
322 encryptables[field] = null;
323 }
324 }
325 log('encryptables (CLI)', toJson(encryptables));
326 log('questions', toJson(questions));
327 return inquirer.prompt(questions, function(answers) {
328 var a;
329 log('answers', toJson(answers));
330 for (a in encryptables) {
331 if (answers[a] && answers[a] !== '') {
332 encryptables[a] = answers[a];
333 }
334 }
335 log('encryptables (ALL)', toJson(encryptables));
336 return step3(chosenTemplate.fileContent, encryptables);
337 });
338 };
339 step1 = function(templates) {
340 return inquirer.prompt({
341 type: 'list',
342 name: 'type',
343 message: 'Which template do you want to use?',
344 choices: getChoicesFrom(templates),
345 filter: getFrom(templates)
346 }, function(answer) {
347 return step2(answer.type);
348 });
349 };
350 matchingTemplates = filterTemplates(cli.flags.template);
351 switch (matchingTemplates.length) {
352 case 0:
353 return step1(templates);
354 case 1:
355 return step2(matchingTemplates[0]);
356 default:
357 return step1(matchingTemplates);
358 }
359 break;
360 case 'list':
361 case 'ls':
362 log('ls');
363 return fs.readdir(getOutputDir(), function(err, files) {
364 var fileList;
365 if (err) {
366 console.error(err);
367 return;
368 }
369 fileList = files.filter(function(fileName) {
370 return /\.enc\.txt$/.test(fileName);
371 }).map(function(fileName) {
372 return ['', chalk.green('*'), chalk.bold(fileName.replace(/\.enc\.txt$/, '').replace(/[-_]/g, ' ')), chalk.dim(" (" + fileName + ")")].join(' ');
373 }).join('\n');
374 console.log("Your encrypted files: (from " + (chalk.bold(getOutputDir())) + ")\n");
375 return console.log(fileList);
376 });
377 }
378});
379
380
381/*
382
383 file
384 login
385 email
386 password
387 website
388 2FA
389 rule (desc)
390 just password(s)
391
392 creds|credentials
393 one
394 many|multi
395 file
396 desc
397 */