fix #1243: auto-type any field

This commit is contained in:
antelle 2019-09-12 19:59:35 +02:00
parent 9e5d6403fa
commit 79dd95facd
13 changed files with 258 additions and 55 deletions

View File

@ -9,7 +9,6 @@ const Logger = require('../util/logger');
const Locale = require('../util/locale');
const Timeouts = require('../const/timeouts');
const AppSettingsModel = require('../models/app-settings-model');
const AutoTypeSequenceType = require('../const/autotype-sequencetype');
const logger = new Logger('auto-type');
const clearTextAutoTypeLog = localStorage.autoTypeDebug;
@ -73,14 +72,7 @@ const AutoType = {
run(result, callback) {
this.running = true;
let sequence;
if (result.sequenceType === AutoTypeSequenceType.PASSWORD) {
sequence = '{PASSWORD}';
} else if (result.sequenceType === AutoTypeSequenceType.USERNAME) {
sequence = '{USERNAME}';
} else {
sequence = result.entry.getEffectiveAutoTypeSeq();
}
const sequence = result.sequence || result.entry.getEffectiveAutoTypeSeq();
logger.debug('Start', sequence);
const ts = logger.ts();
try {

View File

@ -7,6 +7,7 @@ const shortcutKeyProp = navigator.platform.indexOf('Mac') >= 0 ? 'metaKey' : 'ct
const KeyHandler = {
SHORTCUT_ACTION: 1,
SHORTCUT_OPT: 2,
SHORTCUT_SHIFT: 3,
shortcuts: {},
modal: false,
@ -57,7 +58,7 @@ const KeyHandler = {
const keyShortcuts = this.shortcuts[code];
if (keyShortcuts && keyShortcuts.length) {
for (const sh of keyShortcuts) {
if (this.modal && !sh.modal) {
if (this.modal && sh.modal !== this.modal) {
e.stopPropagation();
continue;
}
@ -73,13 +74,18 @@ const KeyHandler = {
continue;
}
break;
case this.SHORTCUT_SHIFT:
if (!e.shiftKey) {
continue;
}
break;
case this.SHORTCUT_ACTION + this.SHORTCUT_OPT:
if (!e.altKey || !isActionKey) {
continue;
}
break;
default:
if (e.metaKey || e.ctrlKey || e.altKey) {
if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) {
continue;
}
break;

View File

@ -1,7 +0,0 @@
const AutoTypeSequenceType = {
DEFAULT: 0,
USERNAME: 1,
PASSWORD: 2
};
module.exports = AutoTypeSequenceType;

View File

@ -318,6 +318,8 @@
"autoTypeSelectionHint": "Type the autotype sequence",
"autoTypeSelectionHintAction": "Only type the password",
"autoTypeSelectionHintOpt": "Only type the username",
"autoTypeSelectionHintShift": "Other fields",
"autoTypeSelectionOtp": "One-time password",
"appSecWarn": "Not Secure!",
"appSecWarnBody1": "You have loaded this app with an insecure connection. Someone may be watching you and stealing your passwords. We strongly advise you to stop, unless you clearly understand what you're doing.",

View File

@ -23,6 +23,9 @@ const FeatureDetector = {
altShortcutSymbol(formatting) {
return this.isMac ? '⌥' : formatting ? '<span class="thin">alt + </span>' : 'alt-';
},
shiftShortcutSymbol(formatting) {
return this.isMac ? '⇧' : formatting ? '<span class="thin">shift + </span>' : 'shift-';
},
globalShortcutSymbol(formatting) {
return this.isMac
? '⌃⌥'

View File

@ -5,8 +5,9 @@ const Locale = require('../../util/locale');
const AppSettingsModel = require('../../models/app-settings-model');
const EntryPresenter = require('../../presenters/entry-presenter');
const Scrollable = require('../../mixins/scrollable');
const AutoTypeSequenceType = require('../../const/autotype-sequencetype');
const FeatureDetector = require('../../util/feature-detector');
const DropdownView = require('../dropdown-view');
const Format = require('../../util/format');
const AutoTypePopupView = Backbone.View.extend({
el: 'body',
@ -16,13 +17,15 @@ const AutoTypePopupView = Backbone.View.extend({
events: {
'click .at-select__header-filter-clear': 'clearFilterText',
'click .at-select__item': 'itemClicked'
'click .at-select__item': 'itemClicked',
'contextmenu .at-select__item': 'itemRightClicked'
},
result: null,
entries: null,
initialize() {
this.views = {};
this.initScroll();
this.listenTo(Backbone, 'main-window-blur', this.mainWindowBlur);
this.listenTo(Backbone, 'main-window-will-close', this.mainWindowWillClose);
@ -30,31 +33,38 @@ const AutoTypePopupView = Backbone.View.extend({
},
setupKeys() {
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, 'auto-type');
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, 'auto-type');
KeyHandler.onKey(
Keys.DOM_VK_RETURN,
this.actionEnterPressed,
this,
KeyHandler.SHORTCUT_ACTION,
true
'auto-type'
);
KeyHandler.onKey(
Keys.DOM_VK_RETURN,
this.optEnterPressed,
this,
KeyHandler.SHORTCUT_OPT,
true
'auto-type'
);
KeyHandler.onKey(Keys.DOM_VK_UP, this.upPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_DOWN, this.downPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_BACK_SPACE, this.backSpacePressed, this, false, true);
KeyHandler.onKey(
Keys.DOM_VK_RETURN,
this.shiftEnterPressed,
this,
KeyHandler.SHORTCUT_SHIFT,
'auto-type'
);
KeyHandler.onKey(Keys.DOM_VK_UP, this.upPressed, this, false, 'auto-type');
KeyHandler.onKey(Keys.DOM_VK_DOWN, this.downPressed, this, false, 'auto-type');
KeyHandler.onKey(Keys.DOM_VK_BACK_SPACE, this.backSpacePressed, this, false, 'auto-type');
KeyHandler.onKey(
Keys.DOM_VK_O,
this.openKeyPressed,
this,
KeyHandler.SHORTCUT_ACTION,
true
'auto-type'
);
KeyHandler.on('keypress:auto-type', this.keyPressed.bind(this));
KeyHandler.setModal('auto-type');
@ -65,6 +75,7 @@ const AutoTypePopupView = Backbone.View.extend({
KeyHandler.offKey(Keys.DOM_VK_RETURN, this.enterPressed, this);
KeyHandler.offKey(Keys.DOM_VK_RETURN, this.actionEnterPressed, this);
KeyHandler.offKey(Keys.DOM_VK_RETURN, this.optEnterPressed, this);
KeyHandler.offKey(Keys.DOM_VK_RETURN, this.shiftEnterPressed, this);
KeyHandler.offKey(Keys.DOM_VK_UP, this.upPressed, this);
KeyHandler.offKey(Keys.DOM_VK_DOWN, this.downPressed, this);
KeyHandler.offKey(Keys.DOM_VK_BACK_SPACE, this.backSpacePressed, this);
@ -96,12 +107,10 @@ const AutoTypePopupView = Backbone.View.extend({
this.renderTemplate({
filterText: this.model.filter.text,
topMessage,
selectionHintDefault: Locale.autoTypeSelectionHint,
selectionHintAction: Locale.autoTypeSelectionHintAction,
selectionHintOpt: Locale.autoTypeSelectionHintOpt,
itemsHtml,
actionSymbol: FeatureDetector.actionShortcutSymbol(true),
altSymbol: FeatureDetector.altShortcutSymbol(true),
shiftSymbol: FeatureDetector.shiftShortcutSymbol(true),
keyEnter: Locale.keyEnter
});
document.activeElement.blur();
@ -123,13 +132,10 @@ const AutoTypePopupView = Backbone.View.extend({
this.trigger('result', this.result);
},
closeWithResult(sequenceType) {
if (!sequenceType) {
sequenceType = AutoTypeSequenceType.DEFAULT;
}
closeWithResult(sequence) {
this.trigger('result', {
entry: this.result,
sequenceType
sequence
});
},
@ -146,11 +152,11 @@ const AutoTypePopupView = Backbone.View.extend({
},
actionEnterPressed() {
this.closeWithResult(AutoTypeSequenceType.PASSWORD);
this.closeWithResult('{PASSWORD}');
},
optEnterPressed() {
this.closeWithResult(AutoTypeSequenceType.USERNAME);
this.closeWithResult('{USERNAME}');
},
openKeyPressed() {
@ -158,6 +164,11 @@ const AutoTypePopupView = Backbone.View.extend({
this.trigger('show-open-files');
},
shiftEnterPressed(e) {
const activeItem = this.$el.find('.at-select__item[data-id="' + this.result.id + '"]');
this.showItemOptions(activeItem, e);
},
upPressed(e) {
e.preventDefault();
const activeIndex = this.entries.indexOf(this.result) - 1;
@ -190,7 +201,7 @@ const AutoTypePopupView = Backbone.View.extend({
},
keyPressed(e) {
if (e.which) {
if (e.which && e.which !== Keys.DOM_VK_RETURN) {
this.model.filter.text += String.fromCharCode(e.which);
this.render();
}
@ -213,9 +224,20 @@ const AutoTypePopupView = Backbone.View.extend({
itemClicked(e) {
const itemEl = $(e.target).closest('.at-select__item');
const id = itemEl.data('id');
this.result = this.entries.get(id);
this.closeWithResult();
const optionsClicked = $(e.target).closest('.at-select__item-options');
if (optionsClicked) {
this.showItemOptions(itemEl, e);
} else {
const id = itemEl.data('id');
this.result = this.entries.get(id);
this.closeWithResult();
}
},
itemRightClicked(e) {
const itemEl = $(e.target).closest('.at-select__item');
this.showItemOptions(itemEl, e);
},
mainWindowBlur() {
@ -225,6 +247,99 @@ const AutoTypePopupView = Backbone.View.extend({
mainWindowWillClose(e) {
e.preventDefault();
this.cancelAndClose();
},
showItemOptions(itemEl, event) {
if (event) {
event.stopImmediatePropagation();
}
const id = itemEl.data('id');
const entry = this.entries.get(id);
if (this.views.optionsDropdown) {
this.hideItemOptionsDropdown();
if (this.result && this.result.id === entry.id) {
return;
}
}
this.result = entry;
if (!itemEl.hasClass('at-select__item--active')) {
this.highlightActive();
}
const view = new DropdownView();
this.listenTo(view, 'cancel', this.hideItemOptionsDropdown);
this.listenTo(view, 'select', this.itemOptionsDropdownSelect);
const options = [];
if (entry.fields.otp) {
options.push({
value: '{TOTP}',
icon: 'clock-o',
text: Locale.autoTypeSelectionOtp
});
}
if (entry.user) {
options.push({
value: '{USERNAME}',
icon: 'user',
text: Format.capFirst(Locale.user)
});
}
if (entry.password) {
options.push({
value: '{PASSWORD}',
icon: 'key',
text: Format.capFirst(Locale.password)
});
}
for (const field of Object.keys(entry.fields)) {
if (field !== 'otp') {
options.push({
value: `{S:${field}}`,
icon: 'th-list',
text: field
});
}
}
let position;
if (event && event.button === 2) {
position = {
top: event.pageY,
left: event.pageX
};
} else {
const targetElRect = itemEl[0].getBoundingClientRect();
position = {
top: targetElRect.bottom,
right: targetElRect.right
};
}
view.render({
position,
options
});
this.views.optionsDropdown = view;
},
hideItemOptionsDropdown() {
if (this.views.optionsDropdown) {
this.views.optionsDropdown.remove();
delete this.views.optionsDropdown;
}
},
itemOptionsDropdownSelect(e) {
this.hideItemOptionsDropdown();
const sequence = e.item;
this.closeWithResult(sequence);
}
});

View File

@ -1,4 +1,6 @@
const Backbone = require('backbone');
const Keys = require('../const/keys');
const KeyHandler = require('../comp/key-handler');
const DropdownView = Backbone.View.extend({
template: require('templates/dropdown.hbs'),
@ -8,9 +10,19 @@ const DropdownView = Backbone.View.extend({
},
initialize() {
Backbone.trigger('dropdown-shown');
this.bodyClick = this.bodyClick.bind(this);
this.listenTo(Backbone, 'show-context-menu', this.bodyClick);
$('body').on('click contextmenu keyup', this.bodyClick);
this.listenTo(Backbone, 'show-context-menu dropdown-shown', this.bodyClick);
$('body').on('click contextmenu keydown', this.bodyClick);
KeyHandler.onKey(Keys.DOM_VK_UP, this.upPressed, this, false, 'dropdown');
KeyHandler.onKey(Keys.DOM_VK_DOWN, this.downPressed, this, false, 'dropdown');
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, 'dropdown');
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, 'dropdown');
this.prevModal = KeyHandler.modal === 'dropdown' ? undefined : KeyHandler.modal;
KeyHandler.setModal('dropdown');
},
render(config) {
@ -33,11 +45,27 @@ const DropdownView = Backbone.View.extend({
remove() {
this.viewRemoved = true;
$('body').off('click contextmenu keyup', this.bodyClick);
$('body').off('click contextmenu keydown', this.bodyClick);
KeyHandler.offKey(Keys.DOM_VK_UP, this.upPressed, this);
KeyHandler.offKey(Keys.DOM_VK_DOWN, this.downPressed, this);
KeyHandler.offKey(Keys.DOM_VK_RETURN, this.enterPressed, this);
KeyHandler.offKey(Keys.DOM_VK_ESCAPE, this.escPressed, this);
KeyHandler.setModal(this.prevModal);
Backbone.View.prototype.remove.apply(this);
},
bodyClick() {
bodyClick(e) {
if (
[Keys.DOM_VK_UP, Keys.DOM_VK_DOWN, Keys.DOM_VK_RETURN, Keys.DOM_VK_ESCAPE].includes(
e.which
)
) {
return;
}
if (!this.viewRemoved) {
this.trigger('cancel');
}
@ -48,6 +76,48 @@ const DropdownView = Backbone.View.extend({
const el = $(e.target).closest('.dropdown__item');
const selected = el.data('value');
this.trigger('select', { item: selected, el });
},
upPressed(e) {
e.preventDefault();
if (!this.selectedOption) {
this.selectedOption = this.options.length - 1;
} else {
this.selectedOption--;
}
this.renderSelectedOption();
},
downPressed(e) {
e.preventDefault();
if (this.selectedOption === undefined || this.selectedOption === this.options.length - 1) {
this.selectedOption = 0;
} else {
this.selectedOption++;
}
this.renderSelectedOption();
},
renderSelectedOption() {
this.$el.find('.dropdown__item').removeClass('dropdown__item--active');
this.$el
.find(`.dropdown__item:nth(${this.selectedOption})`)
.addClass('dropdown__item--active');
},
enterPressed() {
if (!this.viewRemoved && this.selectedOption !== undefined) {
const el = this.$el.find(`.dropdown__item:nth(${this.selectedOption})`);
const selected = el.data('value');
this.trigger('select', { item: selected, el });
}
},
escPressed(e) {
e.stopImmediatePropagation();
if (!this.viewRemoved) {
this.trigger('cancel');
}
}
});

View File

@ -14,10 +14,10 @@ const ModalView = Backbone.View.extend({
initialize() {
if (typeof this.model.esc === 'string') {
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, this, false, 'alert');
}
if (typeof this.model.enter === 'string') {
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, true);
KeyHandler.onKey(Keys.DOM_VK_RETURN, this.enterPressed, this, false, 'alert');
}
KeyHandler.setModal('alert');
},

View File

@ -93,18 +93,18 @@
text-overflow: ellipsis;
word-wrap: break-word;
&:first-of-type {
width: 1em;
width: 2em;
text-align: center;
}
&:nth-of-type(2) {
width: 50%;
}
&:nth-of-type(3) {
width: 25%;
}
&:nth-of-type(4) {
width: 25%;
}
&:nth-of-type(5) {
width: 2em;
}
}
}
&__item {
@ -114,6 +114,17 @@
@include area-selected(right);
cursor: pointer;
}
&--active {
}
&-options {
text-align: center;
&:hover {
background: var(--background-color);
.at-select__item--active & {
background: var(--action-color);
}
}
}
}
&__empty-title {
align-self: center;

View File

@ -51,6 +51,12 @@ body.th-macdark {
.at-select__table .at-select__item.at-select__item--active {
background-color: var(--selected-item-color);
}
.at-select__item--active .at-select__item-options:hover {
background: var(--secondary-background-color);
}
.dropdown__item--active, .dropdown__item--active:hover {
background-color: var(--selected-item-color);
}
@include nomobile {
.list__item--active,
.list__item--active:hover {

View File

@ -9,4 +9,7 @@
<td>{{#if title}}{{title}}{{else}}({{res 'noTitle'}}){{/if}}</td>
<td>{{user}}</td>
<td>{{url}}</td>
<td class="at-select__item-options">
<i class="fa fa-ellipsis-h"></i>
</td>
</tr>

View File

@ -2,9 +2,10 @@
<div class="at-select__header">
<h1 class="at-select__header-text">{{res 'autoTypeHeader'}}</h1>
<div class="at-select__hint">
<div class="at-select__hint-text"><span class="shortcut">{{keyEnter}}</span>: {{selectionHintDefault}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{{actionSymbol}}} {{keyEnter}}</span>: {{selectionHintAction}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{{altSymbol}}} {{keyEnter}}</span>: {{selectionHintOpt}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{keyEnter}}</span>: {{res 'autoTypeSelectionHint'}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{{actionSymbol}}} {{keyEnter}}</span>: {{res 'autoTypeSelectionHintAction'}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{{altSymbol}}} {{keyEnter}}</span>: {{res 'autoTypeSelectionHintOpt'}}</div>
<div class="at-select__hint-text"><span class="shortcut">{{{shiftSymbol}}} {{keyEnter}}</span>: {{res 'autoTypeSelectionHintShift'}}</div>
</div>
{{#if filterText}}
<div class="at-select__header-filter">

View File

@ -3,6 +3,7 @@ Release notes
##### v1.11.0 (TBD)
`+` #1226: 7-digit Authy OTP support
`+` #107: multiline custom fields support
`+` #1243: auto-type any field
`-` fix #764: multiple attachments display
`-` fix multi-line fields display in history