diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 941323e6..133d4cf2 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,17 +1,19 @@
-
## Expected Behavior
+
## Current Behavior
+
## Possible Solution
+
## Steps to Reproduce (for bugs)
@@ -22,11 +24,8 @@
## Debug info
-
-## General Info
-KeePassXC fork - VERSION
-keepassxc-browser - VERSION
-
-Operating system: OS
-Browser: BROWSER
+KeePassXC - {VERSION}
+keepassxc-browser - {VERSION}
+Operating system: Mac/Win/Linux
+Browser: Chrome/Firefox/Vivaldi/Chromium
Proxy used: YES/NO
diff --git a/CHANGELOG b/CHANGELOG
index 12e67272..a02ad448 100755
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,15 @@
+1.1.0 (09-05-2018)
+=========================
+- Allow specifying ignored sites
+- Add new notification options
+- Improve detection of username fields
+- Change conflicting shortcuts
+- Redetect credential fields after reload
+- Don't show popup when database is closed
+- Various password generator fixes
+- Fix various resource leaks
+- Fix searching in all databases
+
1.0.1 (04-03-2018)
=========================
- Don't fill password fields if they already have data
diff --git a/keepassxc-browser/background/browserAction.js b/keepassxc-browser/background/browserAction.js
index 7b72292c..c728f6dd 100755
--- a/keepassxc-browser/background/browserAction.js
+++ b/keepassxc-browser/background/browserAction.js
@@ -1,3 +1,5 @@
+'use strict';
+
const browserAction = {};
const BLINK_TIMEOUT_DEFAULT = 7500;
@@ -202,6 +204,14 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
browserAction.setRememberPopup = function(tabId, username, password, url, usernameExists, credentialsList) {
browser.storage.local.get({'settings': {}}).then(function(item) {
const settings = item.settings;
+
+ // Don't show anything if the site is in the ignore list
+ for (const site in settings.ignoredSites) {
+ if (site === url) {
+ return;
+ }
+ }
+
const id = tabId || page.currentTabId;
let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, 0));
@@ -239,8 +249,30 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna
browserAction.show(null, {'id': id});
- if (page.settings.showNotifications) {
- showNotification('Create or modify the credentials by clicking on the extension icon.');
+ if (page.settings.showLoginNotifications) {
+ const message = 'Create or modify the credentials by clicking on the extension icon.';
+ const buttons = [
+ {
+ 'title': 'Close'
+ },
+ {
+ 'title': 'Never ask for this page'
+ }];
+
+ browser.notifications.create({
+ 'type': 'basic',
+ 'iconUrl': browser.extension.getURL('icons/keepassxc_64x64.png'),
+ 'title': 'KeePassXC-Browser',
+ 'message': message,
+ 'buttons': buttons
+ });
+
+ browser.notifications.onButtonClicked.addListener((id, index) => {
+ browser.notifications.clear(id);
+ if (index === 1) {
+ browserAction.ignoreSite(url);
+ }
+ });
}
});
};
@@ -269,3 +301,21 @@ browserAction.generateIconName = function(iconType, icon) {
return name;
};
+
+browserAction.ignoreSite = function(url) {
+ browser.windows.getCurrent().then((win) => {
+ // Get current active window
+ browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
+ const tab = tabs[0];
+
+ // Send the message to the current tab's content script
+ browser.runtime.getBackgroundPage().then((global) => {
+ browser.tabs.sendMessage(tab.id, {
+ action: 'ignore-site',
+ args: [url]
+ });
+ });
+ });
+ });
+};
+
diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js
index c730af6c..be7b1e66 100755
--- a/keepassxc-browser/background/event.js
+++ b/keepassxc-browser/background/event.js
@@ -1,3 +1,5 @@
+'use strict';
+
const kpxcEvent = {};
kpxcEvent.onMessage = function(request, sender, callback) {
@@ -72,12 +74,15 @@ kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) {
else {
console.log('undefined handler for tab ' + tab.id);
}
- }).catch((e) => {console.log(e);});
+ }).catch((e) => {
+ console.log(e);
+ });
};
-kpxcEvent.onShowAlert = function(callback, tab, message) {
- if (page.settings.supressAlerts) { console.log(message); }
- else { alert(message); }
+kpxcEvent.onShowNotification = function(callback, tab, message) {
+ if (page.settings.showNotifications) {
+ showNotification(message);
+ }
};
kpxcEvent.showStatus = function(configured, tab, callback) {
@@ -159,13 +164,18 @@ kpxcEvent.onReconnect = function(callback, tab) {
if (gdRes) {
keepass.testAssociation((response) => {
keepass.isConfigured().then((configured) => {
+ browser.tabs.sendMessage(tab.id, {
+ action: 'redetect_fields'
+ });
kpxcEvent.showStatus(configured, tab, callback);
- }).catch((e) => {console.log(e);});
+ }).catch((e) => {
+ console.log(e);
+ });
}, tab);
}
}, null);
});
- }, 2000);
+ }, 500);
};
kpxcEvent.lockDatabase = function(callback, tab) {
@@ -216,7 +226,17 @@ kpxcEvent.onRemoveCredentialsFromTabInformation = function(callback, tab) {
};
kpxcEvent.onSetRememberPopup = function(callback, tab, username, password, url, usernameExists, credentialsList) {
- browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList);
+ keepass.testAssociation((response) => {
+ if (response) {
+ keepass.isConfigured().then((configured) => {
+ if (configured) {
+ browserAction.setRememberPopup(tab.id, username, password, url, usernameExists, credentialsList);
+ }
+ }).catch((e) => {
+ console.log(e);
+ });
+ }
+ }, tab);
};
kpxcEvent.onLoginPopup = function(callback, tab, logins) {
@@ -263,20 +283,10 @@ kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
callback();
};
-kpxcEvent.oldDatabaseHash = 'no-hash';
-kpxcEvent.checkDatabaseHash = function(callback, tab) {
- keepass.checkDatabaseHash((response) => {
- callback({old: kpxcEvent.oldDatabaseHash, new: response});
- kpxcEvent.oldDatabaseHash = response;
- });
-};
-
// all methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
- 'alert': kpxcEvent.onShowAlert,
'associate': keepass.associate,
- 'check_databasehash': kpxcEvent.checkDatabaseHash,
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
'generate_password': keepass.generatePassword,
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
@@ -298,6 +308,7 @@ kpxcEvent.messageHandlers = {
'update_credentials': keepass.updateCredentials,
'save_settings': kpxcEvent.onSaveSettings,
'set_remember_credentials': kpxcEvent.onSetRememberPopup,
+ 'show_notification': kpxcEvent.onShowNotification,
'stack_add': browserAction.stackAdd,
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC
};
diff --git a/keepassxc-browser/background/httpauth.js b/keepassxc-browser/background/httpauth.js
index 5457caa7..cdd23edc 100755
--- a/keepassxc-browser/background/httpauth.js
+++ b/keepassxc-browser/background/httpauth.js
@@ -1,3 +1,5 @@
+'use strict';
+
const httpAuth = {};
httpAuth.requests = [];
diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js
index 4f6c6afc..ce7fed70 100644
--- a/keepassxc-browser/background/init.js
+++ b/keepassxc-browser/background/init.js
@@ -1,3 +1,5 @@
+'use strict';
+
keepass.migrateKeyRing().then(() => {
page.initSettings().then(() => {
page.initOpenedTabs().then(() => {
diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js
index 74859d0d..6dec6aaa 100755
--- a/keepassxc-browser/background/keepass.js
+++ b/keepassxc-browser/background/keepass.js
@@ -18,6 +18,7 @@ keepass.keySize = 24;
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
keepass.cacheTimeout = 30 * 1000; // milliseconds
keepass.databaseHash = 'no-hash'; //no-hash = KeePassXC is too old and does not return a hash value
+keepass.previousDatabaseHash = 'no-hash';
keepass.keyId = 'keepassxc-browser-cryptokey-name';
keepass.keyBody = 'keepassxc-browser-key';
keepass.messageTimeout = 500; // milliseconds
@@ -214,15 +215,24 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
}
let entries = [];
+ let keys = [];
const kpAction = kpActions.GET_LOGINS;
const nonce = keepass.getNonce();
const incrementedNonce = keepass.incrementedNonce(nonce);
const {dbid} = keepass.getCryptoKey();
+
+ for (let keyHash in keepass.keyRing) {
+ keys.push({
+ id: keepass.keyRing[keyHash].id,
+ key: keepass.keyRing[keyHash].key
+ });
+ }
let messageData = {
action: kpAction,
id: dbid,
- url: url
+ url: url,
+ keys: keys
};
if (submiturl) {
@@ -704,7 +714,7 @@ keepass.isAssociated = function() {
keepass.migrateKeyRing = function() {
return new Promise((resolve, reject) => {
browser.storage.local.get('keyRing').then((item) => {
- const keyring = item.keyRing;
+ const keyring = item.keyRing;
// Change dates to numbers, for compatibilty with Chromium based browsers
if (keyring) {
let num = 0;
@@ -826,11 +836,22 @@ keepass.onNativeMessage = function(response) {
// Handle database lock/unlock status
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
- keepass.testAssociation((response) => {
+ keepass.testAssociation((associationResponse) => {
keepass.isConfigured().then((configured) => {
let data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
data.iconType = configured ? 'normal' : 'cross';
browserAction.show(null, {'id': page.currentTabId});
+
+ // Send message to content script
+ browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
+ if (tabs.length) {
+ browser.tabs.sendMessage(tabs[0].id, {
+ action: 'check_database_hash',
+ hash: {old: kpxcEvent.previousDatabaseHash, new: keepass.databaseHash}
+ });
+ keepass.previousDatabaseHash = keepass.databaseHash;
+ }
+ });
});
}, null);
}
diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js
index f82b2ea6..60be8922 100755
--- a/keepassxc-browser/background/page.js
+++ b/keepassxc-browser/background/page.js
@@ -1,3 +1,5 @@
+'use strict';
+
const defaultSettings = {
checkUpdateKeePassXC: 3,
autoCompleteUsernames: true,
@@ -5,13 +7,15 @@ const defaultSettings = {
usePasswordGenerator: true,
autoFillSingleEntry: false,
autoRetrieveCredentials: true,
- showNotifications: true
+ showNotifications: true,
+ showLoginNotifications: true,
+ saveDomainOnly: true
};
var page = {};
-page.tabs = {};
+page.tabs = [];
page.currentTabId = -1;
-page.blockedTabs = {};
+page.blockedTabs = [];
page.initSettings = function() {
return new Promise((resolve, reject) => {
@@ -38,6 +42,12 @@ page.initSettings = function() {
if (!('showNotifications' in page.settings)) {
page.settings.showNotifications = defaultSettings.showNotifications;
}
+ if (!('showLoginNotifications' in page.settings)) {
+ page.settings.showLoginNotifications = defaultSettings.showLoginNotifications;
+ }
+ if (!('saveDomainOnly' in page.settings)) {
+ page.settings.saveDomainOnly = defaultSettings.saveDomainOnly;
+ }
browser.storage.local.set({'settings': page.settings});
resolve(page.settings);
});
@@ -101,7 +111,7 @@ page.createTabEntry = function(tabId) {
page.tabs[tabId] = {
'stack': [],
'errorMessage': null,
- 'loginList': {}
+ 'loginList': []
};
};
diff --git a/keepassxc-browser/global.js b/keepassxc-browser/global.js
index 558dc9da..55456cd3 100755
--- a/keepassxc-browser/global.js
+++ b/keepassxc-browser/global.js
@@ -1,3 +1,5 @@
+'use strict';
+
var isFirefox = function() {
if (!(/Chrome/.test(navigator.userAgent) && /Google/.test(navigator.vendor))) {
return true;
diff --git a/keepassxc-browser/keepassxc-browser.css b/keepassxc-browser/keepassxc-browser.css
index 8871bad9..d4b50cdd 100644
--- a/keepassxc-browser/keepassxc-browser.css
+++ b/keepassxc-browser/keepassxc-browser.css
@@ -18,6 +18,7 @@
.kpxc .ui-dialog {
font-size: 12px !important;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
+ z-index: 2147483645;
}
.kpxc .ui-widget-overlay {
@@ -57,10 +58,10 @@ input.genpw-text {
}
.genpw-input-group-addon {
- font-size: inherit !important;
+ font-size: .9em !important;
background-color: #eee;
border: 1px solid #ccc;
- padding: .4em;
+ padding: .2em;
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@@ -102,6 +103,10 @@ input.genpw-text {
margin-right: 5px;
}
+#cip-genpw-quality {
+ text-align: center;
+}
+
.b2c-modal-backdrop {
position: fixed;
top: 0;
diff --git a/keepassxc-browser/keepassxc-browser.js b/keepassxc-browser/keepassxc-browser.js
index 88c70648..c399a8cc 100755
--- a/keepassxc-browser/keepassxc-browser.js
+++ b/keepassxc-browser/keepassxc-browser.js
@@ -1,8 +1,11 @@
+'use strict';
+
// contains already called method names
var _called = {};
_called.retrieveCredentials = false;
_called.clearLogins = false;
_called.manualFillRequested = 'none';
+let _loginId = -1;
// Count of detected form fields on the page
var _detectedFields = 0;
@@ -15,10 +18,12 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
if (cip.u) {
cip.setValueWithChange(cip.u, cip.credentials[req.id].login);
combination = cipFields.getCombination('username', cip.u);
+ _loginId = req.id;
cip.u.focus();
}
if (cip.p) {
cip.setValueWithChange(cip.p, cip.credentials[req.id].password);
+ _loginId = req.id;
combination = cipFields.getCombination('password', cip.p);
}
@@ -27,48 +32,44 @@ browser.runtime.onMessage.addListener(function(req, sender, callback) {
cipForm.destroy(false, {'password': list.list[0], 'username': list.list[1]});
}
}
- }
- else if (req.action === 'fill_user_pass') {
+ } else if (req.action === 'fill_user_pass') {
_called.manualFillRequested = 'both';
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElement(false);
});
- }
- else if (req.action === 'fill_pass_only') {
+ } else if (req.action === 'fill_pass_only') {
_called.manualFillRequested = 'pass';
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElement(false, true); // passOnly to true
});
- }
- else if (req.action === 'fill_totp') {
+ } else if (req.action === 'fill_totp') {
cip.receiveCredentialsIfNecessary().then((response) => {
cip.fillInFromActiveElementTOTPOnly(false);
});
- }
- else if (req.action === 'activate_password_generator') {
+ } else if (req.action === 'activate_password_generator') {
cip.initPasswordGenerator(cipFields.getAllFields());
- }
- else if (req.action === 'remember_credentials') {
+ } else if (req.action === 'remember_credentials') {
cip.contextMenuRememberCredentials();
- }
- else if (req.action === 'choose_credential_fields') {
+ } else if (req.action === 'choose_credential_fields') {
cipDefine.init();
- }
- else if (req.action === 'clear_credentials') {
+ } else if (req.action === 'clear_credentials') {
cipEvents.clearCredentials();
callback();
- }
- else if (req.action === 'activated_tab') {
+ } else if (req.action === 'activated_tab') {
cipEvents.triggerActivatedTab();
callback();
- }
- else if (req.action === 'redetect_fields') {
+ } else if (req.action === 'redetect_fields') {
browser.runtime.sendMessage({
action: 'load_settings',
}).then((response) => {
cip.settings = response;
cip.initCredentialFields(true);
});
+ } else if (req.action === 'ignore-site') {
+ cip.ignoreSite(req.args);
+ }
+ else if (req.action === 'check_database_hash' && 'hash' in req) {
+ cip.detectDatabaseChange(req.hash);
}
}
});
@@ -231,7 +232,7 @@ cipPassword.createDialog = function() {
.addClass('genpw-input-group-addon')
.addClass('b2c-add-on')
.attr('id', 'cip-genpw-quality')
- .text('123 Bits');
+ .text('??? Bits');
$inputGroup.append($textfieldPassword).append($quality);
const $checkGroup = jQuery('
').addClass('genpw-input-group');
@@ -305,7 +306,11 @@ cipPassword.createDialog = function() {
$password = $password.substring(0, field.attr('maxlength'));
jQuery('input#cip-genpw-textfield-password:first').val($password);
jQuery('#cip-genpw-btn-clipboard:first').removeClass('b2c-btn-success');
- alert('The generated password is longer than the allowed length!\nIt has been cut to fit the length.\n\nPlease remember the new password!');
+ const message = 'Error:\nThe generated password is longer than the allowed length!\nIt has been cut to fit the length.\n\nPlease remember the new password!';
+ browser.runtime.sendMessage({
+ action: 'show_notification',
+ args: [message]
+ });
}
}
@@ -489,7 +494,7 @@ cipForm.init = function(form, credentialFields) {
// TODO: could be called multiple times --> update credentialFields
// not already initialized && password-field is not null
- if (!form.data('cipForm-initialized') && credentialFields.password) {
+ if (!form.data('cipForm-initialized') && (credentialFields.password || credentialFields.username)) {
form.data('cipForm-initialized', true);
cipForm.setInputFields(form, credentialFields);
form.submit(cipForm.onSubmit);
@@ -807,7 +812,7 @@ cipFields.prepareId = function(id) {
// Check aria-hidden attribute by looping the parent elements of input field
cipFields.getAriaHidden = function(field) {
let $par = jQuery(field).parents();
- for (p of $par) {
+ for (const p of $par) {
const val = $(p).attr('aria-hidden');
if (val) {
return val;
@@ -818,7 +823,7 @@ cipFields.getAriaHidden = function(field) {
cipFields.getOverflowHidden = function(field) {
let $par = jQuery(field).parents();
- for (p of $par) {
+ for (const p of $par) {
const val = $(p).css('overflow');
if (val === 'hidden') {
return true;
@@ -889,6 +894,15 @@ cipFields.getAllCombinations = function(inputs) {
}
}
+ // If only username field found, add it to the array
+ if (fields.length === 0 && uField) {
+ const combination = {
+ username: uField[0].getAttribute('data-cip-id'),
+ password: null
+ };
+ fields.push(combination);
+ }
+
return fields;
};
@@ -1033,7 +1047,7 @@ cipFields.getPasswordField = function(usernameId, checkDisabled) {
// search all inputs on this one form
if (form) {
passwordField = jQuery('input[type=\'password\']:first', form);
- if (passwordField.length < 1) {
+ if (passwordField && passwordField.length < 1) {
passwordField = null;
}
@@ -1139,7 +1153,6 @@ cip.credentials = [];
jQuery(function() {
cip.init();
cip.detectNewActiveFields();
- cip.detectDatabaseChange();
});
cip.init = function() {
@@ -1166,46 +1179,36 @@ cip.detectNewActiveFields = function() {
};
// Switch credentials if database is changed or closed
-cip.detectDatabaseChange = function() {
- let dbDetectInterval = setInterval(function() {
- if (document.visibilityState !== 'hidden') {
+cip.detectDatabaseChange = function(response) {
+ if (document.visibilityState !== 'hidden') {
+ if (response.new === 'no-hash' && response.old !== 'no-hash') {
+ cipEvents.clearCredentials();
+
browser.runtime.sendMessage({
- action: 'check_databasehash'
- }).then((response) => {
- if (response.new === 'no-hash') {
- cipEvents.clearCredentials();
+ action: 'page_clear_logins'
+ });
- browser.runtime.sendMessage({
- action: 'page_clear_logins'
- });
+ // Switch back to default popup
+ browser.runtime.sendMessage({
+ action: 'get_status',
+ args: [ true ] // Set polling to true, this is an internal function call
+ });
+ } else if (response.new !== 'no-hash' && response.new !== response.old) {
+ browser.runtime.sendMessage({
+ action: 'load_settings',
+ }).then((response) => {
+ cip.settings = response;
+ cip.initCredentialFields(true);
- // Switch back to default popup
- browser.runtime.sendMessage({
- action: 'get_status',
- args: [ true ] // Set polling to true, this is an internal function call
- });
- } else {
- if (response.new !== 'no-hash' && response.new !== response.old) {
- browser.runtime.sendMessage({
- action: 'load_settings',
- }).then((response) => {
- cip.settings = response;
- cip.initCredentialFields(true);
-
- // If user has requested a manual fill through context menu the actual credential filling
- // is handled here when the opened database has been regognized. It's not a pretty hack.
- if (_called.manualFillRequested && _called.manualFillRequested !== 'none') {
- cip.fillInFromActiveElement(false, (_called.manualFillRequested === 'pass' ? true : false));
- _called.manualFillRequested = 'none';
- }
- });
- }
+ // If user has requested a manual fill through context menu the actual credential filling
+ // is handled here when the opened database has been regognized. It's not a pretty hack.
+ if (_called.manualFillRequested && _called.manualFillRequested !== 'none') {
+ cip.fillInFromActiveElement(false, _called.manualFillRequested === 'pass');
+ _called.manualFillRequested = 'none';
}
- }).catch((e) => {
- console.log(e);
});
}
- }, 1000);
+ }
};
cip.initCredentialFields = function(forceCall) {
@@ -1236,6 +1239,11 @@ cip.initCredentialFields = function(forceCall) {
cip.url = document.location.origin;
cip.submitUrl = cip.getFormActionUrl(cipFields.combinations[0]);
+ // Get submitUrl for a single input
+ if (!cip.submitUrl && cipFields.combinations.length === 1 && inputs.length === 1) {
+ cip.submitUrl = cip.getFormActionUrlFromSingleInput(inputs[0]);
+ }
+
if (cip.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (cip.url && cip.submitUrl)) {
browser.runtime.sendMessage({
action: 'retrieve_credentials',
@@ -1358,7 +1366,9 @@ cip.preparePageForMultipleCredentials = function(credentials) {
cipAutocomplete.init(_f(i.username));
}
} else if (_detectedFields == 1) {
- // If only password field is the visible one
+ if (_f(i.username)) {
+ cipAutocomplete.init(_f(i.username));
+ }
if (_f(i.password)) {
cipAutocomplete.init(_f(i.password));
}
@@ -1392,6 +1402,20 @@ cip.getFormActionUrl = function(combination) {
return action;
};
+cip.getFormActionUrlFromSingleInput = function(field) {
+ if (!field) {
+ return null;
+ }
+
+ let action = field.formAction;
+
+ if (typeof(action) !== 'string' || action === '') {
+ action = document.location.origin + document.location.pathname;
+ }
+
+ return action;
+};
+
cip.fillInCredentials = function(combination, onlyPassword, suppressWarnings) {
const action = cip.getFormActionUrl(combination);
@@ -1455,9 +1479,9 @@ cip.fillInFromActiveElement = function(suppressWarnings, passOnly = false) {
if (passOnly) {
if (!_f(combination.password)) {
- const message = 'Unable to find a password field';
+ const message = 'Error:\nUnable to find a password field';
browser.runtime.sendMessage({
- action: 'alert',
+ action: 'show_notification',
args: [message]
});
return;
@@ -1473,17 +1497,21 @@ cip.fillInFromActiveElementTOTPOnly = function(suppressWarnings) {
const el = document.activeElement;
cipFields.setUniqueId(jQuery(el));
const fieldId = cipFields.prepareId(jQuery(el).attr('data-cip-id'));
+ const pos = _loginId;
- if (cip.credentials[0]) {
+ if (pos >= 0 && cip.credentials[pos]) {
+ // Check the value from stringFields (to be removed)
const $sf = _fs(fieldId);
- if (cip.credentials[0].stringFields && cip.credentials[0].stringFields.length > 0) {
- const sFields = cip.credentials[0].stringFields;
+ if (cip.credentials[pos].stringFields && cip.credentials[pos].stringFields.length > 0) {
+ const sFields = cip.credentials[pos].stringFields;
for (const s of sFields) {
const val = s["KPH: {TOTP}"];
if (val) {
cip.setValue($sf, val);
}
}
+ } else if (cip.credentials[pos].totp && cip.credentials[pos].totp.length > 0) {
+ cip.setValue($sf, cip.credentials[pos].totp);
}
}
};
@@ -1539,9 +1567,9 @@ cip.setValueWithChange = function(field, value) {
cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
// no credentials available
if (cip.credentials.length === 0 && !suppressWarnings) {
- const message = 'No logins found.';
+ const message = 'Error:\nNo logins found.';
browser.runtime.sendMessage({
- action: 'alert',
+ action: 'show_notification',
args: [message]
});
return;
@@ -1555,12 +1583,14 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
let filledIn = false;
if (uField && !onlyPassword) {
cip.setValueWithChange(uField, cip.credentials[0].login);
+ _loginId = 0;
filledIn = true;
}
if (pField) {
pField.attr('type', 'password');
cip.setValueWithChange(pField, cip.credentials[0].password);
pField.data('unchanged', true);
+ _loginId = 0;
filledIn = true;
}
@@ -1572,9 +1602,9 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
- const message = 'Error #101\nCannot find fields to fill in.';
+ const message = 'Error:\nCannot find fields to fill in.';
browser.runtime.sendMessage({
- action: 'alert',
+ action: 'show_notification',
args: [message]
});
}
@@ -1585,12 +1615,14 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
let filledIn = false;
if (uField) {
cip.setValueWithChange(uField, cip.credentials[combination.loginId].login);
+ _loginId = combination.loginId;
filledIn = true;
}
if (pField) {
cip.setValueWithChange(pField, cip.credentials[combination.loginId].password);
pField.data('unchanged', true);
+ _loginId = combination.loginId;
filledIn = true;
}
@@ -1602,9 +1634,9 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
- const message = 'Error #102\nCannot find fields to fill in.';
+ const message = 'Error:\nCannot find fields to fill in.';
browser.runtime.sendMessage({
- action: 'alert',
+ action: 'show_notification',
args: [message]
});
}
@@ -1631,7 +1663,7 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
}
- // for the correct alert message: 0 = no logins, X > 1 = too many logins
+ // for the correct notification message: 0 = no logins, X > 1 = too many logins
if (countPasswords === 0) {
countPasswords = cip.credentials.length;
}
@@ -1656,19 +1688,17 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
// user has to select correct credentials by himself
if (countPasswords > 1) {
if (!suppressWarnings) {
- const message = 'Error #105\nMore than one login was found in KeePassXC!\n' +
- 'Press the KeePassXC-Browser icon for more options.';
- browser.runtime.sendMessage({
- action: 'alert',
- args: [message]
- });
+ const $target = onlyPassword ? pField : uField;
+ cipAutocomplete.init($target);
+ $target.focus();
+ jQuery($target).autocomplete('search', jQuery($target).val());
}
}
else if (countPasswords < 1) {
if (!suppressWarnings) {
- const message = 'Error #103\nNo credentials for given username found.';
+ const message = 'Error:\nNo credentials for given username found.';
browser.runtime.sendMessage({
- action: 'alert',
+ action: 'show_notification',
args: [message]
});
}
@@ -1676,12 +1706,10 @@ cip.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
else {
if (!suppressWarnings) {
- const message = 'Error #104\nMore than one login was found in KeePassXC!\n' +
- 'Press the KeePassXC-Browser icon for more options.';
- browser.runtime.sendMessage({
- action: 'alert',
- args: [message]
- });
+ const $target = onlyPassword ? pField : uField;
+ cipAutocomplete.init($target);
+ $target.focus();
+ jQuery($target).autocomplete('search', jQuery($target).val());
}
}
}
@@ -1717,7 +1745,11 @@ cip.contextMenuRememberCredentials = function() {
}
if (!cip.rememberCredentials(usernameValue, passwordValue)) {
- alert('Could not detect changed credentials.');
+ const message = 'Error:\nCould not detect changed credentials.';
+ browser.runtime.sendMessage({
+ action: 'show_notification',
+ args: [message]
+ });
}
};
@@ -1762,7 +1794,7 @@ cip.rememberCredentials = function(usernameValue, passwordValue) {
let url = jQuery(this)[0].action;
if (!url) {
- url = document.location.href;
+ url = cip.settings.saveDomainOnly ? document.location.origin : document.location.href;
if (url.indexOf('?') > 0) {
url = url.substring(0, url.indexOf('?'));
if (url.length < document.location.origin.length) {
@@ -1782,6 +1814,25 @@ cip.rememberCredentials = function(usernameValue, passwordValue) {
return false;
};
+cip.ignoreSite = function(sites) {
+ if (!sites || sites.length === 0) {
+ return;
+ }
+
+ const site = sites[0];
+ if (!cip.settings['ignoredSites']) {
+ cip.settings['ignoredSites'] = {};
+ }
+
+ cip.settings['ignoredSites'][site] = {
+ url: site
+ };
+
+ browser.runtime.sendMessage({
+ action: 'save_settings',
+ args: [cip.settings]
+ });
+};
var cipEvents = {};
diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json
index 08387803..047606da 100755
--- a/keepassxc-browser/manifest.json
+++ b/keepassxc-browser/manifest.json
@@ -1,8 +1,8 @@
{
"manifest_version": 2,
"name": "KeePassXC-Browser",
- "version": "1.0.1",
- "version_name": "1.0.1",
+ "version": "1.1.0",
+ "version_name": "1.1.0",
"description": "KeePassXC integration for modern web browsers",
"author": "KeePassXC Team",
"icons": {
@@ -72,8 +72,8 @@
"fill-password": {
"description": "Insert a password",
"suggested_key": {
- "default": "Alt+Shift+P",
- "mac": "MacCtrl+Shift+P"
+ "default": "Alt+Shift+I",
+ "mac": "MacCtrl+Shift+I"
}
},
"fill-totp": {
diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html
index 4184beed..597b2af9 100644
--- a/keepassxc-browser/options/options.html
+++ b/keepassxc-browser/options/options.html
@@ -20,6 +20,7 @@
KeePassXC-
General
Connected Databases
Specified credential fields
+
Ignored sites
About
@@ -29,9 +30,9 @@
KeePassXC-
General Settings
- If you just want to insert username + password into the fields where your focus is, press Alt + Shift + U
.
+ If you just want to insert username + password into the fields where your focus is, press Ctrl + Shift + U Alt + Shift + U
.
- If you only want to insert the password, just press Alt + Shift + P
.
+ If you only want to insert the password, just press Ctrl + Shift + I Alt + Shift + I
.
You can customize these shortcuts on chrome://extensions/configureCommands
page
@@ -39,7 +40,7 @@
General Settings
Blink Time:
@@ -55,7 +56,7 @@
General Settings
Redirect Offset:
@@ -73,7 +74,7 @@
General Settings
Redirect Allowance:
@@ -130,7 +131,21 @@
General Settings
Show notifications.
-
Allow KeePassXC-Browser to show notifications when user interaction is needed from the extension icon.
+
Show notifications for errors and when user interaction is required.
+
+
+
+ Show a notification when new credentials can be saved to the database.
+
+
+
+
+
+
+
+ Save domain only.
+
+ When saving new credentials save only the domain instead of full URL.
@@ -274,6 +289,52 @@ Remove specified credential fields?
+
+
+
Ignored sites
+
+
+ Sites in this list are ignored when new credentials are detected.
+
+ Go to the page with new credentials, click the blinking KeePassXC-Browser icon or the notification and select Never ask for this page .
+
+
+
+
+ Page URL
+ Delete
+
+
+
+
+ No ignored sites found.
+
+
+
+ Remove
+
+
+
+
+
+
+
+
+
Do you really want to remove the specified site from the ignore list?
+
KeePassXC-Browser will detect new credentials the next time you visit this page.
+
+
+
+
+
+
+
About
diff --git a/keepassxc-browser/options/options.js b/keepassxc-browser/options/options.js
index b4d826a2..e0fab0c8 100644
--- a/keepassxc-browser/options/options.js
+++ b/keepassxc-browser/options/options.js
@@ -1,7 +1,15 @@
+'use strict';
+
if (jQuery) {
var $ = jQuery.noConflict(true);
}
+const defaultSettings = {
+ blinkTimeout: 7500,
+ redirectOffset: -1,
+ redirectAllowance: 1
+};
+
$(function() {
browser.runtime.sendMessage({ action: 'load_settings' }).then((settings) => {
options.settings = settings;
@@ -11,6 +19,7 @@ $(function() {
options.initGeneralSettings();
options.initConnectedDatabases();
options.initSpecifiedCredentialFields();
+ options.initIgnoredSites();
options.initAbout();
});
});
@@ -111,7 +120,7 @@ options.initGeneralSettings = function() {
$('#blinkTimeoutButton').click(function(){
const blinkTimeout = $.trim($('#blinkTimeout').val());
- const blinkTimeoutval = Number(blinkTimeout);
+ const blinkTimeoutval = blinkTimeout !== '' ? Number(blinkTimeout) : defaultSettings.blinkTimeout;
options.settings['blinkTimeout'] = String(blinkTimeoutval);
options.saveSetting('blinkTimeout');
@@ -119,7 +128,7 @@ options.initGeneralSettings = function() {
$('#blinkMinTimeoutButton').click(function(){
const blinkMinTimeout = $.trim($('#blinkMinTimeout').val());
- const blinkMinTimeoutval = Number(blinkMinTimeout);
+ const blinkMinTimeoutval = blinkMinTimeout !== '' ? Number(blinkMinTimeout) : defaultSettings.redirectOffset;
options.settings['blinkMinTimeout'] = String(blinkMinTimeoutval);
options.saveSetting('blinkMinTimeout');
@@ -127,7 +136,7 @@ options.initGeneralSettings = function() {
$('#allowedRedirectButton').click(function(){
const allowedRedirect = $.trim($('#allowedRedirect').val());
- const allowedRedirectval = Number(allowedRedirect);
+ const allowedRedirectval = allowedRedirect !== '' ? Number(allowedRedirect) : defaultSettings.redirectAllowance;
options.settings['allowedRedirect'] = String(allowedRedirectval);
options.saveSetting('allowedRedirect');
@@ -167,8 +176,7 @@ options.initConnectedDatabases = function() {
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
$('#tab-connected-databases table tbody:first tr.empty:first').hide();
- }
- else {
+ } else {
$('#tab-connected-databases table tbody:first tr.empty:first').show();
}
});
@@ -195,8 +203,7 @@ options.initConnectedDatabases = function() {
if ($('#tab-connected-databases table tbody:first tr').length > 2) {
$('#tab-connected-databases table tbody:first tr.empty:first').hide();
- }
- else {
+ } else {
$('#tab-connected-databases table tbody:first tr.empty:first').show();
}
@@ -229,8 +236,7 @@ options.initSpecifiedCredentialFields = function() {
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
- }
- else {
+ } else {
$('#tab-specified-fields table tbody:first tr.empty:first').show();
}
});
@@ -242,7 +248,7 @@ options.initSpecifiedCredentialFields = function() {
const $tr = $trClone.clone(true);
$tr.data('url', url);
$tr.attr('id', 'tr-scf' + counter);
- counter += 1;
+ ++counter;
$tr.children('td:first').text(url);
$('#tab-specified-fields table tbody:first').append($tr);
@@ -250,15 +256,73 @@ options.initSpecifiedCredentialFields = function() {
if ($('#tab-specified-fields table tbody:first tr').length > 2) {
$('#tab-specified-fields table tbody:first tr.empty:first').hide();
- }
- else {
+ } else {
$('#tab-specified-fields table tbody:first tr.empty:first').show();
}
};
+options.initIgnoredSites = function() {
+ $('#dialogDeleteIgnoredSite').modal({keyboard: true, show: false, backdrop: true});
+ $('#tab-ignored-sites tr.clone:first button.delete:first').click(function(e) {
+ e.preventDefault();
+ $('#dialogDeleteIgnoredSite').data('url', $(this).closest('tr').data('url'));
+ $('#dialogDeleteIgnoredSite').data('tr-id', $(this).closest('tr').attr('id'));
+ $('#dialogDeleteIgnoredSite .modal-body:first strong:first').text($(this).closest('tr').children('td:first').text());
+ $('#dialogDeleteIgnoredSite').modal('show');
+ });
+
+ $('#dialogDeleteIgnoredSite .modal-footer:first button.yes:first').click(function(e) {
+ $('#dialogDeleteIgnoredSite').modal('hide');
+
+ const $url = $('#dialogDeleteIgnoredSite').data('url');
+ const $trId = $('#dialogDeleteIgnoredSite').data('tr-id');
+ $('#tab-ignored-sites #' + $trId).remove();
+
+ delete options.settings['ignoredSites'][$url];
+ options.saveSettings();
+
+ if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
+ $('#tab-ignored-sites table tbody:first tr.empty:first').hide();
+ } else {
+ $('#tab-ignored-sites table tbody:first tr.empty:first').show();
+ }
+ });
+
+ const $trClone = $('#tab-ignored-sites table tr.clone:first').clone(true);
+ $trClone.removeClass('clone');
+ let counter = 1;
+ for (let url in options.settings['ignoredSites']) {
+ const $tr = $trClone.clone(true);
+ $tr.data('url', url);
+ $tr.attr('id', 'tr-scf' + counter);
+ ++counter;
+
+ $tr.children('td:first').text(url);
+ $('#tab-ignored-sites table tbody:first').append($tr);
+ }
+
+ if ($('#tab-ignored-sites table tbody:first tr').length > 2) {
+ $('#tab-ignored-sites table tbody:first tr.empty:first').hide();
+ } else {
+ $('#tab-ignored-sites table tbody:first tr.empty:first').show();
+ }
+};
+
options.initAbout = function() {
$('#tab-about em.versionCIP').text(browser.runtime.getManifest().version);
if (isFirefox()) {
$('#chrome-only').remove();
}
+
+ if (navigator.platform === 'MacIntel') {
+ $('#default-user-shortcut').hide();
+ $('#default-pass-shortcut').hide();
+ $('#mac-user-shortcut').show();
+ $('#mac-pass-shortcut').show();
+ } else {
+ $('#mac-user-shortcut').hide();
+ $('#mac-pass-shortcut').hide();
+ $('#default-user-shortcut').show();
+ $('#default-pass-shortcut').show();
+ }
};
diff --git a/keepassxc-browser/popups/popup.html b/keepassxc-browser/popups/popup.html
index 83340d83..fa1bd0cc 100644
--- a/keepassxc-browser/popups/popup.html
+++ b/keepassxc-browser/popups/popup.html
@@ -18,7 +18,7 @@
Choose own credential fields for this page
-
+
You use an old version of KeePassXC.
Please download the latest version from keepassxc.org .
diff --git a/keepassxc-browser/popups/popup.js b/keepassxc-browser/popups/popup.js
index 473d06e8..98511aa9 100644
--- a/keepassxc-browser/popups/popup.js
+++ b/keepassxc-browser/popups/popup.js
@@ -1,3 +1,5 @@
+'use strict';
+
function status_response(r) {
$('#initial-state').hide();
$('#error-encountered').hide();
@@ -6,6 +8,7 @@ function status_response(r) {
$('#configured-and-associated').hide();
$('#configured-not-associated').hide();
$('#lock-database-button').hide();
+ $('#update-available').hide();
if (!r.keePassXCAvailable) {
$('#error-message').html(r.error);
diff --git a/keepassxc-browser/popups/popup_functions.js b/keepassxc-browser/popups/popup_functions.js
index 206938bc..83bd82de 100644
--- a/keepassxc-browser/popups/popup_functions.js
+++ b/keepassxc-browser/popups/popup_functions.js
@@ -1,12 +1,11 @@
+'use strict';
+
var $ = jQuery.noConflict(true);
function updateAvailableResponse(available) {
if (available) {
$('#update-available').show();
}
- else {
- $('#update-available').hide();
- }
}
function initSettings() {
diff --git a/keepassxc-browser/popups/popup_httpauth.js b/keepassxc-browser/popups/popup_httpauth.js
index 9ce92715..cd68d919 100644
--- a/keepassxc-browser/popups/popup_httpauth.js
+++ b/keepassxc-browser/popups/popup_httpauth.js
@@ -1,3 +1,5 @@
+'use strict';
+
$(function() {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({'active': true, 'currentWindow': true}).then((tabs) => {
diff --git a/keepassxc-browser/popups/popup_login.js b/keepassxc-browser/popups/popup_login.js
index eae62d30..b0c2f747 100644
--- a/keepassxc-browser/popups/popup_login.js
+++ b/keepassxc-browser/popups/popup_login.js
@@ -1,3 +1,5 @@
+'use strict';
+
$(function() {
browser.runtime.getBackgroundPage().then((global) => {
browser.tabs.query({'active': true, 'currentWindow': true}).then((tabs) => {
diff --git a/keepassxc-browser/popups/popup_remember.html b/keepassxc-browser/popups/popup_remember.html
index 86ad865c..6d367b41 100644
--- a/keepassxc-browser/popups/popup_remember.html
+++ b/keepassxc-browser/popups/popup_remember.html
@@ -32,6 +32,7 @@
New
Update
Dismiss
+
Never ask for this page
diff --git a/keepassxc-browser/popups/popup_remember.js b/keepassxc-browser/popups/popup_remember.js
index be793288..d1bb15c6 100644
--- a/keepassxc-browser/popups/popup_remember.js
+++ b/keepassxc-browser/popups/popup_remember.js
@@ -1,3 +1,5 @@
+'use strict';
+
var _tab;
function _initialize(tab) {
@@ -78,6 +80,21 @@ function _initialize(tab) {
e.preventDefault();
_close();
});
+
+ $('#btn-ignore').click(function(e) {
+ browser.windows.getCurrent().then((win) => {
+ browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
+ const tab = tabs[0];
+ browser.runtime.getBackgroundPage().then((global) => {
+ browser.tabs.sendMessage(tab.id, {
+ action: 'ignore-site',
+ args: [_tab.credentials.url]
+ });
+ _close();
+ });
+ });
+ });
+ });
}
function _connected_database(db) {
diff --git a/keepassxc-protocol.md b/keepassxc-protocol.md
index 0d0c2fb7..94e7f4a1 100644
--- a/keepassxc-protocol.md
+++ b/keepassxc-protocol.md
@@ -158,7 +158,14 @@ Unencrypted message:
{
"action": "get-logins",
"url": "
",
- "submitUrl": optional
+ "submitUrl": optional,
+ "keys": [
+ {
+ "id": ,
+ "key":
+ },
+ ...
+ ]
}
```