displaying matching otp codes in entries

This commit is contained in:
antelle 2020-05-11 17:24:06 +02:00
parent 16d303d966
commit c6ce694dfe
No known key found for this signature in database
GPG Key ID: 094A2F2D6136A4EE
15 changed files with 171 additions and 70 deletions

View File

@ -1,5 +1,5 @@
{
"plugins": ["prettier", "import"],
"plugins": ["prettier", "import", "babel"],
"extends": ["standard", "eslint:recommended", "plugin:prettier/recommended"],
"rules": {
"semi": ["error", "always"],
@ -15,7 +15,7 @@
"no-useless-escape": "off",
"no-var": "error",
"prefer-const": "error",
"no-unused-expressions": "error",
"no-unused-expressions": "off",
"strict": ["error", "never"],
"no-mixed-operators": "off",
"prefer-promise-reject-errors": "off",
@ -49,7 +49,8 @@
"import/no-relative-parent-imports": "error",
"import/first": "error",
"import/no-namespace": "error",
"import/no-default-export": "error"
"import/no-default-export": "error",
"babel/no-unused-expressions": "error"
},
"parserOptions": {
"sourceType": "module",

View File

@ -3,10 +3,8 @@ import kdbxweb from 'kdbxweb';
import { RuntimeInfo } from 'const/runtime-info';
import { Links } from 'const/links';
import { DateFormat } from 'util/formatting/date-format';
import { MdToHtml } from 'util/formatting/md-to-html';
import { StringFormat } from 'util/formatting/string-format';
import { Locale } from 'util/locale';
import { AppSettingsModel } from 'models/app-settings-model';
const Templates = {
db: require('templates/export/db.hbs'),

View File

@ -59,7 +59,7 @@ class View extends EventEmitter {
Tip.createTips(this.el);
this.debugLogger && this.debugLogger.debug('Render finished', this.debugLogger.ts(ts));
this.debugLogger?.debug('Render finished', this.debugLogger.ts(ts));
return this;
}
@ -129,7 +129,7 @@ class View extends EventEmitter {
eventsMap[event].push({ selector, method });
}
for (const [event, handlers] of Object.entries(eventsMap)) {
this.debugLogger && this.debugLogger.debug('Bind', 'view', event, handlers);
this.debugLogger?.debug('Bind', 'view', event, handlers);
const listener = e => this.eventListener(e, handlers);
this.eventListeners[event] = listener;
this.el.addEventListener(event, listener);
@ -151,8 +151,7 @@ class View extends EventEmitter {
this.unbindElementEvents();
for (const cfg of this.elementEventListeners) {
const els = this.el.querySelectorAll(cfg.selector);
this.debugLogger &&
this.debugLogger.debug('Bind', 'element', cfg.event, cfg.selector, els.length);
this.debugLogger?.debug('Bind', 'element', cfg.event, cfg.selector, els.length);
cfg.listener = e => this.eventListener(e, [cfg]);
for (const el of els) {
el.addEventListener(cfg.event, cfg.listener);
@ -174,7 +173,7 @@ class View extends EventEmitter {
}
eventListener(e, handlers) {
this.debugLogger && this.debugLogger.debug('Listener fired', e.type);
this.debugLogger?.debug('Listener fired', e.type);
for (const { selector, method } of handlers) {
if (selector) {
const closest = e.target.closest(selector);
@ -183,10 +182,10 @@ class View extends EventEmitter {
}
}
if (!this[method]) {
this.debugLogger && this.debugLogger.debug('Method not defined', method);
this.debugLogger?.debug('Method not defined', method);
continue;
}
this.debugLogger && this.debugLogger.debug('Handling event', e.type, method);
this.debugLogger?.debug('Handling event', e.type, method);
this[method](e);
}
}
@ -202,7 +201,7 @@ class View extends EventEmitter {
this.el.remove();
this.removed = true;
this.debugLogger && this.debugLogger.debug('Remove');
this.debugLogger?.debug('Remove');
}
removeInnerViews() {
@ -236,7 +235,7 @@ class View extends EventEmitter {
}
toggle(visible) {
this.debugLogger && this.debugLogger.debug(visible ? 'Show' : 'Hide');
this.debugLogger?.debug(visible ? 'Show' : 'Hide');
if (visible === undefined) {
visible = this.hidden;
}

View File

@ -302,9 +302,37 @@ class AppModel {
getEntriesByFilter(filter) {
const preparedFilter = this.prepareFilter(filter);
const entries = new SearchResultCollection();
this.files.forEach(file => {
file.forEachEntry(preparedFilter, entry => entries.push(entry));
});
const devicesToMatchOtpEntries = this.files.filter(file => file.external);
const matchedOtpEntrySet = this.settings.yubiKeyMatchEntries ? new Set() : undefined;
this.files
.filter(file => !file.external)
.forEach(file => {
file.forEachEntry(preparedFilter, entry => {
if (matchedOtpEntrySet) {
for (const device of devicesToMatchOtpEntries) {
const matchingEntry = device.getMatchingEntry(entry);
if (matchingEntry) {
matchedOtpEntrySet.add(matchingEntry);
}
}
}
entries.push(entry);
});
});
if (devicesToMatchOtpEntries.length) {
for (const device of devicesToMatchOtpEntries) {
device.forEachEntry(preparedFilter, entry => {
if (!matchedOtpEntrySet || !matchedOtpEntrySet.has(entry)) {
entries.push(entry);
}
});
}
}
return entries;
}
@ -382,14 +410,17 @@ class AppModel {
getEntryTemplates() {
const entryTemplates = [];
this.files.forEach(file => {
file.forEachEntryTemplate &&
file.forEachEntryTemplate(entry => {
entryTemplates.push({ file, entry });
});
file.forEachEntryTemplate?.(entry => {
entryTemplates.push({ file, entry });
});
});
return entryTemplates;
}
canCreateEntries() {
return this.files.some(f => f.active && !f.readOnly);
}
createNewEntry(args) {
const sel = this.getFirstSelectedGroupForCreation();
if (args && args.template) {
@ -1185,8 +1216,18 @@ class AppModel {
return device;
}
canCreateEntries() {
return this.files.some(f => f.active && !f.readOnly);
getMatchingOtpEntry(entry) {
if (!this.settings.yubiKeyMatchEntries) {
return null;
}
for (const file of this.files) {
if (file.external) {
const matchingEntry = file.getMatchingEntry(entry);
if (matchingEntry) {
return matchingEntry;
}
}
}
}
}

View File

@ -58,6 +58,7 @@ class EntryModel extends Model {
this.expires = entry.times.expires ? entry.times.expiryTime : undefined;
this.expired = entry.times.expires && entry.times.expiryTime <= new Date();
this.historyLength = entry.history.length;
this.titleUserLower = `${this.title}:${this.user}`.toLowerCase();
this._buildCustomIcon();
this._buildSearchText();
this._buildSearchTags();

View File

@ -4,6 +4,7 @@ import { ExternalEntryCollection } from 'collections/external-entry-collection';
class ExternalDeviceModel extends Model {
entries = new ExternalEntryCollection();
groups = [];
entryMap = {};
close() {}
@ -17,6 +18,20 @@ class ExternalDeviceModel extends Model {
}
}
}
entryId(title, user) {
return `${title}:${user}`.toLowerCase();
}
getMatchingEntry(entry) {
return this.entryMap[this.entryId(entry.title, entry.user)];
}
_buildEntryMap() {
for (const entry of this.entries) {
this.entryMap[entry.id.toLowerCase()] = entry;
}
}
}
ExternalDeviceModel.defineModelProperties({
@ -28,7 +43,8 @@ ExternalDeviceModel.defineModelProperties({
groups: undefined,
name: undefined,
shortName: undefined,
deviceClassName: undefined
deviceClassName: undefined,
entryMap: undefined
});
export { ExternalDeviceModel };

View File

@ -100,6 +100,9 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
if (yubiKeys && yubiKeys.length) {
openNextYubiKey();
} else {
if (openSuccess) {
this._openComplete();
}
callback(openSuccess ? null : openErrors[0]);
}
});
@ -135,7 +138,7 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
this.entries.push(
new ExternalOtpEntryModel({
id: title + ':' + user,
id: this.entryId(title, user),
device: this,
deviceSubId: serial,
icon: 'clock-o',
@ -145,8 +148,6 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
})
);
}
this.active = true;
Events.on('usb-devices-changed', this.onUsbDevicesChanged);
callback();
}
});
@ -187,6 +188,12 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
});
}
_openComplete() {
this.active = true;
this._buildEntryMap();
Events.on('usb-devices-changed', this.onUsbDevicesChanged);
}
cancelOpen() {
logger.info('Cancel open');
Events.off('usb-devices-changed', this.onUsbDevicesChanged);

View File

@ -468,11 +468,11 @@ class StorageBase {
if (token && token.error) {
return callback && callback('OAuth code exchange error: ' + token.error);
}
callback && callback();
callback?.();
},
error: err => {
this.logger.error('Error exchanging OAuth code', err);
callback && callback('OAuth code exchange error: ' + err);
callback?.('OAuth code exchange error: ' + err);
}
});
}
@ -507,7 +507,7 @@ class StorageBase {
this._oauthToken = null;
}
this.logger.error('Error exchanging refresh token', err);
callback && callback('Error exchanging refresh token');
callback?.('Error exchanging refresh token');
}
});
}

