From 8fc6f49fb4924426be7693ac843386007920d36e Mon Sep 17 00:00:00 2001 From: antelle Date: Mon, 26 Apr 2021 11:20:20 +0200 Subject: [PATCH] reworked unlocking files for auto-type and browser extension connection fix #1768 --- app/scripts/auto-type/index.js | 66 ++++++--------------------------- app/scripts/models/app-model.js | 49 ++++++++++++++++++++++++ app/scripts/views/app-view.js | 3 -- app/scripts/views/open-view.js | 5 +++ 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/app/scripts/auto-type/index.js b/app/scripts/auto-type/index.js index 6db5b5ae..dba86130 100644 --- a/app/scripts/auto-type/index.js +++ b/app/scripts/auto-type/index.js @@ -20,7 +20,6 @@ const AutoType = { enabled: !!(Launcher && Launcher.autoTypeSupported), supportsEventsWithWindowId: !!(Launcher && Launcher.platform() === 'linux'), selectEntryView: false, - pendingEvent: null, running: false, init() { @@ -28,10 +27,6 @@ const AutoType = { return; } Events.on('auto-type', (e) => this.handleEvent(e)); - Events.on('main-window-blur', (e) => this.mainWindowBlur(e)); - Events.on('main-window-focus', (e) => this.mainWindowFocus(e)); - Events.on('main-window-will-close', (e) => this.mainWindowWillClose(e)); - Events.on('closed-open-view', (e) => this.processPendingEvent(e)); }, handleEvent(e) { @@ -216,16 +211,24 @@ const AutoType = { }, selectEntryAndRun() { - this.getActiveWindowInfo((e, windowInfo) => { + this.getActiveWindowInfo(async (e, windowInfo) => { const filter = new AutoTypeFilter(windowInfo, AppModel.instance); const evt = { filter, windowInfo }; if (!AppModel.instance.files.hasOpenFiles()) { - this.pendingEvent = evt; logger.debug('auto-type event delayed'); this.focusMainWindow(); - } else { - this.processEventWithFilter(evt); + try { + await AppModel.instance.unlockAnyFile(); + } catch { + logger.debug('auto-type event canceled'); + return; + } + if (this.selectEntryView) { + this.selectEntryView.show(); + } } + logger.debug('processing auto-type event'); + this.processEventWithFilter(evt); }); }, @@ -264,51 +267,6 @@ const AutoType = { this.selectEntryView.hide(); Events.emit('open-file'); }); - }, - - mainWindowBlur() { - this.mainWindowBlurTimer = setTimeout(() => { - // macOS emits focus-blur-focus event in a row when triggering auto-type from minimized state - delete this.mainWindowBlurTimer; - this.resetPendingEvent(); - if (this.selectEntryView) { - this.selectEntryView.emit('result', undefined); - } - }, Timeouts.AutoTypeWindowFocusAfterBlur); - }, - - mainWindowFocus() { - if (this.mainWindowBlurTimer) { - clearTimeout(this.mainWindowBlurTimer); - this.mainWindowBlurTimer = null; - } - }, - - mainWindowWillClose() { - this.resetPendingEvent(); - if (this.selectEntryView) { - this.selectEntryView.emit('result', undefined); - } - }, - - resetPendingEvent() { - if (this.pendingEvent) { - this.pendingEvent = null; - logger.debug('auto-type event canceled'); - } - }, - - processPendingEvent() { - if (this.selectEntryView) { - this.selectEntryView.show(); - } - if (!this.pendingEvent) { - return; - } - logger.debug('processing pending auto-type event'); - const evt = this.pendingEvent; - this.pendingEvent = null; - this.processEventWithFilter(evt); } }; diff --git a/app/scripts/models/app-model.js b/app/scripts/models/app-model.js index c7288768..690411e5 100644 --- a/app/scripts/models/app-model.js +++ b/app/scripts/models/app-model.js @@ -17,6 +17,7 @@ import { MenuModel } from 'models/menu/menu-model'; import { PluginManager } from 'plugins/plugin-manager'; import { Features } from 'util/features'; import { DateFormat } from 'comp/i18n/date-format'; +import { Launcher } from 'comp/launcher'; import { UrlFormat } from 'util/formatting/url-format'; import { IdGenerator } from 'util/generators/id-generator'; import { Locale } from 'util/locale'; @@ -38,6 +39,8 @@ class AppModel { advancedSearch = null; attachedYubiKeysCount = 0; memoryPasswordStorage = {}; + fileUnlockPromise = null; + hardwareDecryptInProgress = false; constructor() { Events.on('refresh', this.refresh.bind(this)); @@ -48,6 +51,9 @@ class AppModel { Events.on('select-entry', this.selectEntry.bind(this)); Events.on('unset-keyfile', this.unsetKeyFile.bind(this)); Events.on('usb-devices-changed', this.usbDevicesChanged.bind(this)); + Events.on('main-window-blur', this.mainWindowBlur.bind(this)); + Events.on('hardware-decrypt-started', this.hardwareDecryptStarted.bind(this)); + Events.on('hardware-decrypt-finished', this.hardwareDecryptFinished.bind(this)); this.appLogger = new Logger('app'); AppModel.instance = this; @@ -167,11 +173,21 @@ class AppModel { page: 'file', file }); + this.refresh(); + file.on('reload', this.reloadFile.bind(this)); file.on('change', () => Events.emit('file-changed', file)); file.on('ejected', () => this.closeFile(file)); + Events.emit('file-opened'); + + if (this.fileUnlockPromise) { + this.appLogger.info('Running pending file unlock operation'); + this.fileUnlockPromise.resolve(file); + this.fileUnlockPromise = null; + } + return true; } @@ -1414,6 +1430,39 @@ class AppModel { this.memoryPasswordStorage = {}; } } + + unlockAnyFile() { + this.rejectPendingFileUnlockPromise('Replaced with a new operation'); + return new Promise((resolve, reject) => { + this.fileUnlockPromise = { resolve, reject }; + this.appLogger.info('Pending file unlock operation is set'); + }); + } + + rejectPendingFileUnlockPromise(reason) { + if (this.fileUnlockPromise) { + this.appLogger.info('Cancel pending file unlock operation', reason); + this.fileUnlockPromise.reject(new Error(reason)); + this.fileUnlockPromise = null; + } + } + + mainWindowBlur() { + if (!this.hardwareDecryptInProgress) { + this.rejectPendingFileUnlockPromise('Main window blur'); + } + } + + hardwareDecryptStarted() { + this.hardwareDecryptInProgress = true; + } + + hardwareDecryptFinished() { + this.hardwareDecryptInProgress = false; + if (!Launcher.isAppFocused()) { + this.rejectPendingFileUnlockPromise('App is not focused after hardware decrypt'); + } + } } export { AppModel }; diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 0eb2b561..133f41d8 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -171,9 +171,6 @@ class AppView extends View { this.views.open.on('close', () => { this.showEntries(); }); - this.views.open.on('remove', () => { - Events.emit('closed-open-view'); - }); } showLastOpenFile() { diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index 31eaaaf0..688e770b 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -680,13 +680,18 @@ class OpenView extends View { const encryptedPassword = kdbxweb.ProtectedValue.fromBase64( this.encryptedPassword.value ); + Events.emit('hardware-decrypt-started'); NativeModules.hardwareDecrypt(encryptedPassword, touchIdPrompt) .then((password) => { + Events.emit('hardware-decrypt-finished'); + this.params.password = password; this.params.encryptedPassword = this.encryptedPassword; this.model.openFile(this.params, (err) => this.openDbComplete(err)); }) .catch((err) => { + Events.emit('hardware-decrypt-finished'); + if (err.message.includes('User refused')) { err.userCanceled = true; } else if (err.message.includes('SecKeyCreateDecryptedData')) {