mirror of https://github.com/keeweb/keeweb
extracted YubiKey module
parent
bf7dec2437
commit
81d4c5c403
|
@ -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
|
||||
yubiKeyMatchEntries: true, // show matching one-time codes in entries
|
||||
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
|
||||
canOpenDemo: true, // can open a demo file
|
||||
|
|
|
@ -604,7 +604,7 @@
|
|||
"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.",
|
||||
"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",
|
||||
"setAboutBuilt": "This app is built with these awesome tools",
|
||||
|
|
|
@ -4,11 +4,7 @@ import { ExternalOtpEntryModel } from 'models/external/external-otp-entry-model'
|
|||
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';
|
||||
|
||||
let ykmanStatus;
|
||||
import { YubiKey } from 'comp/app/yubikey';
|
||||
|
||||
const logger = new Logger('yubikey');
|
||||
|
||||
|
@ -23,10 +19,6 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
});
|
||||
}
|
||||
|
||||
static get ykmanStatus() {
|
||||
return ykmanStatus;
|
||||
}
|
||||
|
||||
onUsbDevicesChanged = () => {
|
||||
if (UsbListener.attachedYubiKeys.length === 0) {
|
||||
this.emit('ejected');
|
||||
|
@ -34,157 +26,61 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
};
|
||||
|
||||
open(callback) {
|
||||
this._open(callback, true);
|
||||
}
|
||||
|
||||
_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();
|
||||
YubiKey.list((err, yubiKeys) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.info('Adding YubiKey', serial);
|
||||
|
||||
this.openProcess = Launcher.spawn({
|
||||
cmd: 'ykman',
|
||||
args: ['-d', serial, 'oath', 'code'],
|
||||
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();
|
||||
YubiKey.getOtpCodes(serial, (err, codes) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_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.openAborted = false;
|
||||
setTimeout(() => {
|
||||
this._open(callback, false);
|
||||
}, Timeouts.ExternalDeviceAfterReconnect);
|
||||
for (const code of codes) {
|
||||
this.entries.push(
|
||||
new ExternalOtpEntryModel({
|
||||
id: this.entryId(code.title, code.user),
|
||||
device: this,
|
||||
deviceSubId: serial,
|
||||
icon: 'clock-o',
|
||||
title: code.title,
|
||||
user: code.user,
|
||||
needsTouch: code.needsTouch
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
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(err);
|
||||
}
|
||||
openTimeout = setTimeout(() => {
|
||||
Events.off('usb-devices-changed', onDevicesChangedDuringRepair);
|
||||
}, Timeouts.ExternalDeviceReconnect);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -195,38 +91,15 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
}
|
||||
|
||||
cancelOpen() {
|
||||
logger.info('Cancel open');
|
||||
YubiKey.abort();
|
||||
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) {
|
||||
const msPeriod = 30000;
|
||||
const timeLeft = msPeriod - (Date.now() % msPeriod) + 500;
|
||||
return Launcher.spawn({
|
||||
cmd: 'ykman',
|
||||
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);
|
||||
}
|
||||
YubiKey.getOtp(entry.deviceSubId, `${entry.title}:${entry.user}`, (err, otp) => {
|
||||
callback(err, otp, timeLeft);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -242,34 +115,10 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
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({
|
||||
onUsbDevicesChanged: null,
|
||||
openProcess: null,
|
||||
openAborted: false
|
||||
onUsbDevicesChanged: null
|
||||
});
|
||||
|
||||
export { YubiKeyOtpModel };
|
||||
|
|
|
@ -9,7 +9,7 @@ import { SecureInput } from 'comp/browser/secure-input';
|
|||
import { Launcher } from 'comp/launcher';
|
||||
import { Alerts } from 'comp/ui/alerts';
|
||||
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 { Comparators } from 'util/data/comparators';
|
||||
import { Features } from 'util/features';
|
||||
|
@ -47,7 +47,7 @@ class OpenView extends View {
|
|||
'keypress .open__pass-input': 'inputKeypress',
|
||||
'click .open__pass-enter-btn': 'openDb',
|
||||
'click .open__settings-key-file': 'openKeyFile',
|
||||
'click .open__settings-yubikey': 'selectYubiKey',
|
||||
'click .open__settings-yubikey': 'selectYubiKeyChalResp',
|
||||
'click .open__last-item': 'openLast',
|
||||
'click .open__icon-generate': 'toggleGenerator',
|
||||
dragover: 'dragover',
|
||||
|
@ -445,10 +445,6 @@ class OpenView extends View {
|
|||
this.showOpenFileInfo(fileInfo, true);
|
||||
}
|
||||
|
||||
selectYubiKey() {
|
||||
Alerts.notImplemented();
|
||||
}
|
||||
|
||||
removeFile(id) {
|
||||
this.model.removeFileInfo(id);
|
||||
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');
|
||||
icon.toggleClass('flip3d', true);
|
||||
|
||||
YubiKeyOtpModel.checkToolStatus().then(() => {
|
||||
if (YubiKeyOtpModel.ykmanStatus !== 'ok') {
|
||||
YubiKey.checkToolStatus().then(status => {
|
||||
if (status !== 'ok') {
|
||||
icon.toggleClass('flip3d', false);
|
||||
this.inputEl.removeAttr('disabled');
|
||||
this.busy = false;
|
||||
return Events.emit('toggle-settings', 'devices');
|
||||
}
|
||||
this.otpDevice = this.model.openOtpDevice(err => {
|
||||
if (err && !this.otpDevice.openAborted) {
|
||||
if (err && !YubiKey.aborted) {
|
||||
Alerts.error({
|
||||
header: Locale.openError,
|
||||
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 };
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Events } from 'framework/events';
|
||||
import { View } from 'framework/views/view';
|
||||
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 { UsbListener } from 'comp/app/usb-listener';
|
||||
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-match-entries': 'changeYubiKeyMatchEntries',
|
||||
'change .settings__yubikey-chalresp-show': 'changeYubiKeyShowChalResp',
|
||||
'change .settings__yubikey-oath-workaround': 'changeYubiKeyOathWorkaround'
|
||||
'change .settings__yubikey-stuck-workaround': 'changeYubiKeyStuckWorkaround'
|
||||
};
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
if (!['ok', 'checking'].includes(YubiKeyOtpModel.ykmanStatus)) {
|
||||
this.toolCheckPromise = YubiKeyOtpModel.checkToolStatus();
|
||||
if (!['ok', 'checking'].includes(YubiKey.ykmanStatus)) {
|
||||
this.toolCheckPromise = YubiKey.checkToolStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,12 +33,12 @@ class SettingsDevicesView extends View {
|
|||
super.render({
|
||||
supported: UsbListener.supported,
|
||||
enableUsb: UsbListener.supported && AppSettingsModel.enableUsb,
|
||||
ykmanStatus: YubiKeyOtpModel.ykmanStatus,
|
||||
ykmanStatus: YubiKey.ykmanStatus,
|
||||
yubiKeyShowIcon: AppSettingsModel.yubiKeyShowIcon,
|
||||
yubiKeyAutoOpen: AppSettingsModel.yubiKeyAutoOpen,
|
||||
yubiKeyMatchEntries: AppSettingsModel.yubiKeyMatchEntries,
|
||||
yubiKeyShowChalResp: AppSettingsModel.yubiKeyShowChalResp,
|
||||
yubiKeyOathWorkaround: AppSettingsModel.yubiKeyOathWorkaround,
|
||||
yubiKeyStuckWorkaround: AppSettingsModel.yubiKeyStuckWorkaround,
|
||||
yubiKeyManualLink: Links.YubiKeyManual,
|
||||
ykmanInstallLink: Links.YubiKeyManagerInstall
|
||||
});
|
||||
|
@ -70,8 +70,8 @@ class SettingsDevicesView extends View {
|
|||
this.render();
|
||||
}
|
||||
|
||||
changeYubiKeyOathWorkaround(e) {
|
||||
AppSettingsModel.yubiKeyOathWorkaround = e.target.checked;
|
||||
changeYubiKeyStuckWorkaround(e) {
|
||||
AppSettingsModel.yubiKeyStuckWorkaround = e.target.checked;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,15 @@
|
|||
{{#res 'setDevicesYubiKeyToolsDesc'}}<code>ykman</code>{{/res}}
|
||||
{{#res 'setDevicesYubiKeyToolsDesc2'}}<a href="{{ykmanInstallLink}}" target="_blank">{{res 'setDevicesYubiKeyToolsDescLink'}}</a>{{/res}}
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
{{#ifeq ykmanStatus 'checking'}}{{#res 'setDevicesYubiKeyToolsStatusChecking'}}<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}}
|
||||
</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>
|
||||
<h3>{{res 'setDevicesYubiKeyOtpTitle'}}</h3>
|
||||
<p>{{res 'setDevicesYubiKeyOtpDesc'}}</p>
|
||||
|
@ -36,11 +41,6 @@
|
|||
{{#if yubiKeyAutoOpen}}checked{{/if}} />
|
||||
<label for="settings__yubikey-auto-open">{{res 'setDevicesYubiKeyOtpAutoOpen'}}</label>
|
||||
</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>
|
||||
<p>{{res 'setDevicesYubiKeyChalRespDesc'}}</p>
|
||||
<div>
|
||||
|
|
Loading…
Reference in New Issue