View File

@ -16,6 +16,7 @@ import { FieldViewReadOnlyWithOptions } from 'views/fields/field-view-read-only-
function createDetailsFields(detailsView) {
const model = detailsView.model;
const otpEntry = detailsView.matchingOtpEntry;
const fieldViews = [];
const fieldViewsAside = [];
@ -185,18 +186,35 @@ function createDetailsFields(detailsView) {
}
})
);
if (otpEntry) {
fieldViews.push(
new FieldViewOtp({
name: '$otp',
title: Locale.detOtpField,
value() {
return otpEntry.otpGenerator;
},
sequence: '{TOTP}',
readonly: true,
needsTouch: otpEntry.needsTouch,
deviceShortName: otpEntry.device.shortName
})
);
}
for (const field of Object.keys(model.fields)) {
if (field === 'otp' && model.otpGenerator) {
fieldViews.push(
FieldViewOtp({
name: '$' + field,
title: field,
value() {
return model.otpGenerator;
},
sequence: '{TOTP}'
})
);
if (!otpEntry) {
fieldViews.push(
FieldViewOtp({
name: '$' + field,
title: field,
value() {
return model.otpGenerator;
},
sequence: '{TOTP}'
})
);
}
} else {
fieldViews.push(
new FieldViewCustom({

View File

@ -120,7 +120,6 @@ class DetailsView extends View {
this.template = template;
super.render(model);
this.setSelectedColor(this.model.color);
this.model.initOtpGenerator();
this.addFieldViews();
this.createScroll({
root: this.$el.find('.details__body')[0],
@ -437,12 +436,25 @@ class DetailsView extends View {
showEntry(entry) {
this.model = entry;
this.initOtp();
this.render();
if (entry && !entry.title && entry.isJustCreated) {
this.editTitle();
}
}
initOtp() {
this.matchingOtpEntry = null;
if (!this.model || this.model.external) {
return;
}
this.matchingOtpEntry = this.appModel.getMatchingOtpEntry(this.model);
this.model.initOtpGenerator();
this.matchingOtpEntry?.initOtpGenerator();
}
copyKeyPress(editView) {
if (!editView || this.isHidden()) {
return false;
@ -948,7 +960,8 @@ class DetailsView extends View {
autoType(sequence) {
const entry = this.model;
if (entry.external && (!sequence || sequence.includes('{TOTP}'))) {
const hasOtp = sequence?.includes('{TOTP}') || (entry.external && !sequence);
if (hasOtp) {
const otpField = this.getFieldView('$otp');
otpField.refreshOtp(err => {
if (!err) {

View File

@ -3,7 +3,6 @@ import { View } from 'framework/views/view';
import { Scrollable } from 'framework/views/scrollable';
import template from 'templates/import-csv.hbs';
import { EntryModel } from 'models/entry-model';
import { escape } from 'util/fn';
class ImportCsvView extends View {
parent = '.app__body';

View File

@ -1,9 +1,10 @@
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 template from 'templates/settings/settings-devices.hbs';
import { Links } from 'const/links';
import { UsbListener } from 'comp/app/usb-listener';
import template from 'templates/settings/settings-devices.hbs';
class SettingsDevicesView extends View {
template = template;
@ -61,6 +62,7 @@ class SettingsDevicesView extends View {
changeYubiKeyMatchEntries(e) {
AppSettingsModel.yubiKeyMatchEntries = e.target.checked;
this.render();
Events.emit('refresh');
}
changeYubiKeyShowChalResp(e) {

View File

@ -20,7 +20,7 @@ if (!gotTheLock) {
app.quit();
}
perfTimestamps && perfTimestamps.push({ name: 'single instance lock', ts: process.hrtime() });
perfTimestamps?.push({ name: 'single instance lock', ts: process.hrtime() });
let openFile = process.argv.filter(arg => /\.kdbx$/i.test(arg))[0];
const userDataDir =
@ -52,7 +52,7 @@ const themeBgColors = {
};
const defaultBgColor = '#282C34';
perfTimestamps && perfTimestamps.push({ name: 'defining args', ts: process.hrtime() });
perfTimestamps?.push({ name: 'defining args', ts: process.hrtime() });
setDevAppIcon();
setEnv();
@ -71,7 +71,7 @@ app.on('window-all-closed', () => {
}
});
app.on('ready', () => {
perfTimestamps && perfTimestamps.push({ name: 'app on ready', ts: process.hrtime() });
perfTimestamps?.push({ name: 'app on ready', ts: process.hrtime() });
appReady = true;
setAppOptions();
setSystemAppearance();
@ -155,7 +155,7 @@ app.setGlobalShortcuts = setGlobalShortcuts;
function setAppOptions() {
app.commandLine.appendSwitch('disable-background-timer-throttling');
perfTimestamps && perfTimestamps.push({ name: 'setting app options', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting app options', ts: process.hrtime() });
}
function readAppSettings() {
@ -164,8 +164,7 @@ function readAppSettings() {
} catch (e) {
return null;
} finally {
perfTimestamps &&
perfTimestamps.push({ name: 'reading app settings', ts: process.hrtime() });
perfTimestamps?.push({ name: 'reading app settings', ts: process.hrtime() });
}
}
@ -175,8 +174,7 @@ function setSystemAppearance() {
electron.systemPreferences.appLevelAppearance = 'dark';
}
}
perfTimestamps &&
perfTimestamps.push({ name: 'setting system appearance', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting system appearance', ts: process.hrtime() });
}
function createMainWindow() {
@ -198,17 +196,17 @@ function createMainWindow() {
windowOptions.icon = path.join(__dirname, 'icon.png');
}
mainWindow = new electron.BrowserWindow(windowOptions);
perfTimestamps && perfTimestamps.push({ name: 'creating main window', ts: process.hrtime() });
perfTimestamps?.push({ name: 'creating main window', ts: process.hrtime() });
setMenu();
perfTimestamps && perfTimestamps.push({ name: 'setting menu', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting menu', ts: process.hrtime() });
mainWindow.loadURL(htmlPath);
if (showDevToolsOnStart) {
mainWindow.openDevTools({ mode: 'bottom' });
}
mainWindow.once('ready-to-show', () => {
perfTimestamps && perfTimestamps.push({ name: 'main window ready', ts: process.hrtime() });
perfTimestamps?.push({ name: 'main window ready', ts: process.hrtime() });
if (startMinimized) {
emitRemoteEvent('launcher-started-minimized');
} else {
@ -216,7 +214,7 @@ function createMainWindow() {
}
ready = true;
notifyOpenFile();
perfTimestamps && perfTimestamps.push({ name: 'main window shown', ts: process.hrtime() });
perfTimestamps?.push({ name: 'main window shown', ts: process.hrtime() });
reportStartProfile();
});
mainWindow.webContents.on('context-menu', onContextMenu);
@ -242,12 +240,10 @@ function createMainWindow() {
mainWindow.on('session-end', () => {
emitRemoteEvent('os-lock');
});
perfTimestamps &&
perfTimestamps.push({ name: 'configuring main window', ts: process.hrtime() });
perfTimestamps?.push({ name: 'configuring main window', ts: process.hrtime() });
restoreMainWindowPosition();
perfTimestamps &&
perfTimestamps.push({ name: 'restoring main window position', ts: process.hrtime() });
perfTimestamps?.push({ name: 'restoring main window position', ts: process.hrtime() });
}
function restoreMainWindow() {
@ -464,8 +460,7 @@ function setGlobalShortcuts(appSettings) {
} catch (e) {}
}
}
perfTimestamps &&
perfTimestamps.push({ name: 'setting global shortcuts', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting global shortcuts', ts: process.hrtime() });
}
function subscribePowerEvents() {
@ -478,8 +473,7 @@ function subscribePowerEvents() {
electron.powerMonitor.on('lock-screen', () => {
emitRemoteEvent('os-lock');
});
perfTimestamps &&
perfTimestamps.push({ name: 'subscribing to power events', ts: process.hrtime() });
perfTimestamps?.push({ name: 'subscribing to power events', ts: process.hrtime() });
}
function setEnv() {
@ -491,7 +485,7 @@ function setEnv() {
// https://github.com/electron/electron/issues/9046
process.env.XDG_CURRENT_DESKTOP = 'Unity';
}
perfTimestamps && perfTimestamps.push({ name: 'setting env', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting env', ts: process.hrtime() });
}
function restorePreferences() {
@ -524,7 +518,7 @@ function restorePreferences() {
}
}
perfTimestamps && perfTimestamps.push({ name: 'restoring preferences', ts: process.hrtime() });
perfTimestamps?.push({ name: 'restoring preferences', ts: process.hrtime() });
}
function deleteOldTempFiles() {
@ -541,8 +535,7 @@ function deleteOldTempFiles() {
}
app.oldTempFilesDeleted = true; // this is added to prevent file deletion on restart
}, 1000);
perfTimestamps &&
perfTimestamps.push({ name: 'deleting old temp files', ts: process.hrtime() });
perfTimestamps?.push({ name: 'deleting old temp files', ts: process.hrtime() });
}
function deleteRecursive(dir) {
@ -577,8 +570,7 @@ function hookRequestHeaders() {
}
callback({ requestHeaders: details.requestHeaders });
});
perfTimestamps &&
perfTimestamps.push({ name: 'setting request handlers', ts: process.hrtime() });
perfTimestamps?.push({ name: 'setting request handlers', ts: process.hrtime() });
}
// If a display is disconnected while KeeWeb is minimized, Electron does not

13
package-lock.json generated
View File

@ -5519,6 +5519,14 @@
}
}
},
"eslint-plugin-babel": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.0.tgz",
"integrity": "sha512-HPuNzSPE75O+SnxHIafbW5QB45r2w78fxqwK3HmjqIUoPfPzVrq6rD+CINU3yzoDSzEhUkX07VUphbF73Lth/w==",
"requires": {
"eslint-rule-composer": "^0.3.0"
}
},
"eslint-plugin-es": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz",
@ -5637,6 +5645,11 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz",
"integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ=="
},
"eslint-rule-composer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
"integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg=="
},
"eslint-scope": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",

View File

@ -34,6 +34,7 @@
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.3",