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 @@

logo KeePassXC-
  • General
  • Connected Databases
  • Specified credential fields
  • +
  • Ignored sites
  • About
  • @@ -29,9 +30,9 @@

    logo 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 + UAlt + 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 + IAlt + Shift + I. You can customize these shortcuts on chrome://extensions/configureCommands page

    @@ -39,7 +40,7 @@

    General Settings

    - +
    @@ -55,7 +56,7 @@

    General Settings

    - +
    @@ -73,7 +74,7 @@

    General Settings

    - +
    @@ -130,7 +131,21 @@

    General Settings

    - 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. +
    +
    + +
    +

    +
    +

    +

    + + 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 URLDelete
    No ignored sites found.
    + +
    +

    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 @@ -
    + 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": + }, + ... + ] } ```