-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTwoFA.js
125 lines (100 loc) · 2.91 KB
/
TwoFA.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
const promisify = func => (...args) =>
new Promise((resolve, reject) =>
func(...args, (err, result) => (err ? reject(err) : resolve(result)))
);
const Conf = require('conf');
const jimpRead = promisify(require('jimp').read);
const jsQR = require('jsqr');
const fs = require('fs');
const OTPAuth = require('otpauth');
const qrcode = require('qrcode-terminal');
const screencapture = promisify(require('screencapture'));
class TwoFA {
constructor() {
this.store = new Conf(this);
this.screencap = screencapture;
}
add(service, options) {
options = options || {};
if (this._storeExists(service)) {
return Promise.reject(`A service with name '${service}' already exists.`);
}
const promise = options.imagePath ?
this._readQRCode(options.imagePath) : this._captureAndReadQRCode() ;
return promise
.then(uri => OTPAuth.URI.parse(uri))
.then(otpauth => this.store.set(service, otpauth.toString()))
.then(() => this.gen(service));
}
del(service) {
return this._storeDel(service);
}
gen(service) {
if (!service) {
return this._genAll();
}
return this._storeGet(service)
.then(uri => OTPAuth.URI.parse(uri))
.then(otpauth => ({
code: otpauth.generate(),
label: otpauth.label,
service: service,
}));
}
qrcode(service) {
return this._storeGet(service)
.then(uri => {
return new Promise(resolve =>
qrcode.generate(uri, { small: true }, qrcode => resolve(qrcode))
)
});
}
_captureAndReadQRCode() {
return this.screencap()
.then(imagePath => {
this.lastQRCode = imagePath;
return imagePath;
})
.then(image => this._readQRCode(image))
.catch((e) => {
const error = e instanceof Object ?
'The image capture failed or user canceled.' : e;
return Promise.reject(error);
});
}
_genAll() {
const services = Object.keys(this.store.get() || {});
const codes = [];
services.forEach(service => {
codes.push(this.gen(service));
});
return Promise.all(codes);
}
_readQRCode(imagePath) {
const buffer = fs.readFileSync(imagePath);
return jimpRead(buffer)
.then(image => {
const bitmap = image.bitmap
const code = jsQR(bitmap.data, bitmap.width, bitmap.height) || {};
if (!{}.hasOwnProperty.call(code, 'data')) {
return Promise.reject('Invalid qrcode image. Try again.');
}
return code.data;
});
}
_storeDel(service) {
return this._storeGet(service)
.then(() => this.store.delete(service));
}
_storeExists(service) {
return !!this.store.get(service);
}
_storeGet(service) {
const value = this.store.get(service);
if (!value) {
return Promise.reject(`A service with name '${service}' not exists.`);
}
return Promise.resolve(value);
}
}
module.exports = TwoFA;