mirror of
https://github.com/keeweb/keeweb.git
synced 2024-06-26 07:39:04 +02:00
extracted YubiKey module
This commit is contained in:
parent
bf7dec2437
commit
81d4c5c403
206
app/scripts/comp/app/yubikey.js
Normal file
206
app/scripts/comp/app/yubikey.js
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
import { Events } from 'framework/events';
|
||||||
|
import { Launcher } from 'comp/launcher';
|
||||||
|
import { Logger } from 'util/logger';
|
||||||
|
import { UsbListener } from 'comp/app/usb-listener';
|
||||||
|
import { AppSettingsModel } from 'models/app-settings-model';
|
||||||
|
import { Timeouts } from 'const/timeouts';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
|
||||||
|
const logger = new Logger('yubikey');
|
||||||
|
|
||||||
|
const YubiKey = {
|
||||||
|
ykmanStatus: undefined,
|
||||||
|
process: null,
|
||||||
|
aborted: false,
|
||||||
|
|
||||||
|
checkToolStatus() {
|
||||||
|
if (this.ykmanStatus === 'ok') {
|
||||||
|
return Promise.resolve(this.ykmanStatus);
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.ykmanStatus = 'checking';
|
||||||
|
Launcher.spawn({
|
||||||
|
cmd: 'ykman',
|
||||||
|
args: ['-v'],
|
||||||
|
noStdOutLogging: true,
|
||||||
|
complete: (err, stdout, code) => {
|
||||||
|
if (err || code !== 0) {
|
||||||
|
this.ykmanStatus = 'error';
|
||||||
|
} else {
|
||||||
|
this.ykmanStatus = 'ok';
|
||||||
|
}
|
||||||
|
resolve(this.ykmanStatus);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
logger.info('Aborting');
|
||||||
|
if (this.process) {
|
||||||
|
logger.info('Killing the process');
|
||||||
|
try {
|
||||||
|
this.process.kill();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
this.aborted = true;
|
||||||
|
this.process = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
list(callback) {
|
||||||
|
this._list(callback, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_list(callback, canRetry) {
|
||||||
|
if (this.process) {
|
||||||
|
return callback('Already in progress');
|
||||||
|
}
|
||||||
|
this.aborted = false;
|
||||||
|
|
||||||
|
logger.info('Listing YubiKeys');
|
||||||
|
|
||||||
|
if (UsbListener.attachedYubiKeys.length === 0) {
|
||||||
|
return callback(null, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.process = Launcher.spawn({
|
||||||
|
cmd: 'ykman',
|
||||||
|
args: ['list'],
|
||||||
|
noStdOutLogging: true,
|
||||||
|
complete: (err, stdout) => {
|
||||||
|
this.process = null;
|
||||||
|
|
||||||
|
if (this.aborted) {
|
||||||
|
return callback('Aborted');
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const yubiKeysIncludingEmpty = stdout
|
||||||
|
.trim()
|
||||||
|
.split(/\n/g)
|
||||||
|
.map(line => (line.match(/\d{5,}$/g) || [])[0]);
|
||||||
|
|
||||||
|
const yubiKeys = yubiKeysIncludingEmpty.filter(s => s);
|
||||||
|
|
||||||
|
if (
|
||||||
|
yubiKeysIncludingEmpty.length === 1 &&
|
||||||
|
yubiKeys.length === 0 &&
|
||||||
|
stdout.startsWith('YubiKey') &&
|
||||||
|
stdout.includes('CCID') &&
|
||||||
|
!stdout.includes('Serial')
|
||||||
|
) {
|
||||||
|
logger.info('The YubiKey is probably stuck');
|
||||||
|
if (!AppSettingsModel.yubiKeyStuckWorkaround) {
|
||||||
|
return callback(Locale.yubiKeyStuckError);
|
||||||
|
}
|
||||||
|
if (canRetry) {
|
||||||
|
return this._repairStuckYubiKey(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yubiKeys.length) {
|
||||||
|
return callback('No YubiKeys returned by "ykman list"');
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, yubiKeys);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_repairStuckYubiKey(callback) {
|
||||||
|
logger.info('Repairing a stuck YubiKey');
|
||||||
|
|
||||||
|
let openTimeout;
|
||||||
|
const countYubiKeys = UsbListener.attachedYubiKeys.length;
|
||||||
|
const onDevicesChangedDuringRepair = () => {
|
||||||
|
if (UsbListener.attachedYubiKeys.length === countYubiKeys) {
|
||||||
|
logger.info('YubiKey was reconnected');
|
||||||
|
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
||||||
|
clearTimeout(openTimeout);
|
||||||
|
this.aborted = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
this._list(callback, false);
|
||||||
|
}, Timeouts.ExternalDeviceAfterReconnect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Events.on('usb-devices-changed', onDevicesChangedDuringRepair);
|
||||||
|
|
||||||
|
Launcher.spawn({
|
||||||
|
cmd: 'ykman',
|
||||||
|
args: ['config', 'usb', '-e', 'oath', '-f'],
|
||||||
|
noStdOutLogging: true,
|
||||||
|
complete: err => {
|
||||||
|
logger.info('Repair complete', err ? 'with error' : 'OK');
|
||||||
|
if (err) {
|
||||||
|
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
||||||
|
return callback(`YubiKey repair error: ${err}`);
|
||||||
|
}
|
||||||
|
openTimeout = setTimeout(() => {
|
||||||
|
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
||||||
|
}, Timeouts.ExternalDeviceReconnect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getOtpCodes(serial, callback) {
|
||||||
|
if (this.process) {
|
||||||
|
return callback('Already in progress');
|
||||||
|
}
|
||||||
|
this.aborted = false;
|
||||||
|
|
||||||
|
this.process = Launcher.spawn({
|
||||||
|
cmd: 'ykman',
|
||||||
|
args: ['-d', serial, 'oath', 'code'],
|
||||||
|
noStdOutLogging: true,
|
||||||
|
throwOnStdErr: true,
|
||||||
|
complete: (err, stdout) => {
|
||||||
|
this.process = null;
|
||||||
|
|
||||||
|
if (this.aborted) {
|
||||||
|
return callback('Aborted');
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const codes = [];
|
||||||
|
|
||||||
|
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+$/);
|
||||||
|
|
||||||
|
codes.push({
|
||||||
|
title,
|
||||||
|
user,
|
||||||
|
needsTouch
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, codes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getOtp(serial, entry, callback) {
|
||||||
|
return Launcher.spawn({
|
||||||
|
cmd: 'ykman',
|
||||||
|
args: ['-d', serial, 'oath', 'code', '--single', entry],
|
||||||
|
noStdOutLogging: true,
|
||||||
|
complete: (err, stdout) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
const otp = stdout.trim();
|
||||||
|
callback(null, otp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { YubiKey };
|
|
@ -41,7 +41,7 @@ const DefaultAppSettings = {
|
||||||
yubiKeyAutoOpen: false, // auto-load one-time codes when there are open files
|
yubiKeyAutoOpen: false, // auto-load one-time codes when there are open files
|
||||||
yubiKeyMatchEntries: true, // show matching one-time codes in entries
|
yubiKeyMatchEntries: true, // show matching one-time codes in entries
|
||||||
yubiKeyShowChalResp: true, // show YubiKey challenge-response option
|
yubiKeyShowChalResp: true, // show YubiKey challenge-response option
|
||||||
yubiKeyOathWorkaround: false, // enable the workaround for YubiKey OATH issues
|
yubiKeyStuckWorkaround: false, // enable the workaround for stuck YubiKeys
|
||||||
|
|
||||||
canOpen: true, // can select and open new files
|
canOpen: true, // can select and open new files
|
||||||
canOpenDemo: true, // can open a demo file
|
canOpenDemo: true, // can open a demo file
|
||||||
|
|
|
@ -604,7 +604,7 @@
|
||||||
"setDevicesYubiKeyChalRespTitle": "Challenge-Response",
|
"setDevicesYubiKeyChalRespTitle": "Challenge-Response",
|
||||||
"setDevicesYubiKeyChalRespDesc": "It's also possible to use a YubiKey in challenge-response mode, so that a piece of private key used to encrypt files resides on a YubiKey.",
|
"setDevicesYubiKeyChalRespDesc": "It's also possible to use a YubiKey in challenge-response mode, so that a piece of private key used to encrypt files resides on a YubiKey.",
|
||||||
"setDevicesYubiKeyChalRespShow": "Show the option to use a YubiKey when opening files",
|
"setDevicesYubiKeyChalRespShow": "Show the option to use a YubiKey when opening files",
|
||||||
"setDevicesYubiKeyOathWorkaround": "Reconnect the YubiKey if it hangs when loading one-time codes",
|
"setDevicesYubiKeyStuckWorkaround": "Reconnect the YubiKey if it seems to be stuck during loading",
|
||||||
|
|
||||||
"setAboutTitle": "About",
|
"setAboutTitle": "About",
|
||||||
"setAboutBuilt": "This app is built with these awesome tools",
|
"setAboutBuilt": "This app is built with these awesome tools",
|
||||||
|
|
249
app/scripts/models/external/yubikey-otp-model.js
vendored
249
app/scripts/models/external/yubikey-otp-model.js
vendored
|
@ -4,11 +4,7 @@ import { ExternalOtpEntryModel } from 'models/external/external-otp-entry-model'
|
||||||
import { Launcher } from 'comp/launcher';
|
import { Launcher } from 'comp/launcher';
|
||||||
import { Logger } from 'util/logger';
|
import { Logger } from 'util/logger';
|
||||||
import { UsbListener } from 'comp/app/usb-listener';
|
import { UsbListener } from 'comp/app/usb-listener';
|
||||||
import { AppSettingsModel } from 'models/app-settings-model';
|
import { YubiKey } from 'comp/app/yubikey';
|
||||||
import { Timeouts } from 'const/timeouts';
|
|
||||||
import { Locale } from 'util/locale';
|
|
||||||
|
|
||||||
let ykmanStatus;
|
|
||||||
|
|
||||||
const logger = new Logger('yubikey');
|
const logger = new Logger('yubikey');
|
||||||
|
|
||||||
|
@ -23,10 +19,6 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get ykmanStatus() {
|
|
||||||
return ykmanStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUsbDevicesChanged = () => {
|
onUsbDevicesChanged = () => {
|
||||||
if (UsbListener.attachedYubiKeys.length === 0) {
|
if (UsbListener.attachedYubiKeys.length === 0) {
|
||||||
this.emit('ejected');
|
this.emit('ejected');
|
||||||
|
@ -34,157 +26,61 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
open(callback) {
|
open(callback) {
|
||||||
this._open(callback, true);
|
YubiKey.list((err, yubiKeys) => {
|
||||||
}
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
_open(callback, canRetry) {
|
|
||||||
logger.info('Listing YubiKeys');
|
|
||||||
|
|
||||||
if (UsbListener.attachedYubiKeys.length === 0) {
|
|
||||||
return callback('No YubiKeys');
|
|
||||||
}
|
|
||||||
this.openProcess = Launcher.spawn({
|
|
||||||
cmd: 'ykman',
|
|
||||||
args: ['list'],
|
|
||||||
noStdOutLogging: true,
|
|
||||||
complete: (err, stdout) => {
|
|
||||||
this.openProcess = null;
|
|
||||||
if (this.openAborted) {
|
|
||||||
return callback('Open aborted');
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
const yubiKeysIncludingEmpty = stdout
|
|
||||||
.trim()
|
|
||||||
.split(/\n/g)
|
|
||||||
.map(line => (line.match(/\d{5,}$/g) || [])[0]);
|
|
||||||
|
|
||||||
const yubiKeys = yubiKeysIncludingEmpty.filter(s => s);
|
|
||||||
|
|
||||||
if (
|
|
||||||
yubiKeysIncludingEmpty.length === 1 &&
|
|
||||||
yubiKeys.length === 0 &&
|
|
||||||
stdout.startsWith('YubiKey') &&
|
|
||||||
stdout.includes('CCID') &&
|
|
||||||
!stdout.includes('Serial')
|
|
||||||
) {
|
|
||||||
logger.info('The YubiKey is probably stuck');
|
|
||||||
if (!AppSettingsModel.yubiKeyOathWorkaround) {
|
|
||||||
return callback(Locale.yubiKeyStuckError);
|
|
||||||
}
|
|
||||||
if (canRetry) {
|
|
||||||
this._repairStuckYubiKey(callback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!yubiKeys || !yubiKeys.length) {
|
|
||||||
return callback('No YubiKeys returned by "ykman list"');
|
|
||||||
}
|
|
||||||
|
|
||||||
let openSuccess = 0;
|
|
||||||
const openErrors = [];
|
|
||||||
const openNextYubiKey = () => {
|
|
||||||
const yubiKey = yubiKeys.shift();
|
|
||||||
this._addYubiKey(yubiKey, err => {
|
|
||||||
if (this.openAborted) {
|
|
||||||
return callback('Open aborted');
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
openErrors.push(err);
|
|
||||||
} else {
|
|
||||||
openSuccess++;
|
|
||||||
}
|
|
||||||
if (yubiKeys && yubiKeys.length) {
|
|
||||||
openNextYubiKey();
|
|
||||||
} else {
|
|
||||||
if (openSuccess) {
|
|
||||||
this._openComplete();
|
|
||||||
}
|
|
||||||
callback(openSuccess ? null : openErrors[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
openNextYubiKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let openSuccess = 0;
|
||||||
|
const openErrors = [];
|
||||||
|
const openNextYubiKey = () => {
|
||||||
|
const yubiKey = yubiKeys.shift();
|
||||||
|
this._addYubiKey(yubiKey, err => {
|
||||||
|
if (YubiKey.aborted) {
|
||||||
|
return callback('Aborted');
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
openErrors.push(err);
|
||||||
|
} else {
|
||||||
|
openSuccess++;
|
||||||
|
}
|
||||||
|
if (yubiKeys && yubiKeys.length) {
|
||||||
|
openNextYubiKey();
|
||||||
|
} else {
|
||||||
|
if (openSuccess) {
|
||||||
|
this._openComplete();
|
||||||
|
}
|
||||||
|
callback(openSuccess ? null : openErrors[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
openNextYubiKey();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_addYubiKey(serial, callback) {
|
_addYubiKey(serial, callback) {
|
||||||
logger.info('Adding YubiKey', serial);
|
logger.info('Adding YubiKey', serial);
|
||||||
|
|
||||||
this.openProcess = Launcher.spawn({
|
YubiKey.getOtpCodes(serial, (err, codes) => {
|
||||||
cmd: 'ykman',
|
if (err) {
|
||||||
args: ['-d', serial, 'oath', 'code'],
|
return callback(err);
|
||||||
noStdOutLogging: true,
|
|
||||||
throwOnStdErr: true,
|
|
||||||
complete: (err, stdout) => {
|
|
||||||
this.openProcess = null;
|
|
||||||
if (this.openAborted) {
|
|
||||||
return callback('Open aborted');
|
|
||||||
}
|
|
||||||
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: this.entryId(title, user),
|
|
||||||
device: this,
|
|
||||||
deviceSubId: serial,
|
|
||||||
icon: 'clock-o',
|
|
||||||
title,
|
|
||||||
user,
|
|
||||||
needsTouch
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_repairStuckYubiKey(callback) {
|
for (const code of codes) {
|
||||||
logger.info('Repairing a stuck YubiKey');
|
this.entries.push(
|
||||||
|
new ExternalOtpEntryModel({
|
||||||
let openTimeout;
|
id: this.entryId(code.title, code.user),
|
||||||
const countYubiKeys = UsbListener.attachedYubiKeys.length;
|
device: this,
|
||||||
const onDevicesChangedDuringRepair = () => {
|
deviceSubId: serial,
|
||||||
if (UsbListener.attachedYubiKeys.length === countYubiKeys) {
|
icon: 'clock-o',
|
||||||
logger.info('YubiKey was reconnected');
|
title: code.title,
|
||||||
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
user: code.user,
|
||||||
clearTimeout(openTimeout);
|
needsTouch: code.needsTouch
|
||||||
this.openAborted = false;
|
})
|
||||||
setTimeout(() => {
|
);
|
||||||
this._open(callback, false);
|
|
||||||
}, Timeouts.ExternalDeviceAfterReconnect);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
Events.on('usb-devices-changed', onDevicesChangedDuringRepair);
|
|
||||||
|
|
||||||
Launcher.spawn({
|
callback();
|
||||||
cmd: 'ykman',
|
|
||||||
args: ['config', 'usb', '-e', 'oath', '-f'],
|
|
||||||
noStdOutLogging: true,
|
|
||||||
complete: err => {
|
|
||||||
logger.info('Repair complete', err ? 'with error' : 'OK');
|
|
||||||
if (err) {
|
|
||||||
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
openTimeout = setTimeout(() => {
|
|
||||||
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
|
||||||
}, Timeouts.ExternalDeviceReconnect);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,38 +91,15 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelOpen() {
|
cancelOpen() {
|
||||||
logger.info('Cancel open');
|
YubiKey.abort();
|
||||||
Events.off('usb-devices-changed', this.onUsbDevicesChanged);
|
Events.off('usb-devices-changed', this.onUsbDevicesChanged);
|
||||||
this.openAborted = true;
|
|
||||||
if (this.openProcess) {
|
|
||||||
logger.info('Killing the process');
|
|
||||||
try {
|
|
||||||
this.openProcess.kill();
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getOtp(entry, callback) {
|
getOtp(entry, callback) {
|
||||||
const msPeriod = 30000;
|
const msPeriod = 30000;
|
||||||
const timeLeft = msPeriod - (Date.now() % msPeriod) + 500;
|
const timeLeft = msPeriod - (Date.now() % msPeriod) + 500;
|
||||||
return Launcher.spawn({
|
YubiKey.getOtp(entry.deviceSubId, `${entry.title}:${entry.user}`, (err, otp) => {
|
||||||
cmd: 'ykman',
|
callback(err, otp, timeLeft);
|
||||||
args: [
|
|
||||||
'-d',
|
|
||||||
entry.deviceSubId,
|
|
||||||
'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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,34 +115,10 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
||||||
active: false
|
active: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkToolStatus() {
|
|
||||||
if (ykmanStatus === 'ok') {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return new Promise(resolve => {
|
|
||||||
ykmanStatus = 'checking';
|
|
||||||
Launcher.spawn({
|
|
||||||
cmd: 'ykman',
|
|
||||||
args: ['-v'],
|
|
||||||
noStdOutLogging: true,
|
|
||||||
complete: (err, stdout, code) => {
|
|
||||||
if (err || code !== 0) {
|
|
||||||
ykmanStatus = 'error';
|
|
||||||
} else {
|
|
||||||
ykmanStatus = 'ok';
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
YubiKeyOtpModel.defineModelProperties({
|
YubiKeyOtpModel.defineModelProperties({
|
||||||
onUsbDevicesChanged: null,
|
onUsbDevicesChanged: null
|
||||||
openProcess: null,
|
|
||||||
openAborted: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export { YubiKeyOtpModel };
|
export { YubiKeyOtpModel };
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { SecureInput } from 'comp/browser/secure-input';
|
||||||
import { Launcher } from 'comp/launcher';
|
import { Launcher } from 'comp/launcher';
|
||||||
import { Alerts } from 'comp/ui/alerts';
|
import { Alerts } from 'comp/ui/alerts';
|
||||||
import { UsbListener } from 'comp/app/usb-listener';
|
import { UsbListener } from 'comp/app/usb-listener';
|
||||||
import { YubiKeyOtpModel } from 'models/external/yubikey-otp-model';
|
import { YubiKey } from 'comp/app/yubikey';
|
||||||
import { Keys } from 'const/keys';
|
import { Keys } from 'const/keys';
|
||||||
import { Comparators } from 'util/data/comparators';
|
import { Comparators } from 'util/data/comparators';
|
||||||
import { Features } from 'util/features';
|
import { Features } from 'util/features';
|
||||||
|
@ -47,7 +47,7 @@ class OpenView extends View {
|
||||||
'keypress .open__pass-input': 'inputKeypress',
|
'keypress .open__pass-input': 'inputKeypress',
|
||||||
'click .open__pass-enter-btn': 'openDb',
|
'click .open__pass-enter-btn': 'openDb',
|
||||||
'click .open__settings-key-file': 'openKeyFile',
|
'click .open__settings-key-file': 'openKeyFile',
|
||||||
'click .open__settings-yubikey': 'selectYubiKey',
|
'click .open__settings-yubikey': 'selectYubiKeyChalResp',
|
||||||
'click .open__last-item': 'openLast',
|
'click .open__last-item': 'openLast',
|
||||||
'click .open__icon-generate': 'toggleGenerator',
|
'click .open__icon-generate': 'toggleGenerator',
|
||||||
dragover: 'dragover',
|
dragover: 'dragover',
|
||||||
|
@ -445,10 +445,6 @@ class OpenView extends View {
|
||||||
this.showOpenFileInfo(fileInfo, true);
|
this.showOpenFileInfo(fileInfo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectYubiKey() {
|
|
||||||
Alerts.notImplemented();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFile(id) {
|
removeFile(id) {
|
||||||
this.model.removeFileInfo(id);
|
this.model.removeFileInfo(id);
|
||||||
this.$el.find('.open__last-item[data-id="' + id + '"]').remove();
|
this.$el.find('.open__last-item[data-id="' + id + '"]').remove();
|
||||||
|
@ -1019,15 +1015,15 @@ class OpenView extends View {
|
||||||
const icon = this.$el.find('.open__icon-yubikey');
|
const icon = this.$el.find('.open__icon-yubikey');
|
||||||
icon.toggleClass('flip3d', true);
|
icon.toggleClass('flip3d', true);
|
||||||
|
|
||||||
YubiKeyOtpModel.checkToolStatus().then(() => {
|
YubiKey.checkToolStatus().then(status => {
|
||||||
if (YubiKeyOtpModel.ykmanStatus !== 'ok') {
|
if (status !== 'ok') {
|
||||||
icon.toggleClass('flip3d', false);
|
icon.toggleClass('flip3d', false);
|
||||||
this.inputEl.removeAttr('disabled');
|
this.inputEl.removeAttr('disabled');
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
return Events.emit('toggle-settings', 'devices');
|
return Events.emit('toggle-settings', 'devices');
|
||||||
}
|
}
|
||||||
this.otpDevice = this.model.openOtpDevice(err => {
|
this.otpDevice = this.model.openOtpDevice(err => {
|
||||||
if (err && !this.otpDevice.openAborted) {
|
if (err && !YubiKey.aborted) {
|
||||||
Alerts.error({
|
Alerts.error({
|
||||||
header: Locale.openError,
|
header: Locale.openError,
|
||||||
body: Locale.openErrorDescription,
|
body: Locale.openErrorDescription,
|
||||||
|
@ -1042,6 +1038,23 @@ class OpenView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectYubiKeyChalResp() {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.busy = true;
|
||||||
|
YubiKey.checkToolStatus().then(status => {
|
||||||
|
if (status !== 'ok') {
|
||||||
|
this.busy = false;
|
||||||
|
return Events.emit('toggle-settings', 'devices');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = false;
|
||||||
|
|
||||||
|
Alerts.notImplemented();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { OpenView };
|
export { OpenView };
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Events } from 'framework/events';
|
import { Events } from 'framework/events';
|
||||||
import { View } from 'framework/views/view';
|
import { View } from 'framework/views/view';
|
||||||
import { AppSettingsModel } from 'models/app-settings-model';
|
import { AppSettingsModel } from 'models/app-settings-model';
|
||||||
import { YubiKeyOtpModel } from 'models/external/yubikey-otp-model';
|
import { YubiKey } from 'comp/app/yubikey';
|
||||||
import { Links } from 'const/links';
|
import { Links } from 'const/links';
|
||||||
import { UsbListener } from 'comp/app/usb-listener';
|
import { UsbListener } from 'comp/app/usb-listener';
|
||||||
import template from 'templates/settings/settings-devices.hbs';
|
import template from 'templates/settings/settings-devices.hbs';
|
||||||
|
@ -15,13 +15,13 @@ class SettingsDevicesView extends View {
|
||||||
'change .settings__yubikey-auto-open': 'changeYubiKeyAutoOpen',
|
'change .settings__yubikey-auto-open': 'changeYubiKeyAutoOpen',
|
||||||
'change .settings__yubikey-match-entries': 'changeYubiKeyMatchEntries',
|
'change .settings__yubikey-match-entries': 'changeYubiKeyMatchEntries',
|
||||||
'change .settings__yubikey-chalresp-show': 'changeYubiKeyShowChalResp',
|
'change .settings__yubikey-chalresp-show': 'changeYubiKeyShowChalResp',
|
||||||
'change .settings__yubikey-oath-workaround': 'changeYubiKeyOathWorkaround'
|
'change .settings__yubikey-stuck-workaround': 'changeYubiKeyStuckWorkaround'
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
if (!['ok', 'checking'].includes(YubiKeyOtpModel.ykmanStatus)) {
|
if (!['ok', 'checking'].includes(YubiKey.ykmanStatus)) {
|
||||||
this.toolCheckPromise = YubiKeyOtpModel.checkToolStatus();
|
this.toolCheckPromise = YubiKey.checkToolStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +33,12 @@ class SettingsDevicesView extends View {
|
||||||
super.render({
|
super.render({
|
||||||
supported: UsbListener.supported,
|
supported: UsbListener.supported,
|
||||||
enableUsb: UsbListener.supported && AppSettingsModel.enableUsb,
|
enableUsb: UsbListener.supported && AppSettingsModel.enableUsb,
|
||||||
ykmanStatus: YubiKeyOtpModel.ykmanStatus,
|
ykmanStatus: YubiKey.ykmanStatus,
|
||||||
yubiKeyShowIcon: AppSettingsModel.yubiKeyShowIcon,
|
yubiKeyShowIcon: AppSettingsModel.yubiKeyShowIcon,
|
||||||
yubiKeyAutoOpen: AppSettingsModel.yubiKeyAutoOpen,
|
yubiKeyAutoOpen: AppSettingsModel.yubiKeyAutoOpen,
|
||||||
yubiKeyMatchEntries: AppSettingsModel.yubiKeyMatchEntries,
|
yubiKeyMatchEntries: AppSettingsModel.yubiKeyMatchEntries,
|
||||||
yubiKeyShowChalResp: AppSettingsModel.yubiKeyShowChalResp,
|
yubiKeyShowChalResp: AppSettingsModel.yubiKeyShowChalResp,
|
||||||
yubiKeyOathWorkaround: AppSettingsModel.yubiKeyOathWorkaround,
|
yubiKeyStuckWorkaround: AppSettingsModel.yubiKeyStuckWorkaround,
|
||||||
yubiKeyManualLink: Links.YubiKeyManual,
|
yubiKeyManualLink: Links.YubiKeyManual,
|
||||||
ykmanInstallLink: Links.YubiKeyManagerInstall
|
ykmanInstallLink: Links.YubiKeyManagerInstall
|
||||||
});
|
});
|
||||||
|
@ -70,8 +70,8 @@ class SettingsDevicesView extends View {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
changeYubiKeyOathWorkaround(e) {
|
changeYubiKeyStuckWorkaround(e) {
|
||||||
AppSettingsModel.yubiKeyOathWorkaround = e.target.checked;
|
AppSettingsModel.yubiKeyStuckWorkaround = e.target.checked;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,15 @@
|
||||||
{{#res 'setDevicesYubiKeyToolsDesc'}}<code>ykman</code>{{/res}}
|
{{#res 'setDevicesYubiKeyToolsDesc'}}<code>ykman</code>{{/res}}
|
||||||
{{#res 'setDevicesYubiKeyToolsDesc2'}}<a href="{{ykmanInstallLink}}" target="_blank">{{res 'setDevicesYubiKeyToolsDescLink'}}</a>{{/res}}
|
{{#res 'setDevicesYubiKeyToolsDesc2'}}<a href="{{ykmanInstallLink}}" target="_blank">{{res 'setDevicesYubiKeyToolsDescLink'}}</a>{{/res}}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<p>
|
||||||
{{#ifeq ykmanStatus 'checking'}}{{#res 'setDevicesYubiKeyToolsStatusChecking'}}<code>ykman</code>{{/res}}...{{/ifeq}}
|
{{#ifeq ykmanStatus 'checking'}}{{#res 'setDevicesYubiKeyToolsStatusChecking'}}<code>ykman</code>{{/res}}...{{/ifeq}}
|
||||||
{{#ifeq ykmanStatus 'ok'}}{{#res 'setDevicesYubiKeyToolsStatusOk'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
{{#ifeq ykmanStatus 'ok'}}{{#res 'setDevicesYubiKeyToolsStatusOk'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
||||||
{{#ifeq ykmanStatus 'error'}}{{#res 'setDevicesYubiKeyToolsStatusError'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
{{#ifeq ykmanStatus 'error'}}{{#res 'setDevicesYubiKeyToolsStatusError'}}<code>ykman</code>{{/res}}{{/ifeq}}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="settings__input input-base settings__yubikey-stuck-workaround" id="settings__yubikey-stuck-workaround"
|
||||||
|
{{#if yubiKeyStuckWorkaround}}checked{{/if}} />
|
||||||
|
<label for="settings__yubikey-stuck-workaround">{{res 'setDevicesYubiKeyStuckWorkaround'}}</label>
|
||||||
</div>
|
</div>
|
||||||
<h3>{{res 'setDevicesYubiKeyOtpTitle'}}</h3>
|
<h3>{{res 'setDevicesYubiKeyOtpTitle'}}</h3>
|
||||||
<p>{{res 'setDevicesYubiKeyOtpDesc'}}</p>
|
<p>{{res 'setDevicesYubiKeyOtpDesc'}}</p>
|
||||||
|
@ -36,11 +41,6 @@
|
||||||
{{#if yubiKeyAutoOpen}}checked{{/if}} />
|
{{#if yubiKeyAutoOpen}}checked{{/if}} />
|
||||||
<label for="settings__yubikey-auto-open">{{res 'setDevicesYubiKeyOtpAutoOpen'}}</label>
|
<label for="settings__yubikey-auto-open">{{res 'setDevicesYubiKeyOtpAutoOpen'}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<input type="checkbox" class="settings__input input-base settings__yubikey-oath-workaround" id="settings__yubikey-oath-workaround"
|
|
||||||
{{#if yubiKeyOathWorkaround}}checked{{/if}} />
|
|
||||||
<label for="settings__yubikey-oath-workaround">{{res 'setDevicesYubiKeyOathWorkaround'}}</label>
|
|
||||||
</div>
|
|
||||||
<h3>{{res 'setDevicesYubiKeyChalRespTitle'}}</h3>
|
<h3>{{res 'setDevicesYubiKeyChalRespTitle'}}</h3>
|
||||||
<p>{{res 'setDevicesYubiKeyChalRespDesc'}}</p>
|
<p>{{res 'setDevicesYubiKeyChalRespDesc'}}</p>
|
||||||
<div>
|
<div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user