keeweb/app/scripts/models/external/yubikey-otp-model.js

137 lines
3.9 KiB
JavaScript
Raw Normal View History

2020-05-05 21:26:41 +02:00
import { Events } from 'framework/events';
2020-04-15 16:50:01 +02:00
import { ExternalOtpDeviceModel } from 'models/external/external-otp-device-model';
import { ExternalOtpEntryModel } from 'models/external/external-otp-entry-model';
import { Launcher } from 'comp/launcher';
2020-05-05 21:26:41 +02:00
import { UsbListener } from 'comp/app/usb-listener';
2020-04-15 16:50:01 +02:00
2020-04-19 20:55:38 +02:00
let ykmanStatus;
2020-04-19 20:42:55 +02:00
2020-04-19 20:55:38 +02:00
class YubiKeyOtpModel extends ExternalOtpDeviceModel {
2020-04-15 16:50:01 +02:00
constructor(props) {
super({
2020-05-05 20:03:38 +02:00
id: 'yubikey',
name: 'YubiKey',
2020-04-15 16:50:01 +02:00
shortName: 'YubiKey',
2020-05-05 20:50:11 +02:00
deviceClassName: 'YubiKey',
2020-04-15 16:50:01 +02:00
...props
});
}
2020-04-19 20:55:38 +02:00
static get ykmanStatus() {
return ykmanStatus;
}
2020-05-05 21:26:41 +02:00
onUsbDevicesChanged = () => {
if (UsbListener.attachedYubiKeys.length === 0) {
this.emit('ejected');
}
};
2020-04-15 16:50:01 +02:00
open(callback) {
this.openProcess = Launcher.spawn({
cmd: 'ykman',
args: ['oath', 'code'],
noStdOutLogging: true,
complete: (err, stdout, code) => {
this.openProcess = null;
2020-05-05 21:26:41 +02:00
if (this.openAborted) {
err = 'Open aborted';
}
2020-04-15 16:50:01 +02:00
if (err) {
return callback(err);
}
for (const line of stdout.split('\n')) {
const match = line.match(/^(.*?):(.*?)\s+(.*)$/);
if (!match) {
continue;
}
const [, title, user, code] = match;
const needsTouch = !code.match(/^\d+$/);
this.entries.push(
new ExternalOtpEntryModel({
id: title + ':' + user,
device: this,
icon: 'clock-o',
title,
user,
needsTouch
})
);
}
2020-05-05 20:03:38 +02:00
this.active = true;
2020-05-05 21:26:41 +02:00
Events.on('usb-devices-changed', this.onUsbDevicesChanged);
2020-04-15 16:50:01 +02:00
callback();
}
});
}
cancelOpen() {
2020-05-05 21:26:41 +02:00
Events.off('usb-devices-changed', this.onUsbDevicesChanged);
2020-04-15 16:50:01 +02:00
this.openAborted = true;
if (this.openProcess) {
this.openProcess.kill();
}
}
getOtp(entry, callback) {
const msPeriod = 30000;
const timeLeft = msPeriod - (Date.now() % msPeriod) + 500;
return Launcher.spawn({
cmd: 'ykman',
args: ['oath', 'code', '--single', `${entry.title}:${entry.user}`],
noStdOutLogging: true,
complete: (err, stdout) => {
if (err) {
return callback(err, null, timeLeft);
}
const otp = stdout.trim();
callback(null, otp, timeLeft);
}
});
}
cancelGetOtp(entry, ps) {
if (ps) {
ps.kill();
}
}
2020-04-19 20:42:55 +02:00
2020-05-05 20:47:15 +02:00
close(callback) {
2020-05-05 21:26:41 +02:00
Events.off('usb-devices-changed', this.onUsbDevicesChanged);
2020-05-05 20:47:15 +02:00
this.set({
active: false
});
}
2020-04-19 20:42:55 +02:00
static checkToolStatus() {
2020-04-19 20:55:38 +02:00
if (ykmanStatus === 'ok') {
return Promise.resolve();
}
2020-04-19 20:42:55 +02:00
return new Promise(resolve => {
2020-04-19 20:55:38 +02:00
ykmanStatus = 'checking';
2020-04-19 20:42:55 +02:00
Launcher.spawn({
cmd: 'ykman',
args: ['-v'],
noStdOutLogging: true,
complete: (err, stdout, code) => {
if (err || code !== 0) {
2020-04-19 20:55:38 +02:00
ykmanStatus = 'error';
2020-04-19 20:42:55 +02:00
} else {
2020-04-19 20:55:38 +02:00
ykmanStatus = 'ok';
2020-04-19 20:42:55 +02:00
}
resolve();
}
});
});
}
2020-04-15 16:50:01 +02:00
}
YubiKeyOtpModel.defineModelProperties({
2020-05-05 21:26:41 +02:00
onUsbDevicesChanged: null,
2020-04-15 16:50:01 +02:00
openProcess: null,
openAborted: false
});
export { YubiKeyOtpModel };