diff --git a/app/scripts/comp/app/yubikey.js b/app/scripts/comp/app/yubikey.js index 1bb8bfd6..c7867151 100644 --- a/app/scripts/comp/app/yubikey.js +++ b/app/scripts/comp/app/yubikey.js @@ -80,9 +80,13 @@ const YubiKey = { const yubiKeysIncludingEmpty = stdout .trim() .split(/\n/g) - .map(line => (line.match(/\d{5,}$/g) || [])[0]); + .map(line => { + const fullName = line; + const serial = (line.match(/\d{5,}$/g) || [])[0]; + return { fullName, serial }; + }); - const yubiKeys = yubiKeysIncludingEmpty.filter(s => s); + const yubiKeys = yubiKeysIncludingEmpty.filter(s => s.serial); if ( yubiKeysIncludingEmpty.length === 1 && diff --git a/app/scripts/locales/base.json b/app/scripts/locales/base.json index e3d7f597..7aa641c9 100644 --- a/app/scripts/locales/base.json +++ b/app/scripts/locales/base.json @@ -233,6 +233,12 @@ "openListErrorBody": "There was an error loading file list", "openShowAllFiles": "Show all files", "openFileNoCacheError": "File not found in the cache storage. This can happen because browser storage was cleaned up. To open the file, remove it from KeeWeb and add it again.", + "openChalRespHeader": "Challenge-Response", + "openChalRespLoading": "Loading the list of YubiKeys", + "openChalRespSelectYubiKey": "Select a YubiKey that you would like to use", + "openChalRespErrorNotInstalled": "YubiKey integration is not configured, please go to settings to set it up", + "openChalRespErrorEmpty": "No YubiKeys found", + "openChalRespSlot": "slot", "detAttDownload": "Shift-click the attachment button to download it or", "detAttDelToRemove": "Delete to remove", diff --git a/app/scripts/models/external/yubikey-otp-model.js b/app/scripts/models/external/yubikey-otp-model.js index c8b0ff1b..209f01b0 100644 --- a/app/scripts/models/external/yubikey-otp-model.js +++ b/app/scripts/models/external/yubikey-otp-model.js @@ -35,7 +35,7 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel { const openErrors = []; const openNextYubiKey = () => { const yubiKey = yubiKeys.shift(); - this._addYubiKey(yubiKey, err => { + this._addYubiKey(yubiKey.serial, err => { if (YubiKey.aborted) { return callback('Aborted'); } diff --git a/app/scripts/views/open-chal-resp-view.js b/app/scripts/views/open-chal-resp-view.js new file mode 100644 index 00000000..5b669831 --- /dev/null +++ b/app/scripts/views/open-chal-resp-view.js @@ -0,0 +1,65 @@ +import { View } from 'framework/views/view'; +import { YubiKey } from 'comp/app/yubikey'; +import { Locale } from 'util/locale'; +import template from 'templates/open-chal-resp.hbs'; + +class OpenChalRespView extends View { + template = template; + + events = { + 'click .open-chal-resp__item': 'itemClick' + }; + + constructor() { + super(); + + YubiKey.checkToolStatus().then(status => { + if (this.removed) { + return; + } + if (status === 'ok') { + YubiKey.list((err, yubiKeys) => { + this.error = err; + this.yubiKeys = []; + for (const { fullName, serial } of yubiKeys) { + for (const slot of [1, 2]) { + this.yubiKeys.push({ + fullName, + serial, + slot + }); + } + } + this.render(); + }); + } else { + this.render(); + } + }); + } + + render() { + let error = this.error; + + if (YubiKey.ykmanStatus === 'error') { + error = Locale.openChalRespErrorNotInstalled.replace('{}', 'ykman'); + } + if (this.yubiKeys && !this.yubiKeys.length) { + error = Locale.openChalRespErrorEmpty; + } + + super.render({ + error, + yubiKeys: this.yubiKeys, + loading: !YubiKey.ykmanStatus || YubiKey.ykmanStatus === 'checking' + }); + } + + itemClick(e) { + const el = e.target.closest('[data-serial]'); + const { serial, slot } = el.dataset; + this.emit('select', { serial, slot }); + } +} + +export { OpenChalRespView }; diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index aea34b20..11aef649 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -19,6 +19,7 @@ import { Logger } from 'util/logger'; import { InputFx } from 'util/ui/input-fx'; import { OpenConfigView } from 'views/open-config-view'; import { StorageFileListView } from 'views/storage-file-list-view'; +import { OpenChalRespView } from 'views/open-chal-resp-view'; import { omit } from 'util/fn'; import { GeneratorView } from 'views/generator-view'; import template from 'templates/open.hbs'; @@ -1043,16 +1044,19 @@ class OpenView extends View { 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; + const chalRespView = new OpenChalRespView(); + chalRespView.on('select', e => { + // console.log('e', e.serial, e.slot); + }); - Alerts.notImplemented(); + Alerts.alert({ + header: Locale.openChalRespHeader, + icon: 'exchange', + buttons: [{ result: '', title: Locale.alertCancel }], + esc: '', + click: '', + view: chalRespView }); } } diff --git a/app/styles/areas/_open.scss b/app/styles/areas/_open.scss index 3f62bdc0..b30f79f3 100644 --- a/app/styles/areas/_open.scss +++ b/app/styles/areas/_open.scss @@ -388,3 +388,19 @@ display: inline-block; } } + +.open-chal-resp { + &__head { + padding: $base-padding; + } + &__icon { + margin-right: $small-spacing; + } + &__item { + padding: $base-padding; + cursor: pointer; + &:hover { + background-color: var(--action-background-color-focus-tr); + } + } +} diff --git a/app/templates/open-chal-resp.hbs b/app/templates/open-chal-resp.hbs new file mode 100644 index 00000000..43eaea75 --- /dev/null +++ b/app/templates/open-chal-resp.hbs @@ -0,0 +1,20 @@ +
+ {{#if loading}} +
+ {{res 'openChalRespLoading'}} +
+ {{else if error}} +
+ {{error}} +
+ {{else}} +
{{res 'openChalRespSelectYubiKey'}}:
+
+ {{#each yubiKeys as |yk|}} +
+ {{yk.fullName}}, {{res 'openChalRespSlot'}} {{yk.slot}} +
+ {{/each}} +
+ {{/if}} +