From c96724c56afab72d690e1467ea953a9fe679db9b Mon Sep 17 00:00:00 2001
From: varjolintu
Date: Thu, 13 Dec 2018 13:55:52 +0200
Subject: [PATCH] UX improvements
---
keepassxc-browser/_locales/en/messages.json | 156 +--
keepassxc-browser/background/browserAction.js | 279 +-----
keepassxc-browser/background/event.js | 299 +++---
keepassxc-browser/background/httpauth.js | 13 +-
keepassxc-browser/background/init.js | 62 +-
keepassxc-browser/background/keepass.js | 925 +++++++++---------
keepassxc-browser/background/page.js | 187 ++--
keepassxc-browser/content/autocomplete.js | 51 +-
keepassxc-browser/content/banner.js | 390 ++++++++
keepassxc-browser/content/define.js | 60 +-
.../content/keepassxc-browser.js | 735 ++++++++------
keepassxc-browser/content/pwgen.js | 229 ++---
keepassxc-browser/content/sites.js | 35 +
keepassxc-browser/content/ui.js | 94 +-
keepassxc-browser/content/username-field.js | 137 +++
keepassxc-browser/css/banner.css | 138 +++
keepassxc-browser/css/button.css | 47 +-
keepassxc-browser/css/notification.css | 69 ++
keepassxc-browser/css/pwgen.css | 61 +-
keepassxc-browser/css/username.css | 24 +
keepassxc-browser/global.js | 14 +
keepassxc-browser/icons/key.png | Bin 1063 -> 0 bytes
keepassxc-browser/icons/locked.svg | 208 ++++
keepassxc-browser/manifest.json | 12 +-
keepassxc-browser/options/options.html | 52 +-
keepassxc-browser/options/options.js | 110 +--
keepassxc-browser/popups/popup.css | 17 +-
keepassxc-browser/popups/popup.html | 8 +
keepassxc-browser/popups/popup.js | 75 +-
keepassxc-browser/popups/popup_functions.js | 33 +-
keepassxc-browser/popups/popup_httpauth.js | 82 +-
keepassxc-browser/popups/popup_login.js | 88 +-
keepassxc-browser/popups/popup_remember.html | 49 -
keepassxc-browser/popups/popup_remember.js | 290 ------
34 files changed, 2851 insertions(+), 2178 deletions(-)
create mode 100644 keepassxc-browser/content/banner.js
create mode 100644 keepassxc-browser/content/sites.js
create mode 100644 keepassxc-browser/content/username-field.js
create mode 100644 keepassxc-browser/css/banner.css
create mode 100644 keepassxc-browser/css/notification.css
create mode 100644 keepassxc-browser/css/username.css
delete mode 100644 keepassxc-browser/icons/key.png
create mode 100644 keepassxc-browser/icons/locked.svg
delete mode 100644 keepassxc-browser/popups/popup_remember.html
delete mode 100644 keepassxc-browser/popups/popup_remember.js
diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json
index f41ce47..77c6fda 100644
--- a/keepassxc-browser/_locales/en/messages.json
+++ b/keepassxc-browser/_locales/en/messages.json
@@ -21,16 +21,12 @@
},
"contextMenuSaveCredentials": {
"message": "Save credentials",
- "description": "Save credentials using the extension popup icon."
+ "description": "Save credentials using the credential banner."
},
"contextMenuShowPasswordGenerator": {
- "message": "Show password generator",
+ "message": "Show Password Generator",
"description": "Show password generator text."
},
- "rememberCredentialsPopup": {
- "message": "Create or modify the credentials by clicking on the extension icon.",
- "description": "Notification text when saving credentials from the context menu."
- },
"multipleCredentialsDetected": {
"message": "HTTP authentication with multiple credentials detected. Click on the extension icon to choose the correct one.",
"description": "Notification when HTTP Authentication has multiple credentials detected."
@@ -99,6 +95,10 @@
"message": "No logins found.",
"description": "No logins found."
},
+ "errorNotConnected": {
+ "message": "Not connected to KeePassXC.",
+ "description": "Error notification shown when not connected to KeePassXC"
+ },
"passwordGeneratorIcon": {
"message": "Password generator icon",
"description": "Alt attribute text of the password generator icon."
@@ -111,10 +111,6 @@
"message": "Also fill in the next password-field",
"description": "Checkbox text below the password generator input field."
},
- "passwordGeneratorBits": {
- "message": "$1 Bits",
- "description": "Bits of the generated password."
- },
"passwordGeneratorTitle": {
"message": "Password Generator",
"description": "Password generator dialog title."
@@ -131,9 +127,9 @@
"message": "Copy",
"description": "Copy button text in password generator."
},
- "passwordGeneratorFillAndCopy": {
- "message": "Fill and copy",
- "description": "Fill and copy button text in password generator."
+ "passwordGeneratorFill": {
+ "message": "Fill password",
+ "description": "Fill a password in password generator."
},
"passwordGeneratorErrorTooLong": {
"message": "Error: The generated password is longer than the allowed length!",
@@ -159,6 +155,18 @@
"message": "Is KeePassXC running and connected?",
"description": "Password generator error text when KeePassXC is closed."
},
+ "usernameFieldText": {
+ "message": "Fill credentials from KeePassXC",
+ "description": "Username field icon hover text."
+ },
+ "usernameLockedFieldText": {
+ "message": "Database is locked",
+ "description": "Username field icon hover text when database is locked."
+ },
+ "usernameFieldIcon": {
+ "message": "Username field icon",
+ "description": "Username field icon text."
+ },
"defineDismiss": {
"message": "Dismiss",
"description": "Dismiss button text when choosing custom login fields."
@@ -224,11 +232,11 @@
"description": "Shown when no username is set in the credentials."
},
"credentialsNoLoginsFound": {
- "message": "Error: No logins found.",
+ "message": "No logins found.",
"description": "Shown when no credentials are found for the current page."
},
"credentialsMultipleFound": {
- "message": "Error: More than one login was found in KeePassXC!",
+ "message": "More than one login was found in KeePassXC!",
"description": "Alert message when trying to fill username and/or password when multiple credentials are found."
},
"credentialsMultipleFoundSecondLine": {
@@ -236,7 +244,7 @@
"description": "Alert message when trying to fill username and/or password when multiple credentials are found."
},
"credentialsNoUsernameFound": {
- "message": "Error: No credentials for the given username found.",
+ "message": "No credentials for the given username found.",
"description": "Alert message when no credentials are found for the given username."
},
"credentialExpired": {
@@ -287,6 +295,10 @@
"message": "Reopen database",
"description": "Popup reopen database button text."
},
+ "popupUsernameButton": {
+ "message": "Add Username-only option for the site",
+ "description": "Button text for adding Username-only option."
+ },
"popupErrorEncountered": {
"message": "KeePassXC-Browser has encountered an error:",
"description": "A text shown above error message in the popup."
@@ -323,46 +335,78 @@
"message": "KeePassXC-Browser is connected to KeePassXC and has been configured using the following identifier: $1",
"description": "A popuo message when a connection to KeePassXC is succesful."
},
- "popupRememberInfoText": {
+ "popupUsernameFieldDetected": {
+ "message": "Only a single username field was detected. Add the URL to Site Preferences with Username-only option enabled?",
+ "description": "Text shown when page can be added to Site Preferences with Username-only option enabled."
+ },
+ "rememberInfoText": {
"message": "Username or password changed! Save it?",
- "description": "Popup message when username or password has changed."
+ "description": "Message when username or password has changed."
},
- "popupRememberSaving": {
+ "rememberSaving": {
"message": "Credentials will be saved in connected database with identifier $1",
- "description": "A popup message when saving or updating credentials."
+ "description": "A message when saving or updating credentials."
},
- "popupRememberNewUsername": {
+ "rememberNewUsername": {
"message": "The following username is currently not saved: $1",
- "description": "A popup message shown when a new username is detected when saving credentials."
+ "description": "A message shown when a new username is detected when saving credentials."
},
- "popupRememberUsernameExists": {
+ "rememberUsernameExists": {
"message": "The credentials with the used username are marked bold: $1",
- "description": "A popup message shown when a existing username is detected when saving credentials."
+ "description": "A message shown when a existing username is detected when saving credentials."
},
- "popupRememberChooseCredentials": {
+ "rememberChooseCredentials": {
"message": "Please choose the credentials you want to update.",
- "description": "A popup message shown choosing what credentials user wants to update."
+ "description": "A message shown choosing what credentials user wants to update."
},
- "popupRememberChooseGroup": {
+ "rememberChooseGroup": {
"message": "Please choose the group you want to add the new credentials.",
- "description": "A popup message shown choosing what group user wants to use for new credentials."
+ "description": "A message shown choosing what group user wants to use for new credentials."
},
- "popupRememberInfoDefaultGroupNotFound": {
- "message": "Info: Default group created in connected database.",
+ "rememberInfoDefaultGroupNotFound": {
+ "message": "Default group created in connected database.",
"description": "Info message shown when default group set cannot be found."
},
- "popupRememberErrorCreatingNewGroup": {
- "message": "Error: New group(s) cannot be created.",
+ "rememberErrorCreatingNewGroup": {
+ "message": "New group(s) cannot be created.",
"description": "Error message shown when new groups cannot be created."
},
- "popupRememberErrorPasswordNotChanged": {
- "message": "Error: Credentials not updated. The password has not been changed.",
+ "rememberErrorPasswordNotChanged": {
+ "message": "Credentials not updated. The password has not been changed.",
"description": "Error message shown when credential password is not changed."
},
- "popupRememberErrorCannotSaveCredentials": {
- "message": "Error: Credentials cannot be saved or updated.",
+ "rememberErrorCannotSaveCredentials": {
+ "message": "Credentials cannot be saved or updated.",
"description": "Error message shown when credentials cannot be saved or updated."
},
+ "rememberErrorDatabaseClosed": {
+ "message": "Credentials cannot be saved or updated to a closed database.",
+ "description": "Error message shown when credentials cannot be saved or updated and the database is closed."
+ },
+ "rememberCredentialsNotSaved": {
+ "message": "Operation cancelled. Credentials are not updated.",
+ "description": "Message shown when saving operation was cancelled."
+ },
+ "rememberCredentialsSaved": {
+ "message": "New credentials with username $1 saved.",
+ "description": "Message shown when new credentials are saved."
+ },
+ "rememberCredentialsUpdated": {
+ "message": "Credentials with username $1 updated.",
+ "description": "Message shown when new credentials are succesfully updated."
+ },
+ "rememberCredentialsExists": {
+ "message": "Identical credentials already exists. Nothing to update.",
+ "description": "Message shown when new credentials already found in database."
+ },
+ "rememberEmptyUsername": {
+ "message": "(empty)",
+ "description": "Means an empty username."
+ },
+ "rememberNoPassword": {
+ "message": "Please fill the password field before saving credentials.",
+ "description": "Message shown when no password is set when saving credentials."
+ },
"popupLoginText": {
"message": "Select the login information you would like to get entered into the page.",
"description": "A popup message shown when one or multiple credentials are present."
@@ -400,7 +444,7 @@
"description": "Dismiss button text in popup when updating credentials."
},
"popupButtonIgnore": {
- "message": "Never ask for this page",
+ "message": "Don't ask again for this site",
"description": "Ignore button text in popup or in notification when updating credentials."
},
"popupButtonClose": {
@@ -491,18 +535,6 @@
"message": "Reset",
"description": "Reset button text for keyboard shortcuts."
},
- "optionsLabelBlinkTime": {
- "message": "Blink Time:",
- "description": "Blink Time option text."
- },
- "optionsLabelRedirectOffset": {
- "message": "Redirect Offset:",
- "description": "Redirect Offset option text."
- },
- "optionsLabelRedirectAllowance": {
- "message": "Maximum (Number of) Redirects:",
- "description": "RMaximum (Number of) Redirects options text."
- },
"optionsLabelDefaultGroup": {
"message": "Default group for saving new passwords:",
"description": "Default group options text."
@@ -520,8 +552,8 @@
"description": "Default checkbox explanation help text."
},
"optionsCheckboxUsePasswordGenerator": {
- "message": "Activate password generator.",
- "description": "Activate password generator checkbox text."
+ "message": "Activate password generator icons.",
+ "description": "Activate password generator icons checkbox text."
},
"optionsCheckboxAutoRetrieveCredentials": {
"message": "Automatically retrieve credentials.",
@@ -560,7 +592,7 @@
"description": "Save domain only checkbox text."
},
"optionsCheckboxShowLoginNotifications": {
- "message": "Show a notification when new credentials can be saved to the database.",
+ "message": "Show a banner on the page when new credentials can be saved to the database.",
"description": "Show login notifications checkbox text."
},
"optionsCheckboxAutoFillAndSend": {
@@ -591,24 +623,8 @@
"message": "Keyboard shortcuts",
"description": "Keyboard shortcut header text."
},
- "optionsBlinkTimeHelpText": {
- "message": "Maximum time (ms) the icon should blink after detecting new credentials",
- "description": "Blink Time option help text."
- },
- "optionsRedirectOffsetHelpText": {
- "message": "Minimum time (ms) the icon should blink before deactivating due to page redirects.",
- "description": "Redirect Offset option help text."
- },
- "optionsRedirectOffsetHelpTextSecond": {
- "message": "-1 to only use blink time ignoring Maximum Redirects (old behavior)",
- "description": "Redirect Offset option help text, second part."
- },
- "optionsRedirectAllowanceHelpText": {
- "message": "How many pages should the tab cycle through after the redirect offset before deactivating the icon",
- "description": "Redirect Allowance option help text."
- },
"optionsUsePasswordGeneratorHelpText": {
- "message": "Adds a button to password fields for generating a new password.",
+ "message": "Adds an icon to password fields for generating a new password.",
"description": "Password Generator option help text."
},
"optionsUsePasswordGeneratorHelpTextSecond": {
@@ -804,7 +820,7 @@
"description": "Site preferences info text, first part."
},
"optionsSitePreferencesTabHelpTextSecond": {
- "message": "To ignore new/modified credentials on a specific site, add them below or click the blinking KeePassXC-Browser icon and select",
+ "message": "To ignore new/modified credentials on a specific site, add them below or when using the credential banner on the page select",
"description": "Site preferences info text, second part."
},
"optionsSitePreferencesTabHelpTextThird": {
diff --git a/keepassxc-browser/background/browserAction.js b/keepassxc-browser/background/browserAction.js
index ca42b5d..42391a6 100755
--- a/keepassxc-browser/background/browserAction.js
+++ b/keepassxc-browser/background/browserAction.js
@@ -2,18 +2,10 @@
const browserAction = {};
-const BLINK_TIMEOUT_DEFAULT = 7500;
-const BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT = -1;
-const BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT = 1;
-
-// Milliseconds for intervall (e.g. to update browserAction)
-const _interval = 250;
-let _loop = null;
-
-browserAction.show = function(callback, tab) {
+browserAction.show = function(tab) {
let data = {};
if (!page.tabs[tab.id] || page.tabs[tab.id].stack.length === 0) {
- browserAction.showDefault(callback, tab);
+ browserAction.showDefault(tab);
return;
} else {
data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
@@ -32,106 +24,31 @@ browserAction.show = function(callback, tab) {
}
};
-browserAction.update = function(interval) {
- if (!page.tabs[page.currentTabId] || page.tabs[page.currentTabId].stack.length === 0) {
- return;
- }
-
- const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
-
- if (data.visibleForMilliSeconds !== undefined && data.visibleForMilliSeconds !== -1) {
- if (data.visibleForMilliSeconds <= 0) {
- browserAction.stackPop(page.currentTabId);
- browserAction.disableLoop();
- browserAction.show(null, { 'id': page.currentTabId });
- page.clearCredentials(page.currentTabId);
- return;
- }
- data.visibleForMilliSeconds -= interval;
- }
-
- if (data.intervalIcon) {
- data.intervalIcon.counter += 1;
- if (data.intervalIcon.counter < data.intervalIcon.max) {
- return;
- }
-
- data.intervalIcon.counter = 0;
- data.intervalIcon.index += 1;
-
- if (data.intervalIcon.index > data.intervalIcon.icons.length - 1) {
- data.intervalIcon.index = 0;
- }
-
- browser.browserAction.setIcon({
- tabId: page.currentTabId,
- path: '/icons/toolbar/' + browserAction.generateIconName(null, data.intervalIcon.icons[data.intervalIcon.index])
- });
- }
-};
-
-browserAction.showDefault = function(callback, tab) {
+browserAction.showDefault = async function(tab) {
const stackData = {
level: 1,
iconType: 'normal',
popup: 'popup.html'
};
- keepass.isConfigured().then((response) => {
- if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable || page.tabs[tab.id].errorMessage) {
- stackData.iconType = 'cross';
- }
- if (page.tabs[tab.id].loginList.length > 0) {
- stackData.iconType = 'questionmark';
- stackData.popup = 'popup_login.html';
- }
-
- browserAction.stackUnshift(stackData, tab.id);
- browserAction.disableLoop();
- browserAction.show(null, tab);
+ const response = await keepass.isConfigured().catch((err) => {
+ console.log('Error: Cannot show default popup: ' + err);
});
+
+ if (!response || keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
+ stackData.iconType = 'cross';
+ }
+
+ if (page.tabs[tab.id] && page.tabs[tab.id].loginList.length > 0) {
+ stackData.iconType = 'questionmark';
+ stackData.popup = 'popup_login.html';
+ }
+
+ browserAction.stackUnshift(stackData, tab.id);
+ browserAction.show(tab);
};
-browserAction.stackAdd = function(callback, tab, icon, popup, level, push, visibleForMilliSeconds, visibleForPageUpdates, redirectOffset, dontShow) {
- const id = tab.id || page.currentTabId;
-
- if (!level) {
- level = 1;
- }
-
- const stackData = {
- level: level,
- icon: icon
- };
-
- if (popup) {
- stackData.popup = popup;
- }
-
- if (visibleForMilliSeconds !== undefined) {
- stackData.visibleForMilliSeconds = visibleForMilliSeconds;
- }
-
- if (visibleForPageUpdates !== undefined) {
- stackData.visibleForPageUpdates = visibleForPageUpdates;
- }
-
- if (redirectOffset !== undefined) {
- stackData.redirectOffset = redirectOffset;
- }
-
- if (push) {
- browserAction.stackPush(stackData, id);
- } else {
- browserAction.stackUnshift(stackData, id);
- }
-
- if (!dontShow) {
- browserAction.show(null, { 'id': id });
- }
-};
-
-browserAction.removeLevelFromStack = function(callback, tab, level, type, dontShow) {
+browserAction.removeLevelFromStack = function(tab, level, type, dontShow) {
if (!page.tabs[tab.id]) {
return;
}
@@ -156,7 +73,7 @@ browserAction.removeLevelFromStack = function(callback, tab, level, type, dontSh
page.tabs[tab.id].stack = newStack;
if (!dontShow) {
- browserAction.show(callback, tab);
+ browserAction.show(tab);
}
};
@@ -167,132 +84,16 @@ browserAction.stackPop = function(tabId) {
browserAction.stackPush = function(data, tabId) {
const id = tabId || page.currentTabId;
- browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true);
+ browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.push(data);
};
browserAction.stackUnshift = function(data, tabId) {
const id = tabId || page.currentTabId;
- browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true);
+ browserAction.removeLevelFromStack({ 'id': id }, data.level, '<=', true);
page.tabs[id].stack.unshift(data);
};
-
-browserAction.removeRememberPopup = function(callback, tab, removeImmediately) {
- if (!page.tabs[tab.id]) {
- return;
- }
-
- if (page.tabs[tab.id].stack.length === 0) {
- page.clearCredentials(tab.id);
- return;
- }
- const data = page.tabs[tab.id].stack[page.tabs[tab.id].stack.length - 1];
-
- if (removeImmediately || !isNaN(data.visibleForPageUpdates)) {
- const currentMS = Date.now();
- if (removeImmediately || (data.visibleForPageUpdates <= 0 && data.redirectOffset > 0)) {
- browserAction.stackPop(tab.id);
- browserAction.show(null, { 'id': tab.id });
- page.clearCredentials(tab.id);
- } else if (!isNaN(data.visibleForPageUpdates) && data.redirectOffset > 0 && currentMS >= data.redirectOffset) {
- data.visibleForPageUpdates -= 1;
- }
- }
-};
-
-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
- if (settings.sitePreferences !== undefined) {
- for (const site of settings.sitePreferences) {
- if (site.ignore === IGNORE_NORMAL && (site.url === url || siteMatch(site.url, url))) {
- return;
- }
- }
- }
-
- const id = tabId || page.currentTabId;
- let timeoutMinMillis = Number(getValueOrDefault(settings, 'blinkMinTimeout', BLINK_TIMEOUT_REDIRECT_THRESHOLD_TIME_DEFAULT, -1));
-
- if (timeoutMinMillis > 0) {
- timeoutMinMillis += Date.now();
- }
-
- const blinkTimeout = getValueOrDefault(settings, 'blinkTimeout', BLINK_TIMEOUT_DEFAULT, -1);
- const pageUpdateAllowance = getValueOrDefault(settings, 'allowedRedirect', BLINK_TIMEOUT_REDIRECT_COUNT_DEFAULT, 0);
-
- const stackData = {
- visibleForMilliSeconds: blinkTimeout,
- visibleForPageUpdates: pageUpdateAllowance,
- redirectOffset: timeoutMinMillis,
- level: 10,
- intervalIcon: {
- index: 0,
- counter: 0,
- max: 2,
- icons: [ 'icon_remember_red_background.png', 'icon_remember_red_lock.png' ]
- },
- icon: 'icon_remember_red_background.png',
- popup: 'popup_remember.html'
- };
-
- browserAction.activateLoop();
- browserAction.stackPush(stackData, id);
-
- page.tabs[id].credentials = {
- username: username,
- password: password,
- url: url,
- usernameExists: usernameExists,
- list: credentialsList
- };
-
- browserAction.show(null, { 'id': id });
-
- if (page.settings.showLoginNotifications) {
- const message = tr('rememberCredentialsPopup');
- const buttons = [
- {
- 'title': tr('popupButtonClose')
- },
- {
- 'title': tr('popupButtonIgnore')
- }
- ];
-
- browser.notifications.create({
- 'type': 'basic',
- 'iconUrl': browser.extension.getURL('icons/keepassxc_64x64.png'),
- 'title': 'KeePassXC-Browser',
- 'message': message,
- 'buttons': buttons
- });
-
- browser.notifications.onButtonClicked.addListener((notificationId, index) => {
- browser.notifications.clear(notificationId);
- if (index === 1) {
- browserAction.ignoreSite(url);
- }
- });
- }
- });
-};
-
-function getValueOrDefault(settings, key, defaultVal, min) {
- try {
- let val = settings[key];
- if (isNaN(val) || val < min) {
- val = defaultVal;
- }
- return val;
- } catch (e) {
- return defaultVal;
- }
-}
-
browserAction.generateIconName = function(iconType, icon) {
if (icon) {
return icon;
@@ -306,33 +107,17 @@ browserAction.generateIconName = function(iconType, icon) {
return name;
};
-browserAction.ignoreSite = function(url) {
- browser.windows.getCurrent().then(() => {
- // Get current active window
- browser.tabs.query({ 'active': true, 'currentWindow': true }).then((tabs) => {
- const tab = tabs[0];
+browserAction.ignoreSite = async function(url) {
+ await browser.windows.getCurrent();
- // Send the message to the current tab's content script
- browser.runtime.getBackgroundPage().then(() => {
- browser.tabs.sendMessage(tab.id, {
- action: 'ignore_site',
- args: [ url ]
- });
- });
- });
+ // Get current active window
+ const tabs = await browser.tabs.query({ 'active': true, 'currentWindow': true });
+ const tab = tabs[0];
+
+ // Send the message to the current tab's content script
+ await browser.runtime.getBackgroundPage();
+ browser.tabs.sendMessage(tab.id, {
+ action: 'ignore_site',
+ args: [ url ]
});
};
-
-// Interval which updates the browserAction (e.g. blinking icon)
-browserAction.activateLoop = function() {
- if (_loop === null) {
- _loop = setInterval(function() {
- browserAction.update(_interval);
- }, _interval);
- }
-};
-
-browserAction.disableLoop = function() {
- clearInterval(_loop);
- _loop = null;
-};
diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js
index 1394ee6..708cb31 100755
--- a/keepassxc-browser/background/event.js
+++ b/keepassxc-browser/background/event.js
@@ -2,302 +2,263 @@
const kpxcEvent = {};
-kpxcEvent.onMessage = function(request, sender, callback) {
+kpxcEvent.onMessage = async function(request, sender) {
if (request.action in kpxcEvent.messageHandlers) {
- //console.log('onMessage(' + request.action + ') for #' + sender.tab.id);
if (!sender.hasOwnProperty('tab') || sender.tab.id < 1) {
sender.tab = {};
sender.tab.id = page.currentTabId;
}
- kpxcEvent.invoke(kpxcEvent.messageHandlers[request.action], callback, sender.tab.id, request.args);
-
- // onMessage closes channel for callback automatically
- // if this method does not return true
- if (callback !== undefined) {
- return true;
- }
+ return await kpxcEvent.messageHandlers[request.action](sender.tab, request.args);
}
};
-/**
- * Get interesting information about the given tab.
- * Function adapted from AdBlock-Plus.
- *
- * @param {function} handler to call after invoke
- * @param {function} callback to call after handler or null
- * @param {integer} senderTabId
- * @param {array} args
- * @param {bool} secondTime
- * @returns null (asynchronous)
- */
-kpxcEvent.invoke = function(handler, callback, senderTabId, args, secondTime) {
- if (senderTabId < 1) {
- return;
- }
-
- if (!page.tabs[senderTabId]) {
- page.createTabEntry(senderTabId);
- }
-
- // Remove information from no longer existing tabs
- page.removePageInformationFromNotExistingTabs();
-
- browser.tabs.get(senderTabId).then((tab) => {
- if (!tab) {
- return;
- }
-
- if (!tab.url) {
- // Issue 6877: tab URL is not set directly after you opened a window
- // using window.open()
- if (!secondTime) {
- window.setTimeout(function() {
- kpxcEvent.invoke(handler, callback, senderTabId, args, true);
- }, 250);
- }
- return;
- }
-
- if (!page.tabs[tab.id]) {
- page.createTabEntry(tab.id);
- }
-
- args = args || [];
-
- args.unshift(tab);
- args.unshift(callback);
-
- if (handler) {
- handler.apply(this, args);
- } else {
- console.log('undefined handler for tab ' + tab.id);
- }
- }).catch((e) => {
- console.log(e);
- });
-};
-
-kpxcEvent.onShowNotification = function(callback, tab, message) {
- if (page.settings.showNotifications) {
- showNotification(message);
- }
-};
-
-kpxcEvent.showStatus = function(configured, tab, callback) {
+kpxcEvent.showStatus = async function(tab, configured) {
let keyId = null;
if (configured && keepass.databaseHash !== '') {
keyId = keepass.keyRing[keepass.databaseHash].id;
}
- browserAction.showDefault(null, tab);
+ browserAction.showDefault(tab);
const errorMessage = page.tabs[tab.id].errorMessage;
- callback({
+ return {
identifier: keyId,
configured: configured,
databaseClosed: keepass.isDatabaseClosed,
keePassXCAvailable: keepass.isKeePassXCAvailable,
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
associated: keepass.isAssociated(),
- error: errorMessage || null
+ error: errorMessage || null,
+ usernameFieldDetected: page.usernameFieldDetected
+ };
+};
+
+kpxcEvent.onLoadSettings = async function() {
+ return await page.initSettings().catch((err) => {
+ console.log('onLoadSettings error: ' + err);
+ return Promise.reject();
});
};
-kpxcEvent.onLoadSettings = function(callback, tab) {
- page.initSettings().then((settings) => {
- callback(settings);
- }, (err) => {
- console.log('error loading settings: ' + err);
+kpxcEvent.onLoadKeyRing = async function() {
+ const item = await browser.storage.local.get({ 'keyRing': {} }).catch((err) => {
+ console.log('kpxcEvent.onLoadKeyRing error: ' + err);
+ return Promise.reject();
});
+
+ keepass.keyRing = item.keyRing;
+ if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
+ keepass.associated = {
+ 'value': false,
+ 'hash': null
+ };
+ }
+
+ return item.keyRing;
};
-kpxcEvent.onLoadKeyRing = function(callback, tab) {
- browser.storage.local.get({ 'keyRing': {} }).then(function(item) {
- keepass.keyRing = item.keyRing;
- if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
- keepass.associated = {
- 'value': false,
- 'hash': null
- };
- }
- callback(item.keyRing);
- }, (err) => {
- console.log('error loading keyRing: ' + err);
- });
+kpxcEvent.onSaveSettings = async function(tab, args = []) {
+ const [ settings ] = args;
+ browser.storage.local.set({ 'settings': settings });
+ kpxcEvent.onLoadSettings(tab);
+ return Promise.resolve();
};
-kpxcEvent.onSaveSettings = function(callback, tab, settings) {
- browser.storage.local.set({ 'settings': settings }).then(function() {
- kpxcEvent.onLoadSettings(callback, tab);
- });
-};
-
-kpxcEvent.onGetStatus = function(callback, tab, internalPoll = false, triggerUnlock = false) {
+kpxcEvent.onGetStatus = async function(tab, args = []) {
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
- if (!internalPoll) {
- keepass.testAssociation((response) => {
+ try {
+ const [ internalPoll = false, triggerUnlock = false ] = args;
+ if (!internalPoll) {
+ const response = await keepass.testAssociation(tab, [ true, triggerUnlock ]);
if (!response) {
- kpxcEvent.showStatus(false, tab, callback);
- return;
+ return kpxcEvent.showStatus(tab, false);
}
+ }
- keepass.isConfigured().then((configured) => {
- kpxcEvent.showStatus(configured, tab, callback);
- });
- }, tab, true, triggerUnlock);
- } else {
- keepass.isConfigured().then((configured) => {
- kpxcEvent.showStatus(configured, tab, callback);
- });
+ const configured = await keepass.isConfigured();
+ return kpxcEvent.showStatus(tab, configured);
+ } catch (err) {
+ console.log('Error: No status shown: ' + err);
+ return Promise.reject();
}
};
-kpxcEvent.onReconnect = async function(callback, tab) {
- const configured = await keepass.reconnect(callback, tab);
+kpxcEvent.onReconnect = async function(tab) {
+ const configured = await keepass.reconnect(tab);
if (configured) {
browser.tabs.sendMessage(tab.id, {
action: 'redetect_fields'
}).catch((err) => {
console.log(err);
+ return;
});
}
- kpxcEvent.showStatus(configured, tab, callback);
+
+ return kpxcEvent.showStatus(tab, configured);
};
-kpxcEvent.lockDatabase = function(callback, tab) {
- keepass.lockDatabase(tab).then(() => {
- kpxcEvent.showStatus(true, tab, callback);
- });
+kpxcEvent.lockDatabase = async function(tab) {
+ try {
+ await keepass.lockDatabase(tab);
+ return kpxcEvent.showStatus(tab, false);
+ } catch (err) {
+ console.log('kpxcEvent.lockDatabase error: ' + err);
+ return false;
+ }
};
-kpxcEvent.onPopStack = function(callback, tab) {
+kpxcEvent.onPopStack = function(tab) {
browserAction.stackPop(tab.id);
- browserAction.show(null, tab);
+ browserAction.show(tab);
+ return Promise.resolve();
};
-kpxcEvent.onGetTabInformation = function(callback, tab) {
+kpxcEvent.onGetTabInformation = function(tab) {
const id = tab.id || page.currentTabId;
- callback(page.tabs[id]);
+ return Promise.resolve(page.tabs[id]);
};
-kpxcEvent.onGetConnectedDatabase = function(callback, tab) {
- callback({
+kpxcEvent.onGetConnectedDatabase = function() {
+ return Promise.resolve({
count: Object.keys(keepass.keyRing).length,
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
});
};
-kpxcEvent.onGetKeePassXCVersions = function(callback, tab) {
+kpxcEvent.onGetKeePassXCVersions = async function(tab) {
if (keepass.currentKeePassXC === '') {
- keepass.getDatabaseHash((res) => {
- callback({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
- }, tab);
- } else {
- callback({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
+ await keepass.getDatabaseHash(tab);
+ return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
}
+
+ return Promise.resolve({ 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version });
};
-kpxcEvent.onCheckUpdateKeePassXC = function(callback, tab) {
+kpxcEvent.onCheckUpdateKeePassXC = async function() {
keepass.checkForNewKeePassXCVersion();
- callback({ current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version });
+ return { current: keepass.currentKeePassXC.version, latest: keepass.latestKeePassXC.version };
};
-kpxcEvent.onUpdateAvailableKeePassXC = function(callback, tab) {
- callback(page.settings.checkUpdateKeePassXC > 0 ? keepass.keePassXCUpdateAvailable() : false);
+kpxcEvent.onUpdateAvailableKeePassXC = async function() {
+ return (page.settings.checkUpdateKeePassXC > 0) ? keepass.keePassXCUpdateAvailable() : false;
};
-kpxcEvent.onRemoveCredentialsFromTabInformation = function(callback, tab) {
+kpxcEvent.onRemoveCredentialsFromTabInformation = function(tab) {
const id = tab.id || page.currentTabId;
page.clearCredentials(id);
+ page.clearSubmittedCredentials();
+ return Promise.resolve();
};
-kpxcEvent.onSetRememberPopup = function(callback, tab, 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) {
+kpxcEvent.onLoginPopup = function(tab, logins) {
const stackData = {
level: 1,
iconType: 'questionmark',
popup: 'popup_login.html'
};
+
browserAction.stackUnshift(stackData, tab.id);
- page.tabs[tab.id].loginList = logins;
- browserAction.show(null, tab);
+
+ if (logins.length > 0) {
+ page.tabs[tab.id].loginList = logins[0];
+ }
+
+ browserAction.show(tab);
+ return Promise.resolve();
};
-kpxcEvent.initHttpAuth = function(callback) {
+kpxcEvent.initHttpAuth = function() {
httpAuth.init();
- callback();
-};
+ return Promise.resolve();
+}
-kpxcEvent.onHTTPAuthPopup = function(callback, tab, data) {
+kpxcEvent.onHTTPAuthPopup = function(tab, data) {
const stackData = {
level: 1,
iconType: 'questionmark',
popup: 'popup_httpauth.html'
};
+
browserAction.stackUnshift(stackData, tab.id);
page.tabs[tab.id].loginList = data;
- browserAction.show(null, tab);
+ browserAction.show(tab);
+ return Promise.resolve();
};
-kpxcEvent.onMultipleFieldsPopup = function(callback, tab) {
+kpxcEvent.onMultipleFieldsPopup = function(tab) {
const stackData = {
level: 1,
iconType: 'normal',
popup: 'popup_multiple-fields.html'
};
+
browserAction.stackUnshift(stackData, tab.id);
- browserAction.show(null, tab);
+ browserAction.show(tab);
+ return Promise.resolve();
};
-kpxcEvent.pageClearLogins = function(callback, tab, alreadyCalled) {
+kpxcEvent.pageClearLogins = function(tab, alreadyCalled) {
if (!alreadyCalled) {
page.clearLogins(tab.id);
}
- callback();
+ return Promise.resolve();
};
-kpxcEvent.pageGetLoginId = function(callback, tab) {
- callback(page.loginId);
+kpxcEvent.pageGetLoginId = function() {
+ return Promise.resolve(page.loginId);
};
-kpxcEvent.pageSetLoginId = function(callback, tab, loginId) {
+kpxcEvent.pageSetLoginId = function(tab, loginId) {
page.loginId = loginId;
+ return Promise.resolve();
+};
+
+kpxcEvent.pageClearSubmitted = function() {
+ page.clearSubmittedCredentials();
+ return Promise.resolve();
+}
+
+kpxcEvent.pageGetSubmitted = function() {
+ return Promise.resolve(page.submittedCredentials);
+};
+
+kpxcEvent.pageSetSubmitted = function(tab, args = []) {
+ const [ submitted, username, password, url, oldCredentials ] = args;
+ page.setSubmittedCredentials(submitted, username, password, url, oldCredentials);
+ return Promise.resolve();
+};
+
+kpxcEvent.onUsernameFieldDetected = function(tab, args = []) {
+ page.usernameFieldDetected = args[0];
};
// All methods named in this object have to be declared BEFORE this!
kpxcEvent.messageHandlers = {
'add_credentials': keepass.addCredentials,
'associate': keepass.associate,
+ 'check_database_hash': keepass.checkDatabaseHash,
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
'create_new_group': keepass.createNewGroup,
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
'generate_password': keepass.generatePassword,
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
+ 'get_database_hash': keepass.getDatabaseHash,
'get_database_groups': keepass.getDatabaseGroups,
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
'get_status': kpxcEvent.onGetStatus,
'get_tab_information': kpxcEvent.onGetTabInformation,
'init_http_auth': kpxcEvent.initHttpAuth,
+ 'is_connected': keepass.getIsKeePassXCAvailable,
'load_keyring': kpxcEvent.onLoadKeyRing,
'load_settings': kpxcEvent.onLoadSettings,
'lock-database': kpxcEvent.lockDatabase,
'page_clear_logins': kpxcEvent.pageClearLogins,
+ 'page_clear_submitted': kpxcEvent.pageClearSubmitted,
'page_get_login_id': kpxcEvent.pageGetLoginId,
+ 'page_get_submitted': kpxcEvent.pageGetSubmitted,
'page_set_login_id': kpxcEvent.pageSetLoginId,
+ 'page_set_submitted': kpxcEvent.pageSetSubmitted,
'pop_stack': kpxcEvent.onPopStack,
'popup_login': kpxcEvent.onLoginPopup,
'popup_multiple-fields': kpxcEvent.onMultipleFieldsPopup,
@@ -306,9 +267,7 @@ kpxcEvent.messageHandlers = {
'retrieve_credentials': keepass.retrieveCredentials,
'show_default_browseraction': browserAction.showDefault,
'update_credentials': keepass.updateCredentials,
+ 'username_field_detected': kpxcEvent.onUsernameFieldDetected,
'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 367dc43..0389247 100755
--- a/keepassxc-browser/background/httpauth.js
+++ b/keepassxc-browser/background/httpauth.js
@@ -47,11 +47,10 @@ httpAuth.handleRequestCallback = function(details, callback) {
httpAuth.processPendingCallbacks(details, callback, callback);
};
-httpAuth.retrieveCredentials = function(tabId, url, submitUrl, forceCallback) {
- return new Promise((resolve, reject) => {
- keepass.retrieveCredentials((logins) => {
- resolve(logins);
- }, tabId, url, submitUrl, forceCallback, false, true);
+httpAuth.retrieveCredentials = async function(tabId, url, submitUrl) {
+ return await keepass.retrieveCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
+ console.log('httpAuth.retrieveCredentials error: ' + err);
+ return Promise.reject();
});
};
@@ -69,7 +68,7 @@ httpAuth.processPendingCallbacks = async function(details, resolve, reject) {
details.searchUrl = (details.isProxy && details.proxyUrl) ? details.proxyUrl : details.url;
- const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl, true);
+ const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
httpAuth.loginOrShowCredentials(logins, details, resolve, reject);
};
@@ -87,7 +86,7 @@ httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
if (page.settings.showNotifications) {
showNotification(tr('multipleCredentialsDetected'));
}
- kpxcEvent.onHTTPAuthPopup(null, { 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
+ kpxcEvent.onHTTPAuthPopup({ 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
}
} else {
reject({ cancel: false }); // No logins found
diff --git a/keepassxc-browser/background/init.js b/keepassxc-browser/background/init.js
index a74a732..0eee148 100644
--- a/keepassxc-browser/background/init.js
+++ b/keepassxc-browser/background/init.js
@@ -1,15 +1,17 @@
'use strict';
-keepass.migrateKeyRing().then(() => {
- page.initSettings().then(() => {
- page.initOpenedTabs().then(() => {
- httpAuth.init();
- keepass.reconnect().then(() => {
- keepass.enableAutomaticReconnect();
- });
- });
- });
-});
+(async () => {
+ try {
+ await keepass.migrateKeyRing();
+ await page.initSettings();
+ await page.initOpenedTabs();
+ await httpAuth.init();
+ await keepass.reconnect();
+ await keepass.enableAutomaticReconnect();
+ } catch (e) {
+ console.log('init.js failed');
+ }
+})();
/**
* Generate information structure for created tab and invoke all needed
@@ -18,10 +20,12 @@ keepass.migrateKeyRing().then(() => {
*/
browser.tabs.onCreated.addListener((tab) => {
if (tab.id > 0) {
- //console.log('browser.tabs.onCreated(' + tab.id+ ')');
if (tab.selected) {
page.currentTabId = tab.id;
- kpxcEvent.invoke(page.switchTab, null, tab.id, []);
+ if (!page.tabs[tab.id]) {
+ page.createTabEntry(tab.id);
+ }
+ page.switchTab(tab);
}
}
});
@@ -43,20 +47,24 @@ browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
* Invoke functions to retrieve credentials for focused tab
* @param {object} activeInfo
*/
-browser.tabs.onActivated.addListener((activeInfo) => {
+browser.tabs.onActivated.addListener(async function(activeInfo) {
// Remove possible credentials from old tab information
page.clearCredentials(page.currentTabId, true);
- browserAction.removeRememberPopup(null, { 'id': page.currentTabId }, true);
- browser.tabs.get(activeInfo.tabId).then((info) => {
+ try {
+ const info = await browser.tabs.get(activeInfo.tabId);
if (info && info.id) {
page.currentTabId = info.id;
if (info.status === 'complete') {
- //console.log('kpxcEvent.invoke(page.switchTab, null, '+info.id + ', []);');
- kpxcEvent.invoke(page.switchTab, null, info.id, []);
+ if (!page.tabs[info.id]) {
+ page.createTabEntry(info.id);
+ }
+ page.switchTab(info);
}
}
- });
+ } catch (err) {
+ console.log('Error: ' + err);
+ }
});
/**
@@ -71,8 +79,10 @@ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
}
if (changeInfo.status === 'complete') {
- browserAction.showDefault(null, tab);
- kpxcEvent.invoke(browserAction.removeRememberPopup, null, tabId, []);
+ browserAction.showDefault(tab);
+ if (!page.tabs[tab.id]) {
+ page.createTabEntry(tab.id);
+ }
}
});
@@ -82,7 +92,6 @@ const contextMenuItems = [
{ title: tr('contextMenuFillUsernameAndPassword'), action: 'fill_username_password' },
{ title: tr('contextMenuFillPassword'), action: 'fill_password' },
{ title: tr('contextMenuFillTOTP'), action: 'fill_totp' },
- { title: tr('contextMenuShowPasswordGeneratorIcons'), action: 'activate_password_generator' },
{ title: tr('contextMenuShowPasswordGenerator'), action: 'show_password_generator' },
{ title: tr('contextMenuSaveCredentials'), action: 'remember_credentials' }
];
@@ -109,12 +118,11 @@ for (const item of contextMenuItems) {
}
// Listen for keyboard shortcuts specified by user
-browser.commands.onCommand.addListener((command) => {
+browser.commands.onCommand.addListener(async (command) => {
if (contextMenuItems.some(e => e.action === command)) {
- browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
- if (tabs.length) {
- browser.tabs.sendMessage(tabs[0].id, { action: command });
- }
- });
+ const tabs = await browser.tabs.query({ active: true, currentWindow: true });
+ if (tabs.length) {
+ browser.tabs.sendMessage(tabs[0].id, { action: command });
+ }
}
});
diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js
index 7f6a09c..030a71a 100755
--- a/keepassxc-browser/background/keepass.js
+++ b/keepassxc-browser/background/keepass.js
@@ -6,7 +6,7 @@ keepass.keyPair = { publicKey: null, secretKey: null };
keepass.serverPublicKey = '';
keepass.clientID = '';
keepass.isConnected = false;
-keepass.isDatabaseClosed = false;
+keepass.isDatabaseClosed = true;
keepass.isKeePassXCAvailable = false;
keepass.isEncryptionKeyUnrecognized = false;
keepass.currentKeePassXC = '';
@@ -128,26 +128,23 @@ keepass.sendNativeMessage = function(request, enableTimeout = false) {
});
};
-keepass.addCredentials = function(callback, tab, username, password, url, group, groupUuid) {
- keepass.updateCredentials(callback, tab, null, username, password, url, group, groupUuid);
+keepass.addCredentials = async function(tab, args = []) {
+ const [ username, password, url, group, groupUuid ] = args;
+ return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
};
-keepass.updateCredentials = function(callback, tab, entryId, username, password, url, group, groupUuid) {
- if (tab && page.tabs[tab.id]) {
- page.tabs[tab.id].errorMessage = null;
- }
-
- keepass.testAssociation((taResponse) => {
+keepass.updateCredentials = async function(tab, args = []) {
+ try {
+ const [ entryId, username, password, url, group, groupUuid ] = args;
+ const taResponse = await keepass.testAssociation(tab);
if (!taResponse) {
- browserAction.showDefault(null, tab);
- callback([]);
- return;
+ browserAction.showDefault(tab);
+ return [];
}
const kpAction = kpActions.SET_LOGIN;
- const { dbid } = keepass.getCryptoKey();
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ dbid ] = keepass.getCryptoKey();
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const messageData = {
action: kpAction,
@@ -167,43 +164,44 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password,
messageData.groupUuid = groupUuid;
}
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
-
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback('error');
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- callback(keepass.verifyResponse(parsed, incrementedNonce) ? 'success' : 'error');
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
- callback('error');
- } else {
- browserAction.showDefault(null, tab);
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return 'error';
}
- });
- });
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+
+ // KeePassXC versions lower than 2.5.0 will have an empty parsed.error
+ let successMessage = parsed.error;
+ if (parsed.error === 'success' || parsed.error === '') {
+ successMessage = entryId ? 'updated' : 'created';
+ }
+ return keepass.verifyResponse(parsed, incrementedNonce) ? successMessage : 'error';
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ return 'error';
+ }
+
+ browserAction.showDefault(tab);
+ return [];
+ } catch (err) {
+ console.log('updateCredentials failed: ', err);
+ return [];
+ }
};
-keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallback, triggerUnlock = false, httpAuth = false) {
- keepass.testAssociation((taResponse) => {
+keepass.retrieveCredentials = async function(tab, args = []) {
+ try {
+ const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
+ const taResponse = await keepass.testAssociation(tab, [ false, triggerUnlock ]);
if (!taResponse) {
- browserAction.showDefault(null, tab);
- if (forceCallback) {
- callback([]);
- }
- return;
+ browserAction.showDefault(tab);
+ return [];
}
if (tab && page.tabs[tab.id]) {
@@ -211,16 +209,14 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
}
if (!keepass.isConnected) {
- callback([]);
- return;
+ return [];
}
let entries = [];
const keys = [];
const kpAction = kpActions.GET_LOGINS;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
- const { dbid } = keepass.getCryptoKey();
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
+ const [ dbid ] = keepass.getCryptoKey();
for (const keyHash in keepass.keyRing) {
keys.push({
@@ -244,70 +240,63 @@ keepass.retrieveCredentials = function(callback, tab, url, submiturl, forceCallb
messageData.httpAuth = 'true';
}
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
-
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback([]);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- keepass.setcurrentKeePassXCVersion(parsed.version);
-
- if (keepass.verifyResponse(parsed, incrementedNonce)) {
- entries = parsed.entries;
- keepass.updateLastUsed(keepass.databaseHash);
- if (entries.length === 0) {
- // Questionmark-icon is not triggered, so we have to trigger for the normal symbol
- browserAction.showDefault(null, tab);
- }
- callback(entries);
- } else {
- console.log('RetrieveCredentials for ' + url + ' rejected');
- }
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
- callback([]);
- } else {
- browserAction.showDefault(null, tab);
- callback([]);
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return [];
}
- });
- }, tab, false, triggerUnlock);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+ keepass.setcurrentKeePassXCVersion(parsed.version);
+
+ if (keepass.verifyResponse(parsed, incrementedNonce)) {
+ entries = parsed.entries;
+ keepass.updateLastUsed(keepass.databaseHash);
+ if (entries.length === 0) {
+ // Questionmark-icon is not triggered, so we have to trigger for the normal symbol
+ browserAction.showDefault(tab);
+ }
+
+ return entries;
+ } else {
+ console.log('RetrieveCredentials for ' + url + ' rejected');
+ }
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ return [];
+ }
+
+ browserAction.showDefault(tab);
+ return [];
+ } catch (err) {
+ console.log('retrieveCredentials failed: ', err);
+ return [];
+ }
};
-keepass.generatePassword = function(callback, tab) {
+keepass.generatePassword = async function(tab) {
if (!keepass.isConnected) {
- callback([]);
- return;
+ return [];
}
- keepass.testAssociation((taresponse) => {
- if (!taresponse) {
- browserAction.showDefault(null, tab);
- callback([]);
- return;
+ try {
+ const taResponse = await keepass.testAssociation(tab);
+ if (!taResponse) {
+ browserAction.showDefault(tab);
+ return [];
}
if (!keepass.compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
- callback([]);
- return;
+ return [];
}
let passwords = [];
const kpAction = kpActions.GENERATE_PASSWORD;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const request = {
action: kpAction,
@@ -315,47 +304,50 @@ keepass.generatePassword = function(callback, tab) {
clientID: keepass.clientID
};
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback([]);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- keepass.setcurrentKeePassXCVersion(parsed.version);
-
- if (keepass.verifyResponse(parsed, incrementedNonce)) {
- if (parsed.entries) {
- passwords = parsed.entries;
- keepass.updateLastUsed(keepass.databaseHash);
- } else {
- console.log('No entries returned. Is KeePassXC up-to-date?');
- }
- } else {
- console.log('GeneratePassword rejected');
- }
- callback(passwords);
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return [];
}
- });
- }, tab);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+ keepass.setcurrentKeePassXCVersion(parsed.version);
+
+ if (keepass.verifyResponse(parsed, incrementedNonce)) {
+ if (parsed.entries) {
+ passwords = parsed.entries;
+ keepass.updateLastUsed(keepass.databaseHash);
+ } else {
+ console.log('No entries returned. Is KeePassXC up-to-date?');
+ }
+ } else {
+ console.log('GeneratePassword rejected');
+ }
+
+ return passwords;
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ }
+
+ return passwords;
+ } catch (err) {
+ console.log('generatePassword failed: ', err);
+ return [];
+ }
};
-keepass.associate = function(callback, tab) {
+keepass.associate = async function(tab) {
if (keepass.isAssociated()) {
- callback([]);
- return;
+ return AssociatedAction.ASSOCIATED;
}
- keepass.getDatabaseHash((hash) => {
+ try {
+ await keepass.getDatabaseHash(tab);
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
- callback([]);
- return;
+ return AssociatedAction.NOT_ASSOCIATED;
}
if (tab && page.tabs[tab.id]) {
@@ -364,8 +356,7 @@ keepass.associate = function(callback, tab) {
const kpAction = kpActions.ASSOCIATE;
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const idKeyPair = nacl.box.keyPair();
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
@@ -375,63 +366,61 @@ keepass.associate = function(callback, tab) {
idKey: idKey
};
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID,
- triggerUnlock: 'true'
- };
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID, true);
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- keepass.setcurrentKeePassXCVersion(parsed.version);
- const id = parsed.id;
-
- if (!keepass.verifyResponse(parsed, incrementedNonce)) {
- keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
- } else {
- // Use public key as identification key with older KeePassXC releases
- const savedKey = keepass.compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key;
- keepass.setCryptoKey(id, savedKey); // Save the new identification public key as id key for the database
- keepass.associated.value = true;
- keepass.associated.hash = parsed.hash || 0;
- }
-
- browserAction.show(callback, tab);
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return AssociatedAction.NOT_ASSOCIATED;
}
- });
- }, tab);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+ keepass.setcurrentKeePassXCVersion(parsed.version);
+ const id = parsed.id;
+
+ if (!keepass.verifyResponse(parsed, incrementedNonce)) {
+ keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
+ return AssociatedAction.NOT_ASSOCIATED;
+ } else {
+ // Use public key as identification key with older KeePassXC releases
+ const savedKey = keepass.compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key;
+ keepass.setCryptoKey(id, savedKey); // Save the new identification public key as id key for the database
+ keepass.associated.value = true;
+ keepass.associated.hash = parsed.hash || 0;
+ }
+
+ browserAction.show(tab);
+ return AssociatedAction.NEW_ASSOCIATION;
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ }
+ } catch (err) {
+ console.log('associate failed: ', err);
+ }
+
+ return AssociatedAction.NOT_ASSOCIATED;
};
-keepass.testAssociation = async function(callback, tab, enableTimeout = false, triggerUnlock = false) {
+keepass.testAssociation = async function(tab, args = []) {
if (tab && page.tabs[tab.id]) {
page.tabs[tab.id].errorMessage = null;
}
- keepass.getDatabaseHash((dbHash) => {
+ try {
+ const [ enableTimeout = false, triggerUnlock = false ] = args;
+ const dbHash = await keepass.getDatabaseHash(tab, [ enableTimeout, triggerUnlock ]);
if (!dbHash) {
- callback(false);
return false;
}
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
- callback(false);
return false;
}
if (keepass.isAssociated()) {
- callback(true);
return true;
}
@@ -439,20 +428,17 @@ keepass.testAssociation = async function(callback, tab, enableTimeout = false, t
if (tab && page.tabs[tab.id]) {
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
}
- callback(false);
return false;
}
const kpAction = kpActions.TEST_ASSOCIATE;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
- const { dbid, dbkey } = keepass.getCryptoKey();
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
+ const [ dbid, dbkey ] = keepass.getCryptoKey();
if (dbkey === null || dbid === null) {
if (tab && page.tabs[tab.id]) {
keepass.handleError(tab, kpErrors.NO_SAVED_DATABASES_FOUND);
}
- callback(false);
return false;
}
@@ -462,63 +448,60 @@ keepass.testAssociation = async function(callback, tab, enableTimeout = false, t
key: dbkey
};
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
- keepass.sendNativeMessage(request, enableTimeout).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback(false);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- keepass.setcurrentKeePassXCVersion(parsed.version);
- keepass.isEncryptionKeyUnrecognized = false;
-
- if (!keepass.verifyResponse(parsed, incrementedNonce)) {
- const hash = response.hash || 0;
- keepass.deleteKey(hash);
- keepass.isEncryptionKeyUnrecognized = true;
- keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED);
- keepass.associated.value = false;
- keepass.associated.hash = null;
- } else if (!keepass.isAssociated()) {
- keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
- } else {
- if (tab && page.tabs[tab.id]) {
- delete page.tabs[tab.id].errorMessage;
- }
- }
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
+ const response = await keepass.sendNativeMessage(request, enableTimeout);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return false;
}
- callback(keepass.isAssociated());
- });
- }, tab, enableTimeout, triggerUnlock);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+ keepass.setcurrentKeePassXCVersion(parsed.version);
+ keepass.isEncryptionKeyUnrecognized = false;
+
+ if (!keepass.verifyResponse(parsed, incrementedNonce)) {
+ const hash = response.hash || 0;
+ keepass.deleteKey(hash);
+ keepass.isEncryptionKeyUnrecognized = true;
+ keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED);
+ keepass.associated.value = false;
+ keepass.associated.hash = null;
+ } else if (!keepass.isAssociated()) {
+ keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
+ } else {
+ keepass.isEncryptionKeyUnrecognized = false;
+ if (tab && page.tabs[tab.id]) {
+ delete page.tabs[tab.id].errorMessage;
+ }
+ }
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ }
+
+ return keepass.isAssociated();
+ } catch (err) {
+ console.log('testAssociation failed: ', err);
+ return false;
+ }
};
-keepass.getDatabaseHash = function(callback, tab, enableTimeout = false, triggerUnlock = false) {
+keepass.getDatabaseHash = async function(tab, args = []) {
if (!keepass.isConnected) {
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
- callback([]);
- return;
+ return '';
}
if (!keepass.serverPublicKey) {
keepass.changePublicKeys(tab);
}
+ const [ enableTimeout = false, triggerUnlock = false ] = args;
const kpAction = kpActions.GET_DATABASE_HASH;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const messageData = {
action: kpAction,
@@ -528,28 +511,18 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false, trigger
const encrypted = keepass.encrypt(messageData, nonce);
if (encrypted.length <= 0) {
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
- callback(keepass.databaseHash);
- return;
+ return keepass.databaseHash;
}
- const request = {
- action: kpAction,
- message: encrypted,
- nonce: nonce,
- clientID: keepass.clientID
- };
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
- if (triggerUnlock === true) {
- request.triggerUnlock = 'true';
- }
-
- keepass.sendNativeMessage(request, enableTimeout).then((response) => {
+ try {
+ const response = await keepass.sendNativeMessage(request, enableTimeout);
if (response.message && response.nonce) {
const res = keepass.decrypt(response.message, response.nonce);
if (!res) {
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback('');
- return;
+ return '';
}
const message = nacl.util.encodeUTF8(res);
@@ -559,7 +532,7 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false, trigger
keepass.setcurrentKeePassXCVersion(parsed.version);
keepass.databaseHash = parsed.hash || '';
- if (oldDatabaseHash && oldDatabaseHash !== keepass.databaseHash) {
+ if (oldDatabaseHash && oldDatabaseHash != keepass.databaseHash) {
keepass.associated.value = false;
keepass.associated.hash = null;
}
@@ -572,125 +545,125 @@ keepass.getDatabaseHash = function(callback, tab, enableTimeout = false, trigger
keepass.updateDatabaseHash(parsed.oldHash, parsed.hash);
}
- callback(parsed.hash);
- return;
+ return parsed.hash;
} else if (parsed.errorCode) {
keepass.databaseHash = '';
keepass.isDatabaseClosed = true;
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
- callback(keepass.databaseHash);
- return;
+ return keepass.databaseHash;
+ }
+
+ return keepass.databaseHash;
+ }
+
+ keepass.databaseHash = '';
+ keepass.isDatabaseClosed = true;
+ if (response.message && response.message === '') {
+ keepass.isKeePassXCAvailable = false;
+ keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
+ } else {
+ keepass.handleError(tab, response.errorCode, response.error);
+ }
+ return keepass.databaseHash;
+ } catch (err) {
+ console.log('getDatabaseHash failed: ', err);
+ return keepass.databaseHash;
+ }
+};
+
+keepass.changePublicKeys = async function(tab, enableTimeout = false) {
+ if (!keepass.isConnected) {
+ keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
+ return false;
+ }
+
+ const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
+ const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
+ keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize));
+
+ const request = {
+ action: kpAction,
+ publicKey: key,
+ nonce: nonce,
+ clientID: keepass.clientID
+ };
+
+ try {
+ const response = await keepass.sendNativeMessage(request, enableTimeout);
+ keepass.setcurrentKeePassXCVersion(response.version);
+
+ if (!keepass.verifyKeyResponse(response, key, incrementedNonce)) {
+ if (tab && page.tabs[tab.id]) {
+ keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
+ return false;
}
} else {
- keepass.databaseHash = '';
- keepass.isDatabaseClosed = true;
- if (response.message && response.message === '') {
- keepass.isKeePassXCAvailable = false;
- keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
- } else {
- keepass.handleError(tab, response.errorCode, response.error);
- }
- callback(keepass.databaseHash);
- return;
+ keepass.isKeePassXCAvailable = true;
+ console.log('Server public key: ' + nacl.util.encodeBase64(keepass.serverPublicKey));
}
- });
+
+ return true;
+ } catch (err) {
+ console.log('changePublicKeys failed: ', err);
+ return false;
+ }
};
-keepass.changePublicKeys = function(tab, enableTimeout = false) {
- return new Promise((resolve, reject) => {
- if (!keepass.isConnected) {
- keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
- reject(false);
- }
+keepass.lockDatabase = async function(tab) {
+ if (!keepass.isConnected) {
+ keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
+ return false;
+ }
- const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
- const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
- keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepass.keySize));
+ const kpAction = kpActions.LOCK_DATABASE;
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
- const request = {
- action: kpAction,
- publicKey: key,
- nonce: nonce,
- clientID: keepass.clientID
- };
+ const messageData = {
+ action: kpAction
+ };
- keepass.sendNativeMessage(request, enableTimeout).then((response) => {
- keepass.setcurrentKeePassXCVersion(response.version);
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
- if (!keepass.verifyKeyResponse(response, key, incrementedNonce)) {
- if (tab && page.tabs[tab.id]) {
- keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
- }
- reject(false);
- } else {
- keepass.isKeePassXCAvailable = true;
- console.log('Server public key: ' + nacl.util.encodeBase64(keepass.serverPublicKey));
+ try {
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return false;
}
- resolve(true);
- });
- });
-};
-keepass.lockDatabase = function(tab) {
- return new Promise((resolve, reject) => {
- if (!keepass.isConnected) {
- keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
- reject(false);
- }
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+ keepass.setcurrentKeePassXCVersion(parsed.version);
- const kpAction = kpActions.LOCK_DATABASE;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
-
- const messageData = {
- action: kpAction
- };
-
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
-
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- resolve(false);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
- keepass.setcurrentKeePassXCVersion(parsed.version);
-
- if (keepass.verifyResponse(parsed, incrementedNonce)) {
- keepass.isDatabaseClosed = true;
- keepass.updateDatabase();
-
- // Display error message in the popup
- keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
- resolve(true);
- }
- } else if (response.error && response.errorCode) {
+ if (keepass.verifyResponse(parsed, incrementedNonce)) {
keepass.isDatabaseClosed = true;
- keepass.handleError(tab, response.errorCode, response.error);
+ keepass.updateDatabase();
+
+ // Display error message in the popup
+ keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
+ return true;
}
- resolve(false);
- });
- });
+ } else if (response.error && response.errorCode) {
+ keepass.isDatabaseClosed = true;
+ keepass.handleError(tab, response.errorCode, response.error);
+ }
+
+ return false;
+ } catch (err) {
+ console.log('lockDatabase failed: ', err);
+ return false;
+ }
};
-keepass.getDatabaseGroups = function(callback, tab) {
- keepass.testAssociation((taResponse) => {
+keepass.getDatabaseGroups = async function(tab) {
+ try {
+ const taResponse = await keepass.testAssociation(tab, [ false ]);
if (!taResponse) {
- browserAction.showDefault(null, tab);
- callback([]);
- return;
+ browserAction.showDefault(tab);
+ return [];
}
if (tab && page.tabs[tab.id]) {
@@ -698,65 +671,59 @@ keepass.getDatabaseGroups = function(callback, tab) {
}
if (!keepass.isConnected) {
- callback([]);
- return;
+ return [];
}
let groups = [];
const kpAction = kpActions.GET_DATABASE_GROUPS;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const messageData = {
action: kpAction
};
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
-
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback([]);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
-
- if (keepass.verifyResponse(parsed, incrementedNonce)) {
- groups = parsed.groups;
- groups.defaultGroup = page.settings.defaultGroup;
- groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk;
- keepass.updateLastUsed(keepass.databaseHash);
- callback(groups);
- } else {
- console.log('getDatabaseGroups rejected');
- callback([]);
- }
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
- callback([]);
- } else {
- browserAction.showDefault(null, tab);
- callback([]);
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return [];
}
- });
- }, tab, false);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+
+ if (keepass.verifyResponse(parsed, incrementedNonce)) {
+ groups = parsed.groups;
+ groups.defaultGroup = page.settings.defaultGroup;
+ groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk;
+ keepass.updateLastUsed(keepass.databaseHash);
+ return groups;
+ } else {
+ console.log('getDatabaseGroups rejected');
+ return [];
+ }
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ return [];
+ }
+
+ browserAction.showDefault(tab);
+ return [];
+ } catch (err) {
+ console.log('getDatabaseGroups failed: ', err);
+ return [];
+ }
};
-keepass.createNewGroup = function(callback, tab, groupName) {
- keepass.testAssociation((taResponse) => {
+keepass.createNewGroup = async function(tab, args = []) {
+ try {
+ const [ groupName ] = args;
+ const taResponse = await keepass.testAssociation(tab, [ false ]);
if (!taResponse) {
- browserAction.showDefault(null, tab);
- callback([]);
- return;
+ browserAction.showDefault(tab);
+ return [];
}
if (tab && page.tabs[tab.id]) {
@@ -764,54 +731,47 @@ keepass.createNewGroup = function(callback, tab, groupName) {
}
if (!keepass.isConnected) {
- callback([]);
- return;
+ return [];
}
const kpAction = kpActions.CREATE_NEW_GROUP;
- const nonce = keepass.getNonce();
- const incrementedNonce = keepass.incrementedNonce(nonce);
+ const [ nonce, incrementedNonce ] = keepass.getNonces();
const messageData = {
action: kpAction,
groupName: groupName
};
- const request = {
- action: kpAction,
- message: keepass.encrypt(messageData, nonce),
- nonce: nonce,
- clientID: keepass.clientID
- };
-
- keepass.sendNativeMessage(request).then((response) => {
- if (response.message && response.nonce) {
- const res = keepass.decrypt(response.message, response.nonce);
- if (!res) {
- keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
- callback([]);
- return;
- }
-
- const message = nacl.util.encodeUTF8(res);
- const parsed = JSON.parse(message);
-
- if (keepass.verifyResponse(parsed, incrementedNonce)) {
- keepass.updateLastUsed(keepass.databaseHash);
- callback(parsed);
- } else {
- console.log('getDatabaseGroups rejected');
- callback([]);
- }
- } else if (response.error && response.errorCode) {
- keepass.handleError(tab, response.errorCode, response.error);
- callback([]);
- } else {
- browserAction.showDefault(null, tab);
- callback([]);
+ const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
+ const response = await keepass.sendNativeMessage(request);
+ if (response.message && response.nonce) {
+ const res = keepass.decrypt(response.message, response.nonce);
+ if (!res) {
+ keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
+ return [];
}
- });
- }, tab, false);
+
+ const message = nacl.util.encodeUTF8(res);
+ const parsed = JSON.parse(message);
+
+ if (keepass.verifyResponse(parsed, incrementedNonce)) {
+ keepass.updateLastUsed(keepass.databaseHash);
+ return parsed;
+ }
+
+ console.log('getDatabaseGroups rejected');
+ return [];
+ } else if (response.error && response.errorCode) {
+ keepass.handleError(tab, response.errorCode, response.error);
+ return [];
+ }
+
+ browserAction.showDefault(tab);
+ return [];
+ } catch (err) {
+ console.log('createNewGroup failed: ', err);
+ return [];
+ }
};
keepass.generateNewKeyPair = function() {
@@ -819,20 +779,17 @@ keepass.generateNewKeyPair = function() {
//console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey));
};
-keepass.isConfigured = function() {
- return new Promise((resolve, reject) => {
- if (typeof(keepass.databaseHash) === 'undefined') {
- keepass.getDatabaseHash((hash) => {
- resolve(hash in keepass.keyRing);
- });
- } else {
- resolve(keepass.databaseHash in keepass.keyRing);
- }
- });
+keepass.isConfigured = async function() {
+ if (typeof(keepass.databaseHash) === 'undefined') {
+ const hash = keepass.getDatabaseHash();
+ return hash in keepass.keyRing;
+ }
+
+ return keepass.databaseHash in keepass.keyRing;
};
-keepass.checkDatabaseHash = function(callback, tab) {
- callback(keepass.databaseHash);
+keepass.checkDatabaseHash = async function(tab) {
+ return keepass.databaseHash;
};
keepass.isAssociated = function() {
@@ -995,7 +952,7 @@ keepass.getNonce = function() {
keepass.incrementedNonce = function(nonce) {
const oldNonce = nacl.util.decodeBase64(nonce);
- let newNonce = oldNonce.slice(0);
+ const newNonce = oldNonce.slice(0);
// from libsodium/utils.c
let i = 0;
@@ -1009,6 +966,12 @@ keepass.incrementedNonce = function(nonce) {
return nacl.util.encodeBase64(newNonce);
};
+keepass.getNonces = function() {
+ const nonce = keepass.getNonce();
+ const incrementedNonce = keepass.incrementedNonce(nonce);
+ return [ nonce, incrementedNonce ];
+};
+
keepass.nativeConnect = function() {
console.log('Connecting to native messaging host ' + keepass.nativeHostName);
keepass.nativePort = browser.runtime.connectNative(keepass.nativeHostName);
@@ -1105,7 +1068,7 @@ keepass.getCryptoKey = function() {
let dbkey = null;
let dbid = null;
if (!(keepass.databaseHash in keepass.keyRing)) {
- return { dbid, dbkey };
+ return [ dbid, dbkey ];
}
dbid = keepass.keyRing[keepass.databaseHash].id;
@@ -1114,7 +1077,7 @@ keepass.getCryptoKey = function() {
dbkey = keepass.keyRing[keepass.databaseHash].key;
}
- return { dbid, dbkey };
+ return [ dbid, dbkey ];
};
keepass.setCryptoKey = function(id, key) {
@@ -1131,6 +1094,7 @@ keepass.encrypt = function(input, nonce) {
return nacl.util.encodeBase64(message);
}
}
+
return '';
};
@@ -1149,7 +1113,7 @@ keepass.enableAutomaticReconnect = function() {
}
if (keepass.reconnectLoop === null) {
- keepass.reconnectLoop = setInterval(async() => {
+ keepass.reconnectLoop = setInterval(async () => {
if (!keepass.isKeePassXCAvailable) {
keepass.reconnect();
}
@@ -1162,24 +1126,23 @@ keepass.disableAutomaticReconnect = function() {
keepass.reconnectLoop = null;
};
-keepass.reconnect = function(callback, tab) {
- return new Promise((resolve) => {
+keepass.reconnect = function(tab) {
+ return new Promise(async (resolve) => {
keepass.connectToNative();
keepass.generateNewKeyPair();
- keepass.changePublicKeys(tab, true).then((r) => {
- keepass.getDatabaseHash((gdRes) => {
- if (gdRes !== '' && tab && page.tabs[tab.id]) {
- delete page.tabs[tab.id].errorMessage;
- }
- keepass.testAssociation((associationResponse) => {
- keepass.isConfigured().then((configured) => {
- resolve(true);
- });
- });
- }, tab);
- }).catch((e) => {
- resolve(false);
+ await keepass.changePublicKeys(tab, true).catch((e) => {
+ resolve(false);
});
+
+ const hash = await keepass.getDatabaseHash(tab);
+ if (hash !== '' && tab && page.tabs[tab.id]) {
+ delete page.tabs[tab.id].errorMessage;
+ }
+
+ await keepass.testAssociation();
+ await keepass.isConfigured();
+ keepass.updateDatabaseHashToContent();
+ resolve(true);
});
};
@@ -1187,33 +1150,34 @@ keepass.updatePopup = function(iconType) {
if (page && page.tabs.length > 0) {
const data = page.tabs[page.currentTabId].stack[page.tabs[page.currentTabId].stack.length - 1];
data.iconType = iconType;
- browserAction.show(null, { 'id': page.currentTabId });
+ browserAction.show({ 'id': page.currentTabId });
}
};
// Updates the database hashes to content script
-keepass.updateDatabase = function() {
- keepass.testAssociation((associationResponse) => {
- keepass.isConfigured().then((configured) => {
- keepass.updatePopup(configured ? 'normal' : 'cross');
- keepass.updateDatabaseHashToContent();
- });
- }, null);
+keepass.updateDatabase = async function() {
+ await keepass.testAssociation(null);
+ const configured = await keepass.isConfigured();
+ keepass.updatePopup(configured ? 'normal' : 'cross');
+ keepass.updateDatabaseHashToContent();
};
-keepass.updateDatabaseHashToContent = function() {
- // Send message to content script
- browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
+keepass.updateDatabaseHashToContent = async function() {
+ try {
+ const tabs = await browser.tabs.query({ active: true, currentWindow: true });
if (tabs.length) {
+ // Send message to content script
browser.tabs.sendMessage(tabs[0].id, {
action: 'check_database_hash',
hash: { old: keepass.previousDatabaseHash, new: keepass.databaseHash }
}).catch((err) => {
- console.log(err);
+ console.log('Error: No content script available for this tab.');
});
keepass.previousDatabaseHash = keepass.databaseHash;
}
- });
+ } catch (err) {
+ console.log('updateDatabaseHashToContent failed: ', err);
+ }
};
keepass.compareVersion = function(minimum, current, canBeEqual = true) {
@@ -1225,3 +1189,22 @@ keepass.compareVersion = function(minimum, current, canBeEqual = true) {
const cur = current.split('.', 3).map(s => s.padStart(4, '0')).join('.');
return (canBeEqual ? (min <= cur) : (min < cur));
};
+
+keepass.buildRequest = function(action, encrypted, nonce, clientID, triggerUnlock = false) {
+ const request = {
+ action: action,
+ message: encrypted,
+ nonce: nonce,
+ clientID: clientID
+ };
+
+ if (triggerUnlock) {
+ request.triggerUnlock = 'true';
+ }
+
+ return request;
+};
+
+keepass.getIsKeePassXCAvailable = async function() {
+ return keepass.isKeePassXCAvailable;
+};
diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js
index f161b60..08d6f6c 100755
--- a/keepassxc-browser/background/page.js
+++ b/keepassxc-browser/background/page.js
@@ -4,7 +4,7 @@ const defaultSettings = {
checkUpdateKeePassXC: 3,
autoCompleteUsernames: true,
autoFillAndSend: false,
- usePasswordGenerator: true,
+ usePasswordGeneratorIcons: false,
autoFillSingleEntry: false,
autoSubmit: false,
autoRetrieveCredentials: true,
@@ -17,79 +17,86 @@ const defaultSettings = {
};
var page = {};
-page.tabs = [];
-page.currentTabId = -1;
page.blockedTabs = [];
+page.currentTabId = -1;
page.loginId = -1;
+page.submitted = false;
+page.submittedCredentials = {};
+page.tabs = [];
+page.usernameFieldDetected = false;
-page.initSettings = function() {
- return new Promise((resolve, reject) => {
- browser.storage.local.get({ 'settings': {} }).then((item) => {
- page.settings = item.settings;
- if (!('checkUpdateKeePassXC' in page.settings)) {
- page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
- }
- if (!('autoCompleteUsernames' in page.settings)) {
- page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
- }
- if (!('autoFillAndSend' in page.settings)) {
- page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
- }
- if (!('usePasswordGenerator' in page.settings)) {
- page.settings.usePasswordGenerator = defaultSettings.usePasswordGenerator;
- }
- if (!('autoFillSingleEntry' in page.settings)) {
- page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
- }
- if (!('autoSubmit' in page.settings)) {
- page.settings.autoSubmit = defaultSettings.autoSubmit;
- }
- if (!('autoRetrieveCredentials' in page.settings)) {
- page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
- }
- 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;
- }
- if (!('autoReconnect' in page.settings)) {
- page.settings.autoReconnect = defaultSettings.autoReconnect;
- }
- if (!('defaultGroup' in page.settings)) {
- page.settings.defaultGroup = defaultSettings.defaultGroup;
- }
- if (!('defaultGroupAlwaysAsk' in page.settings)) {
- page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk;
- }
- browser.storage.local.set({ 'settings': page.settings });
- resolve(page.settings);
- });
- });
+page.initSettings = async function() {
+ try {
+ const item = await browser.storage.local.get({ 'settings': {} });
+ page.settings = item.settings;
+
+ if (!('checkUpdateKeePassXC' in page.settings)) {
+ page.settings.checkUpdateKeePassXC = defaultSettings.checkUpdateKeePassXC;
+ }
+ if (!('autoCompleteUsernames' in page.settings)) {
+ page.settings.autoCompleteUsernames = defaultSettings.autoCompleteUsernames;
+ }
+ if (!('autoFillAndSend' in page.settings)) {
+ page.settings.autoFillAndSend = defaultSettings.autoFillAndSend;
+ }
+ if (!('usePasswordGeneratorIcons' in page.settings)) {
+ page.settings.usePasswordGeneratorIcons = defaultSettings.usePasswordGeneratorIcons;
+ }
+ if (!('autoFillSingleEntry' in page.settings)) {
+ page.settings.autoFillSingleEntry = defaultSettings.autoFillSingleEntry;
+ }
+ if (!('autoSubmit' in page.settings)) {
+ page.settings.autoSubmit = defaultSettings.autoSubmit;
+ }
+ if (!('autoRetrieveCredentials' in page.settings)) {
+ page.settings.autoRetrieveCredentials = defaultSettings.autoRetrieveCredentials;
+ }
+ 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;
+ }
+ if (!('autoReconnect' in page.settings)) {
+ page.settings.autoReconnect = defaultSettings.autoReconnect;
+ }
+ if (!('defaultGroup' in page.settings)) {
+ page.settings.defaultGroup = defaultSettings.defaultGroup;
+ }
+ if (!('defaultGroupAlwaysAsk' in page.settings)) {
+ page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk;
+ }
+
+ await browser.storage.local.set({ 'settings': page.settings });
+ return page.settings;
+ } catch (err) {
+ console.log('page.initSettings error: ' + err);
+ return Promise.reject();
+ }
};
-page.initOpenedTabs = function() {
- return new Promise((resolve, reject) => {
- browser.tabs.query({}).then((tabs) => {
- for (const i of tabs) {
- page.createTabEntry(i.id);
- }
+page.initOpenedTabs = async function() {
+ try {
+ const tabs = await browser.tabs.query({});
+ for (const i of tabs) {
+ page.createTabEntry(i.id);
+ }
- // Set initial tab-ID
- browser.tabs.query({ 'active': true, 'currentWindow': true }).then((currentTabs) => {
- if (currentTabs.length === 0) {
- resolve();
- return; // For example: only the background devtools or a popup are opened
- }
- page.currentTabId = currentTabs[0].id;
- browserAction.show(null, currentTabs[0]);
- resolve();
- });
- });
- });
+ // Set initial tab-ID
+ const currentTabs = await browser.tabs.query({ active: true, currentWindow: true });
+ if (currentTabs.length === 0) {
+ return Promise.resolve();
+ }
+ page.currentTabId = currentTabs[0].id;
+ browserAction.show(currentTabs[0]);
+ return Promise.resolve();
+ } catch (err) {
+ console.log('page.initOpenedTabs error: ' + err);
+ return Promise.reject();
+ }
};
page.isValidProtocol = function(url) {
@@ -98,8 +105,8 @@ page.isValidProtocol = function(url) {
return !(url.indexOf('.') === -1 || (protocol !== 'http' && protocol !== 'https' && protocol !== 'ftp' && protocol !== 'sftp'));
};
-page.switchTab = function(callback, tab) {
- browserAction.showDefault(null, tab);
+page.switchTab = function(tab) {
+ browserAction.showDefault(tab);
browser.tabs.sendMessage(tab.id, { action: 'activated_tab' }).catch((e) => {});
};
@@ -108,7 +115,8 @@ page.clearCredentials = function(tabId, complete) {
return;
}
- page.tabs[tabId].credentials = {};
+ page.usernameFieldDetected = false;
+ page.tabs[tabId].credentials = [];
delete page.tabs[tabId].credentials;
if (complete) {
@@ -128,30 +136,43 @@ page.clearLogins = function(tabId) {
page.tabs[tabId].loginList = [];
};
+page.setSubmittedCredentials = function(submitted, username, password, url, oldCredentials) {
+ page.submittedCredentials.submitted = submitted;
+ page.submittedCredentials.username = username;
+ page.submittedCredentials.password = password;
+ page.submittedCredentials.url = url;
+ page.submittedCredentials.oldCredentials = oldCredentials;
+};
+
+page.clearSubmittedCredentials = function() {
+ page.submitted = false;
+ page.submittedCredentials = {};
+};
+
page.createTabEntry = function(tabId) {
page.tabs[tabId] = {
'stack': [],
'errorMessage': null,
'loginList': []
};
+ page.clearSubmittedCredentials();
};
-page.removePageInformationFromNotExistingTabs = function() {
+page.removePageInformationFromNotExistingTabs = async function() {
const rand = Math.floor(Math.random() * 1001);
if (rand === 28) {
- browser.tabs.query({}).then(function(tabs) {
- const tabIds = [];
- const infoIds = Object.keys(page.tabs);
+ const tabs = await browser.tabs.query({});
+ const tabIds = [];
+ const infoIds = Object.keys(page.tabs);
- for (const t of tabs) {
- tabIds[t.id] = true;
- }
+ for (const t of tabs) {
+ tabIds[t.id] = true;
+ }
- for (const i of infoIds) {
- if (!(i in tabIds)) {
- delete page.tabs[i];
- }
+ for (const i of infoIds) {
+ if (!(i in tabIds)) {
+ delete page.tabs[i];
}
- });
+ }
}
};
diff --git a/keepassxc-browser/content/autocomplete.js b/keepassxc-browser/content/autocomplete.js
index 0483cf2..9ac794f 100644
--- a/keepassxc-browser/content/autocomplete.js
+++ b/keepassxc-browser/content/autocomplete.js
@@ -34,13 +34,9 @@ kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit
kpxcAutocomplete.showList = function(inputField) {
kpxcAutocomplete.closeList();
kpxcAutocomplete.input = inputField;
- const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
- // Element position
- const rect = inputField.getBoundingClientRect();
- div.style.top = String((rect.top + document.body.scrollTop) + inputField.offsetHeight) + 'px';
- div.style.left = String((rect.left + document.body.scrollLeft)) + 'px';
- div.style.minWidth = String(inputField.offsetWidth) + 'px';
+ const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
+ kpxcAutocomplete.updatePosition(inputField, div);
div.style.zIndex = '2147483646';
document.body.append(div);
@@ -56,6 +52,10 @@ kpxcAutocomplete.showList = function(inputField) {
// Save index for combination.loginId
const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget);
+ browser.runtime.sendMessage({
+ action: 'page_set_login_id', args: [ index ]
+ });
+
inputField.value = this.getElementsByTagName('input')[0].value;
kpxcAutocomplete.fillPassword(inputField.value, index);
kpxcAutocomplete.closeList();
@@ -188,6 +188,7 @@ kpxcAutocomplete.keyPress = function(e) {
kpxcAutocomplete.fillPassword = function(value, index) {
const fieldId = kpxcAutocomplete.input.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
+
const givenType = kpxcAutocomplete.input.type === 'password' ? 'password' : 'username';
const combination = kpxcFields.getCombination(givenType, fieldId);
combination.loginId = index;
@@ -196,6 +197,18 @@ kpxcAutocomplete.fillPassword = function(value, index) {
kpxcAutocomplete.input.setAttribute('fetched', true);
};
+kpxcAutocomplete.updatePosition = function(inputField, elem) {
+ const div = elem || $('.kpxcAutocomplete-items');
+ if (!div) {
+ return;
+ }
+
+ const rect = inputField.getBoundingClientRect();
+ div.style.top = Pixels((rect.top + document.scrollingElement.scrollTop) + inputField.offsetHeight);
+ div.style.left = Pixels((rect.left + document.scrollingElement.scrollLeft));
+ div.style.minWidth = Pixels(inputField.offsetWidth);
+};
+
// Detect click outside autocomplete
document.addEventListener('click', function(e) {
if (!e.isTrusted) {
@@ -207,7 +220,31 @@ document.addEventListener('click', function(e) {
return;
}
- if (e.target !== kpxcAutocomplete.input && (e.target.nodeName !== kpxcAutocomplete.input.nodeName)) {
+ if (e.target !== kpxcAutocomplete.input &&
+ !e.target.classList.contains('kpxc-username-icon') &&
+ e.target.nodeName !== kpxcAutocomplete.input.nodeName) {
kpxcAutocomplete.closeList(e.target);
}
});
+
+// Handle autocomplete position on window resize
+window.addEventListener('resize', function() {
+ if (!kpxc.settings.autoCompleteUsernames) {
+ return;
+ }
+
+ if (kpxcAutocomplete.input) {
+ kpxcAutocomplete.updatePosition(kpxcAutocomplete.input);
+ }
+});
+
+// Handle autocomplete position on scroll
+window.addEventListener('scroll', function() {
+ if (!kpxc.settings.autoCompleteUsernames) {
+ return;
+ }
+
+ if (kpxcAutocomplete.input) {
+ kpxcAutocomplete.updatePosition(kpxcAutocomplete.input);
+ }
+});
diff --git a/keepassxc-browser/content/banner.js b/keepassxc-browser/content/banner.js
new file mode 100644
index 0000000..49e6102
--- /dev/null
+++ b/keepassxc-browser/content/banner.js
@@ -0,0 +1,390 @@
+'use strict';
+
+const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords';
+
+var kpxcBanner = {};
+kpxcBanner.banner = undefined;
+kpxcBanner.created = false;
+kpxcBanner.credentials = {};
+
+kpxcBanner.destroy = function() {
+ kpxcBanner.created = false;
+ kpxcBanner.credentials = {};
+
+ const dialog = $('.kpxc-banner-dialog');
+ if (dialog) {
+ document.body.removeChild(dialog);
+ }
+
+ browser.runtime.sendMessage({
+ action: 'remove_credentials_from_tab_information'
+ });
+
+ const banners = document.querySelectorAll('.kpxc-banner');
+ if (banners.length > 0) {
+ for (const b of banners) {
+ document.body.removeChild(b);
+ }
+ return;
+ }
+
+ document.body.removeChild(kpxcBanner.banner);
+};
+
+kpxcBanner.create = async function(credentials = {}) {
+ if (!kpxc.settings.showLoginNotifications || kpxcBanner.created) {
+ return;
+ }
+
+ // Check if database is closed
+ const state = await browser.runtime.sendMessage({ action: 'check_database_hash' });
+ if (state === '') {
+ //kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
+ return;
+ }
+
+ // Don't show anything if the site is in the ignore
+ if (kpxc.settings.sitePreferences !== undefined) {
+ for (const site of kpxc.settings.sitePreferences) {
+ if (site.ignore !== IGNORE_NOTHING && (site.url === credentials.url || siteMatch(site.url, credentials.url))) {
+ return;
+ }
+ }
+ }
+
+ kpxcBanner.created = true;
+ kpxcBanner.credentials = credentials;
+
+ const banner = kpxcUI.createElement('div', 'kpxc-banner', { 'id': 'container' });
+ banner.style.zIndex = '2147483646';
+
+ const bannerInfo = kpxcUI.createElement('div', 'banner-info');
+ const bannerButtons = kpxcUI.createElement('div', 'banner-buttons');
+
+ const className = (isFirefox() ? 'kpxc-banner-icon-moz' : 'kpxc-banner-icon');
+ const icon = kpxcUI.createElement('span', className, { 'alt': 'logo' });
+
+ const infoText = kpxcUI.createElement('span', '', {}, tr('rememberInfoText'));
+ const usernameText = kpxcUI.createElement('span', 'small', {}, tr('popupUsername') + ' ');
+ const usernameSpan = kpxcUI.createElement('span', 'small info information-username', {}, credentials.username);
+
+ const newButton = kpxcUI.createElement('button', 'kpxc-button kpxc-green-button', { 'id': 'kpxc-banner-btn-new' }, tr('popupButtonNew'));
+ const updateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-banner-btn-update' }, tr('popupButtonUpdate'));
+ const dismissButton = kpxcUI.createElement('button', 'kpxc-button kpxc-red-button', { 'id': 'kpxc-banner-btn-dismiss' }, tr('popupButtonDismiss'));
+
+ const separator = kpxcUI.createElement('div', 'separator');
+ const ignoreCheckbox = kpxcUI.createElement('input', 'kpxc-checkbox', { type: 'checkbox', name: 'ignoreCheckbox' });
+ const checkboxLabel = kpxcUI.createElement('label', 'kpxc-checkbox-label', { for: 'ignoreCheckbox' }, tr('popupButtonIgnore'));
+
+ // No existing credentials to update --> disable Update button
+ if (credentials.list.length === 0) {
+ updateButton.classList.remove('kpxc-orange-button');
+ updateButton.disabled = true;
+ }
+
+ newButton.addEventListener('click', function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+ kpxcBanner.saveNewCredentials(credentials);
+ });
+
+ updateButton.addEventListener('click', function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+ kpxcBanner.updateCredentials(credentials);
+ });
+
+ dismissButton.addEventListener('click', function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
+ // If a banner dialog is shown, display the main banner
+ const dialog = $('.kpxc-banner-dialog');
+ if (dialog) {
+ $('#kpxc-banner-btn-new').hidden = false;
+ $('#kpxc-banner-btn-update').hidden = false;
+ $('.kpxc-checkbox').disabled = false;
+ document.body.removeChild(dialog);
+ } else {
+ if (ignoreCheckbox.checked) {
+ kpxc.ignoreSite([ window.top.location.href ]);
+ }
+ kpxcBanner.destroy();
+ }
+ });
+
+ kpxcBanner.banner = banner;
+ bannerInfo.appendMultiple(icon, infoText, usernameText, usernameSpan);
+ bannerButtons.appendMultiple(newButton, updateButton, separator, ignoreCheckbox, checkboxLabel, dismissButton);
+ banner.appendMultiple(bannerInfo, bannerButtons);
+ document.body.appendChild(banner);
+};
+
+kpxcBanner.saveNewCredentials = async function(credentials = {}) {
+ const result = await browser.runtime.sendMessage({
+ action: 'get_database_groups'
+ });
+
+ // Only the Root group and no KeePassXC-Browser passwords -> save to default
+ // Or when default group is not set and defaultGroupAskAlways is disabled -> save to default
+ if ((result.groups === undefined || (result.groups.length > 0 && result.groups[0].children.length === 0)) ||
+ (!result.defaultGroupAlwaysAsk && (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP))) {
+ const res = await browser.runtime.sendMessage({
+ action: 'add_credentials',
+ args: [ credentials.username, credentials.password, credentials.url ]
+ });
+ kpxcBanner.verifyResult(res);
+ return;
+ } else if (!result.defaultGroupAlwaysAsk && (result.defaultGroup !== '' || result.defaultGroup !== DEFAULT_BROWSER_GROUP)) {
+ // Another group name has been specified
+ const [ gname, guuid ] = kpxcBanner.getDefaultGroup(result.groups[0].children, result.defaultGroup);
+ if (gname === '' && guuid === '') {
+ // Root group is used -> use the root path
+ if (result.defaultGroup.toLowerCase() === 'root') {
+ result.defaultGroup = '/';
+ }
+
+ // Create a new group
+ const newGroup = await browser.runtime.sendMessage({
+ action: 'create_new_group',
+ args: [ result.defaultGroup ]
+ });
+
+ if (newGroup.name && newGroup.uuid) {
+ const res = await browser.runtime.sendMessage({
+ action: 'add_credentials',
+ args: [ credentials.username, credentials.password, credentials.url, newGroup.name, newGroup.uuid ]
+ });
+ kpxcBanner.verifyResult(res);
+ } else {
+ kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup'));
+ }
+ return;
+ }
+
+ const res = await browser.runtime.sendMessage({
+ action: 'add_credentials',
+ args: [ credentials.username, credentials.password, credentials.url, gname, guuid ]
+ });
+ kpxcBanner.verifyResult(res);
+ return;
+ }
+
+ const addChildren = function(group, parentElement, depth) {
+ ++depth;
+ const padding = depth * 20;
+
+ for (const child of group.children) {
+ const a = createLink(child.name, child.uuid, child.children.length > 0);
+ a.setAttribute('id', 'child');
+ a.style.paddingLeft = Pixels(padding);
+
+ if (parentElement.getAttribute('id') === 'root') {
+ a.setAttribute('id', 'root-child');
+ }
+
+ $('ul#list').appendChild(a);
+ addChildren(child, a, depth);
+ }
+ };
+
+ const createLink = function(group, groupUuid, hasChildren) {
+ const a = kpxcUI.createElement('a', 'list-group-item', { 'href': '#' }, group);
+ a.addEventListener('click', async function(e) {
+ e.preventDefault();
+ if (!e.isTrusted) {
+ return;
+ }
+
+ const res = await browser.runtime.sendMessage({
+ action: 'add_credentials',
+ args: [ credentials.username, credentials.password, credentials.url, group, groupUuid ]
+ });
+
+ kpxcBanner.verifyResult(res);
+ });
+
+ if (hasChildren) {
+ a.textContent = '\u25BE ' + group;
+ }
+ return a;
+ };
+
+ kpxcBanner.createGroupDialog();
+
+ // Create the link list for group selection
+ let depth = 0;
+ for (const g of result.groups) {
+ const a = createLink(g.name, g.uuid, g.children.length > 0);
+ a.setAttribute('id', 'root');
+
+ $('ul#list').appendChild(a);
+ addChildren(g, a, depth);
+ }
+
+ $('.kpxc-banner-dialog').style.display = 'block';
+};
+
+kpxcBanner.updateCredentials = async function(credentials = {}) {
+ // Only one entry which could be updated
+ if (credentials.list.length === 1) {
+ // Use the current username if it's empty
+ if (!credentials.username) {
+ credentials.username = credentials.list[0].login;
+ }
+
+ const res = await browser.runtime.sendMessage({
+ action: 'update_credentials',
+ args: [ credentials.list[0].uuid, credentials.username, credentials.password, credentials.url ]
+ });
+ kpxcBanner.verifyResult(res);
+ } else {
+ await kpxcBanner.createCredentialDialog();
+ $('.kpxc-banner-dialog .username-new .strong').textContent = credentials.username;
+ $('.kpxc-banner-dialog .username-exists .strong').textContent = credentials.username;
+
+ if (credentials.usernameExists) {
+ $('.kpxc-banner-dialog .username-new').style.display = 'none';
+ $('.kpxc-banner-dialog .username-exists').style.display = 'block';
+ } else {
+ $('.kpxc-banner-dialog .username-new').style.display = 'block';
+ $('.kpxc-banner-dialog .username-exists').style.display = 'none';
+ }
+
+ for (let i = 0; i < credentials.list.length; i++) {
+ const a = kpxcUI.createElement('a', 'list-group-item', { 'href': '#', 'entryId': i }, `${credentials.list[i].login} (${credentials.list[i].name})`);
+ a.addEventListener('click', function(e) {
+ e.preventDefault();
+ if (!e.isTrusted) {
+ return;
+ }
+
+ const entryId = e.target.getAttribute('entryId');
+
+ // Use the current username if it's empty
+ if (!credentials.username) {
+ credentials.username = credentials.list[entryId].login;
+ }
+
+ let url = credentials.url;
+ url = (url.length > 50) ? url.substring(0, 50) + '...' : url;
+
+ // Check if the password has changed for the updated credentials
+ browser.runtime.sendMessage({
+ action: 'retrieve_credentials',
+ args: [ url, '', true ] // Sets triggerUnlock to true
+ }).then(async (creds) => {
+ if (!creds || creds.length !== credentials.list.length) {
+ kpxcBanner.verifyResult('error');
+ return;
+ }
+
+ const res = await browser.runtime.sendMessage({
+ action: 'update_credentials',
+ args: [ credentials.list[entryId].uuid, credentials.username, credentials.password, credentials.url ]
+ });
+ kpxcBanner.verifyResult(res);
+ });
+ });
+
+ if (credentials.usernameExists && credentials.username === credentials.list[i].login) {
+ a.style.fontWeight = 'bold';
+ }
+
+ $('ul#list').appendChild(a);
+ }
+
+ $('.kpxc-banner-dialog').style.display = 'block';
+ }
+};
+
+kpxcBanner.verifyResult = function(code) {
+ if (code === 'error') {
+ kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials'));
+ } else if (code === 'created') {
+ kpxcUI.createNotification('success', tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')));
+ } else if (code === 'updated') {
+ kpxcUI.createNotification('success', tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')));
+ } else if (code === 'canceled') {
+ kpxcUI.createNotification('warning', tr('rememberCredentialsNotSaved'));
+ } else {
+ kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
+ }
+ kpxcBanner.destroy();
+};
+
+// Traverse the groups and ensure all paths are found
+kpxcBanner.getDefaultGroup = function(groups, defaultGroup) {
+ const getGroup = function(group, splitted, depth) {
+ ++depth;
+ for (const g of group) {
+ if (g.name === splitted[depth]) {
+ if (splitted.length === (depth + 1)) {
+ return [ g.name, g.uuid ];
+ }
+ return getGroup(g.children, splitted, depth);
+ }
+ }
+ return [ '', '' ];
+ };
+
+ let depth = -1;
+ const splitted = defaultGroup.split('/');
+ return getGroup(groups, splitted, depth);
+};
+
+kpxcBanner.createCredentialDialog = async function() {
+ $('#kpxc-banner-btn-new').hidden = true;
+ $('#kpxc-banner-btn-update').hidden = true;
+ $('.kpxc-checkbox').disabled = true;
+
+ const connectedDatabase = await browser.runtime.sendMessage({
+ action: 'get_connected_database'
+ });
+ const databaseName = connectedDatabase.count > 0 ? connectedDatabase.identifier : '';
+
+ const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
+ const databaseText = kpxcUI.createElement('p');
+ const spanDatabaseText = kpxcUI.createElement('span', '', {}, tr('rememberSaving'));
+ const usernameNew = kpxcUI.createElement('p', 'username-new');
+ const usernameExists = kpxcUI.createElement('p', 'username-exists');
+ const chooseCreds = kpxcUI.createElement('p', '', {}, tr('rememberChooseCredentials'));
+ const list = kpxcUI.createElement('ul', 'list-group', { 'id': 'list' });
+ const spanNewUsername = kpxcUI.createElement('span', '', {}, tr('rememberNewUsername'));
+ const spanUsernameExists = kpxcUI.createElement('span', '', {}, tr('rememberUsernameExists'));
+
+ // Set dialog position
+ dialog.style.top = Pixels(kpxcBanner.banner.offsetHeight);
+ dialog.style.right = '0';
+
+ databaseText.appendChild(spanDatabaseText);
+ const spanName = kpxcUI.createElement('span', 'strong', {}, databaseName);
+ databaseText.append(spanName);
+
+ usernameNew.appendChild(spanNewUsername);
+ usernameExists.appendChild(spanUsernameExists);
+ usernameNew.append(kpxcUI.createElement('span', 'strong'));
+ usernameExists.append(kpxcUI.createElement('span', 'strong'));
+ dialog.appendMultiple(databaseText, usernameNew, usernameExists, chooseCreds, list);
+ document.body.appendChild(dialog);
+};
+
+kpxcBanner.createGroupDialog = function() {
+ $('#kpxc-banner-btn-new').hidden = true;
+ $('#kpxc-banner-btn-update').hidden = true;
+ $('.kpxc-checkbox').disabled = true;
+
+ const dialog = kpxcUI.createElement('div', 'kpxc-banner-dialog');
+ const chooseGroup = kpxcUI.createElement('p', '', {}, tr('rememberChooseGroup'));
+ const list = kpxcUI.createElement('ul', 'list-group', { 'id': 'list' });
+
+ // Set dialog position
+ dialog.style.top = Pixels(kpxcBanner.banner.offsetHeight);
+ dialog.style.right = Pixels(0);
+
+ dialog.appendMultiple(chooseGroup, list);
+ document.body.appendChild(dialog);
+};
diff --git a/keepassxc-browser/content/define.js b/keepassxc-browser/content/define.js
index 33cd2b4..baac773 100644
--- a/keepassxc-browser/content/define.js
+++ b/keepassxc-browser/content/define.js
@@ -134,6 +134,10 @@ kpxcDefine.isFieldSelected = function(kpxcId) {
kpxcDefine.markAllUsernameFields = function(chooser) {
kpxcDefine.eventFieldClick = function(e, elem) {
+ if (!e.isTrusted) {
+ return;
+ }
+
const field = elem || e.currentTarget;
kpxcDefine.selection.username = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-username-field');
@@ -147,6 +151,10 @@ kpxcDefine.markAllUsernameFields = function(chooser) {
kpxcDefine.markAllPasswordFields = function(chooser, more = false) {
kpxcDefine.eventFieldClick = function(e, elem) {
+ if (!e.isTrusted) {
+ return;
+ }
+
const field = elem || e.currentTarget;
kpxcDefine.selection.password = field.getAttribute('data-kpxc-id');
field.classList.add('kpxcDefine-fixed-password-field');
@@ -164,6 +172,10 @@ kpxcDefine.markAllPasswordFields = function(chooser, more = false) {
kpxcDefine.markAllStringFields = function(chooser) {
kpxcDefine.eventFieldClick = function(e, elem) {
+ if (!e.isTrusted) {
+ return;
+ }
+
const field = elem || e.currentTarget;
const value = field.getAttribute('data-kpxc-id');
kpxcDefine.selection.fields[value] = true;
@@ -189,10 +201,10 @@ kpxcDefine.markFields = function(chooser, pattern) {
if (kpxcFields.isVisible(i)) {
const field = kpxcUI.createElement('div', 'kpxcDefine-fixed-field', { 'data-kpxc-id': i.getAttribute('data-kpxc-id') });
const rect = i.getBoundingClientRect();
- field.style.top = rect.top + 'px';
- field.style.left = rect.left + 'px';
- field.style.width = rect.width + 'px';
- field.style.height = rect.height + 'px';
+ field.style.top = Pixels(rect.top);
+ field.style.left = Pixels(rect.left);
+ field.style.width = Pixels(rect.width);
+ field.style.height = Pixels(rect.height);
field.textContent = String(index);
field.addEventListener('click', function(e) {
kpxcDefine.eventFieldClick(e);
@@ -262,7 +274,11 @@ kpxcDefine.prepareStep3 = function() {
$('#kpxcDefine-btn-confirm').style.display = 'inline-block';
};
-kpxcDefine.skip = function() {
+kpxcDefine.skip = function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
if (kpxcDefine.dataStep === 1) {
kpxcDefine.selection.username = null;
kpxcDefine.prepareStep2();
@@ -274,20 +290,32 @@ kpxcDefine.skip = function() {
}
};
-kpxcDefine.again = function() {
+kpxcDefine.again = function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
kpxcDefine.resetSelection();
kpxcDefine.prepareStep1();
kpxcDefine.markAllUsernameFields('#kpxcDefine-fields');
};
-kpxcDefine.more = function() {
+kpxcDefine.more = function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
if (kpxcDefine.dataStep === 2) {
kpxcDefine.prepareStep2();
kpxcDefine.markAllPasswordFields('#kpxcDefine-fields', true);
}
};
-kpxcDefine.confirm = function() {
+kpxcDefine.confirm = async function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
if (kpxcDefine.dataStep !== 3) {
return;
}
@@ -317,7 +345,7 @@ kpxcDefine.confirm = function() {
fields: fieldIds
};
- browser.runtime.sendMessage({
+ await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
@@ -325,7 +353,11 @@ kpxcDefine.confirm = function() {
kpxcDefine.close();
};
-kpxcDefine.discard = function() {
+kpxcDefine.discard = async function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
if (!$('#kpxcDefine-btn-discard')) {
return;
}
@@ -333,12 +365,12 @@ kpxcDefine.discard = function() {
const location = kpxc.getDocumentLocation();
delete kpxc.settings['defined-custom-fields'][location];
- browser.runtime.sendMessage({
+ await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
- browser.runtime.sendMessage({
+ await browser.runtime.sendMessage({
action: 'load_settings'
});
@@ -347,6 +379,10 @@ kpxcDefine.discard = function() {
// Handle the keyboard events
kpxcDefine.keyDown = function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
if (e.key === 'Escape') {
kpxcDefine.close();
} else if (e.key === 'Enter') {
diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js
index fe60ba0..c661bca 100755
--- a/keepassxc-browser/content/keepassxc-browser.js
+++ b/keepassxc-browser/content/keepassxc-browser.js
@@ -1,10 +1,16 @@
'use strict';
+var ManualFill = {
+ NONE: 0,
+ PASS: 1,
+ BOTH: 2
+};
+
// contains already called method names
var _called = {};
_called.retrieveCredentials = false;
_called.clearLogins = false;
-_called.manualFillRequested = 'none';
+_called.manualFillRequested = ManualFill.NONE;
let _singleInputEnabledForPage = false;
const _maximumInputs = 100;
@@ -18,46 +24,21 @@ var _observerIds = [];
let _documentURL = document.location.href;
// These are executed in each frame
-browser.runtime.onMessage.addListener(function(req, sender) {
+browser.runtime.onMessage.addListener(async function(req, sender) {
if ('action' in req) {
if (req.action === 'fill_user_pass_with_specific_login') {
- if (kpxc.credentials[req.id]) {
- let combination = null;
- if (kpxc.u) {
- kpxc.setValueWithChange(kpxc.u, kpxc.credentials[req.id].login);
- combination = kpxcFields.getCombination('username', kpxc.u);
- browser.runtime.sendMessage({
- action: 'page_set_login_id', args: [ req.id ]
- });
- kpxc.u.focus();
- }
- if (kpxc.p) {
- kpxc.setValueWithChange(kpxc.p, kpxc.credentials[req.id].password);
- browser.runtime.sendMessage({
- action: 'page_set_login_id', args: [ req.id ]
- });
- combination = kpxcFields.getCombination('password', kpxc.p);
- }
-
- let list = [];
- if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[req.id].stringFields, list)) {
- kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
- }
- }
+ kpxc.fillWithSpecificLogin(req.id);
} else if (req.action === 'fill_username_password') {
- _called.manualFillRequested = 'both';
- kpxc.receiveCredentialsIfNecessary().then((response) => {
- kpxc.fillInFromActiveElement(false);
- });
+ _called.manualFillRequested = ManualFill.BOTH;
+ await kpxc.receiveCredentialsIfNecessary();
+ kpxc.fillInFromActiveElement(false);
} else if (req.action === 'fill_password') {
- _called.manualFillRequested = 'pass';
- kpxc.receiveCredentialsIfNecessary().then((response) => {
- kpxc.fillInFromActiveElement(false, true); // passOnly to true
- });
+ _called.manualFillRequested = ManualFill.PASS;
+ await kpxc.receiveCredentialsIfNecessary();
+ kpxc.fillInFromActiveElement(false, true); // passOnly to true
} else if (req.action === 'fill_totp') {
- kpxc.receiveCredentialsIfNecessary().then((response) => {
- kpxc.fillInFromActiveElementTOTPOnly(false);
- });
+ await kpxc.receiveCredentialsIfNecessary();
+ kpxc.fillInFromActiveElementTOTPOnly(false);
} else if (req.action === 'clear_credentials') {
kpxcEvents.clearCredentials();
return Promise.resolve();
@@ -68,21 +49,20 @@ browser.runtime.onMessage.addListener(function(req, sender) {
kpxc.ignoreSite(req.args);
} else if (req.action === 'check_database_hash' && 'hash' in req) {
kpxc.detectDatabaseChange(req.hash);
- } else if (req.action === 'activate_password_generator') {
- kpxc.initPasswordGenerator(kpxcFields.getAllFields());
} else if (req.action === 'remember_credentials') {
kpxc.contextMenuRememberCredentials();
} else if (req.action === 'choose_credential_fields') {
kpxcDefine.init();
} else if (req.action === 'redetect_fields') {
- browser.runtime.sendMessage({
+ const response = await browser.runtime.sendMessage({
action: 'load_settings'
- }).then((response) => {
- kpxc.settings = response;
- kpxc.initCredentialFields(true);
});
+ kpxc.settings = response;
+ kpxc.initCredentialFields(true);
} else if (req.action === 'show_password_generator') {
kpxcPassword.trigger();
+ } else if (req.action === 'add_username_only_option') {
+ kpxc.addToSitePreferences();
}
}
});
@@ -98,7 +78,8 @@ function _fs(fieldId) {
}
-var kpxcForm = {};
+const kpxcForm = {};
+kpxcForm.passwordInputs = [];
kpxcForm.init = function(form, credentialFields) {
if (!form.getAttribute('kpxcForm-initialized') && (credentialFields.password || credentialFields.username)) {
@@ -106,7 +87,7 @@ kpxcForm.init = function(form, credentialFields) {
kpxcForm.setInputFields(form, credentialFields);
form.addEventListener('submit', kpxcForm.onSubmit);
- const submitButton = kpxc.getSubmitButton(form);
+ const submitButton = kpxc.getFormSubmitButton(form);
if (submitButton !== undefined) {
submitButton.addEventListener('click', kpxcForm.onSubmit);
}
@@ -117,44 +98,95 @@ kpxcForm.destroy = function(form, credentialFields) {
if (form === false && credentialFields) {
const field = _f(credentialFields.password) || _f(credentialFields.username);
if (field) {
- form = field.closest('form');
+ form = kpxc.getForm(field);
+ if (form && form.length > 0) {
+ form.removeEventListener('submit', kpxcForm.onSubmit);
+ }
}
}
- if (form && form.length > 0) {
- form.removeEventListener('submit', kpxcForm.onSubmit);
- }
+ kpxcForm.passwordInputs = [];
};
kpxcForm.setInputFields = function(form, credentialFields) {
form.setAttribute('kpxcUsername', credentialFields.username);
form.setAttribute('kpxcPassword', credentialFields.password);
+
+ // Save all the password input fields from the form
+ kpxcForm.passwordInputs = Array.from(form.elements).filter(e => e.nodeName === 'INPUT' && e.type === 'password');
};
-kpxcForm.onSubmit = function() {
+kpxcForm.onSubmit = async function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
const form = this.nodeName === 'FORM' ? this : this.form;
const usernameId = form.getAttribute('kpxcUsername');
const passwordId = form.getAttribute('kpxcPassword');
-
+ const usernameField = _f(usernameId);
+ const passwordField = _f(passwordId);
let usernameValue = '';
let passwordValue = '';
- const usernameField = _f(usernameId);
- const passwordField = _f(passwordId);
-
if (usernameField) {
usernameValue = usernameField.value || usernameField.placeholder;
+ } else if (kpxc.credentials.length === 1) {
+ // Single entry found for the page, use the username of it instead of an empty one
+ usernameValue = kpxc.credentials[0].login;
}
- if (passwordField) {
+
+ // Check if the form has three password fields -> a possible password change form
+ if (kpxcForm.passwordInputs.length >= 2) {
+ passwordValue = kpxcForm.getNewPassword();
+ } else if (passwordField) {
+ // Use the combination password field instead
passwordValue = passwordField.value;
}
+ // Return if credentials are already found
+ if (kpxc.credentials.some(c => c.login === usernameValue && c.password === passwordValue)) {
+ return;
+ }
+
+ browser.runtime.sendMessage({
+ action: 'page_set_submitted',
+ args: [ true, usernameValue, passwordValue, trimURL(window.top.location.href), kpxc.credentials ]
+ });
+
+ // Show the banner if the page does not reload
kpxc.rememberCredentials(usernameValue, passwordValue);
};
+// Retrieve new password from a form with three elements: Current, New, Repeat New
+kpxcForm.getNewPassword = function() {
+ if (kpxcForm.passwordInputs.length < 2) {
+ return '';
+ }
+
+ // Just two password fields, current and new
+ if (kpxcForm.passwordInputs.length === 2 &&
+ kpxcForm.passwordInputs[0] !== kpxcForm.passwordInputs[1]) {
+ return kpxcForm.passwordInputs[1].value;
+ }
+
+ // Choose the last three password fields. The first ones are almost always for something else
+ const current = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 3].value;
+ const newPass = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 2].value;
+ const repeatNew = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 1].value;
+
+ if (current !== newPass && newPass !== '' && newPass === repeatNew) {
+ return newPass;
+ } else if (current === newPass && repeatNew !== newPass) {
+ // Reverse form where current password is at the bottom
+ return repeatNew;
+ }
+
+ return '';
+};
+
var kpxcFields = {};
-
kpxcFields.inputQueryPattern = 'input[type=\'text\'], input[type=\'email\'], input[type=\'password\'], input[type=\'tel\'], input[type=\'number\'], input[type=\'username\'], input:not([type])';
// copied from Sizzle.js
@@ -201,26 +233,6 @@ kpxcFields.prepareId = function(id) {
return (id + '').replace(kpxcFields.rcssescape, kpxcFields.fcssescape);
};
-/**
- * Returns the first parent element satifying the {@code predicate} mapped by {@code resultFn} or else {@code defaultVal}.
- * @param {HTMLElement} element The start element (excluded, starting with the parents)
- * @param {function} predicate Matcher for the element to find, type (HTMLElement) => boolean
- * @param {function} resultFn Callback function of type (HTMLElement) => {*} called for the first matching element
- * @param {fun} defaultValFn Fallback return value supplier, if no element matching the predicate can be found
- */
-kpxcFields.traverseParents = function(element, predicate, resultFn = () => true, defaultValFn = () => false) {
- for (let f = element.parentElement; f !== null; f = f.parentElement) {
- if (predicate(f)) {
- return resultFn(f);
- }
- }
- return defaultValFn();
-};
-
-kpxcFields.getOverflowHidden = function(field) {
- return kpxcFields.traverseParents(field, f => f.style.overflow === 'hidden');
-};
-
// Checks if input field is a search field. Attributes or form action containing 'search', or parent element holding
// role="search" will be identified as a search field.
kpxcFields.isSearchField = function(target) {
@@ -234,7 +246,7 @@ kpxcFields.isSearchField = function(target) {
}
// Check closest form
- const closestForm = target.closest('form');
+ const closestForm = kpxc.getForm(target);
if (closestForm) {
// Check form action
const formAction = closestForm.getAttribute('action');
@@ -252,10 +264,8 @@ kpxcFields.isSearchField = function(target) {
}
}
- // Check parent elements for role="search"
- const roleFunc = f => f.getAttribute('role');
- const roleValue = kpxcFields.traverseParents(target, roleFunc, roleFunc, () => null);
- if (roleValue && roleValue === 'search') {
+ // Check parent elements for role='search'
+ if (target.closest('[role~=\'search\']')) {
return true;
}
@@ -290,6 +300,16 @@ kpxcFields.getAllFields = function() {
}
_detectedFields = fields.length;
+
+ // Show add username-only option for the site in popup
+ if (!_singleInputEnabledForPage &&
+ fields.length === 1 && fields[0].getLowerCaseAttribute('type') !== 'password') {
+ browser.runtime.sendMessage({
+ action: 'username_field_detected',
+ args: [ true ]
+ });
+ }
+
return fields;
};
@@ -308,7 +328,7 @@ kpxcFields.getAllCombinations = function(inputs) {
for (const i of inputs) {
if (i) {
- if (i.getAttribute('type') && i.getAttribute('type').toLowerCase() === 'password') {
+ if (i.getLowerCaseAttribute('type') === 'password') {
const uId = (!uField || uField.length < 1) ? null : uField.getAttribute('data-kpxc-id');
const combination = {
@@ -411,7 +431,7 @@ kpxcFields.getUsernameField = function(passwordId, checkDisabled) {
return null;
}
- const form = passwordField.closest('form');
+ const form = kpxc.getForm(passwordField);
let usernameField = null;
// Search all inputs on this one form
@@ -423,10 +443,11 @@ kpxcFields.getUsernameField = function(passwordId, checkDisabled) {
return false; // Break
}
- if (i.getAttribute('type') && i.getAttribute('type').toLowerCase() === 'password') {
+ if (i.getLowerCaseAttribute('type') === 'password') {
return true; // Continue
}
+ kpxcUsernameField.initField(usernameField);
usernameField = i;
}
} else {
@@ -438,7 +459,7 @@ kpxcFields.getUsernameField = function(passwordId, checkDisabled) {
break;
}
- if (i.getAttribute('type') && i.getAttribute('type').toLowerCase() === 'password') {
+ if (i.getLowerCaseAttribute('type') === 'password') {
continue;
}
@@ -470,7 +491,7 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) {
return null;
}
- const form = usernameField.closest('form');
+ const form = kpxc.getForm(usernameField);
let passwordField = null;
// Search all inputs on this one form
@@ -483,10 +504,8 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) {
passwordField = null;
}
- if (kpxc.settings.usePasswordGenerator) {
- kpxcPassword.init();
- kpxcPassword.initField(passwordField);
- }
+ kpxcPassword.init(kpxc.settings.usePasswordGeneratorIcons);
+ kpxcPassword.initField(passwordField);
} else {
// Search all inputs on page
const inputs = kpxcFields.getAllFields();
@@ -497,7 +516,7 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) {
if (i.getAttribute('data-kpxc-id') === usernameId) {
active = true;
}
- if (active && i.getAttribute('type') && i.getAttribute('type').toLowerCase() === 'password') {
+ if (active && i.getLowerCaseAttribute('type') === 'password') {
passwordField = i;
break;
}
@@ -519,7 +538,7 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) {
return passwordField;
};
-kpxcFields.prepareCombinations = function(combinations) {
+kpxcFields.prepareCombinations = async function(combinations) {
for (const c of combinations) {
const pwField = _f(c.password);
// Needed for auto-complete: don't overwrite manually filled-in password field
@@ -530,11 +549,23 @@ kpxcFields.prepareCombinations = function(combinations) {
});
}
- // Initialize form-submit for remembering credentials
const fieldId = c.password || c.username;
const field = _f(fieldId);
+
+ // If no username field is found, handle the single password field as such
+ const usernameField = c.username ? _f(c.username) : field;
+
+ // Change icon for username field
+ const res = await browser.runtime.sendMessage({
+ action: 'get_status',
+ args: [ true ]
+ });
+
+ kpxcUsernameField.initField(usernameField, res.databaseClosed);
+
+ // Initialize form-submit for remembering credentials
if (field) {
- const form = field.closest('form');
+ const form = kpxc.getForm(field);
if (form && form.length > 0) {
kpxcForm.init(form, c);
}
@@ -571,7 +602,8 @@ kpxcFields.useDefinedCredentialFields = function() {
return false;
};
-var kpxcObserverHelper = {};
+const kpxcObserverHelper = {};
+
kpxcObserverHelper.inputTypes = [
'text',
'email',
@@ -610,11 +642,15 @@ kpxcObserverHelper.getInputs = function(target) {
}
});
- // Do not allow more visible inputs than _maximumInputs (default value: 100)
- if (inputFields.length === 0 || inputFields.length > _maximumInputs) {
+ if (inputFields.length === 0) {
return [];
}
+ // Do not allow more visible inputs than _maximumInputs (default value: 100) -> return the first 100
+ if (inputFields.length > _maximumInputs) {
+ return inputFields.slice(0, _maximumInputs);
+ }
+
// Only include input fields that match with kpxcObserverHelper.inputTypes
const inputs = [];
for (const i of inputFields) {
@@ -721,6 +757,12 @@ const observer = new MutationObserver(function(mutations, obs) {
// Handle attributes only if CSS display is modified
if (mut.type === 'attributes') {
+ // Check if some class is changed that folds a form or input field(s)
+ if (mut.attributeName === 'class' && mut.target.querySelectorAll('form input').length > 0) {
+ kpxc.initCredentialFields(true);
+ continue;
+ }
+
const newValue = mut.target.getAttribute(mut.attributeName);
if (newValue && (newValue.includes('display') || newValue.includes('z-index'))) {
if (mut.target.style.display !== 'none') {
@@ -743,7 +785,7 @@ observer.observe(document, {
attributes: true,
childList: true,
characterData: true,
- attributeFilter: [ 'style' ]
+ attributeFilter: [ 'style', 'class' ]
});
var kpxc = {};
@@ -754,13 +796,35 @@ kpxc.url = null;
kpxc.submitUrl = null;
kpxc.credentials = [];
-const initcb = function() {
- browser.runtime.sendMessage({
- action: 'load_settings'
- }).then((response) => {
- kpxc.settings = response;
- kpxc.initCredentialFields();
- });
+const initcb = async function() {
+ try {
+ const settings = await browser.runtime.sendMessage({
+ action: 'load_settings'
+ });
+
+ kpxc.settings = settings;
+ await kpxc.initCredentialFields();
+
+ // Retrieve submitted credentials if available.
+ const creds = await browser.runtime.sendMessage({
+ action: 'page_get_submitted'
+ });
+
+ if (creds && creds.submitted) {
+ // If username field is not set, wait for credentials in kpxc.retrieveCredentialsCallback.
+ if (!creds.username) {
+ return;
+ }
+
+ await browser.runtime.sendMessage({
+ action: 'page_clear_submitted'
+ });
+
+ kpxc.rememberCredentials(creds.username, creds.password, creds.url, creds.oldCredentials);
+ }
+ } catch (err) {
+ console.log('Error: Cannot load extension settings');
+ }
};
if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) {
@@ -776,7 +840,6 @@ kpxc.init = function() {
// Clears all from the content and background scripts, including autocomplete
kpxc.clearAllFromPage = function() {
kpxcEvents.clearCredentials();
-
browser.runtime.sendMessage({
action: 'page_clear_logins'
});
@@ -789,128 +852,128 @@ kpxc.clearAllFromPage = function() {
};
// Switch credentials if database is changed or closed
-kpxc.detectDatabaseChange = function(response) {
+kpxc.detectDatabaseChange = async function(response) {
kpxc.clearAllFromPage();
+ kpxcUsernameField.switchIcon(true);
+
if (document.visibilityState !== 'hidden') {
if (response.new !== '' && response.new !== response.old) {
_called.retrieveCredentials = false;
- browser.runtime.sendMessage({
+ const settings = await browser.runtime.sendMessage({
action: 'load_settings'
- }).then((settings) => {
- kpxc.settings = settings;
- kpxc.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') {
- kpxc.fillInFromActiveElement(false, _called.manualFillRequested === 'pass');
- _called.manualFillRequested = 'none';
- }
});
+ kpxc.settings = settings;
+ await kpxc.initCredentialFields(true);
+ kpxcUsernameField.switchIcon(false); // Unlocked
+
+ // 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 !== ManualFill.NONE) {
+ await kpxc.fillInFromActiveElement(false, _called.manualFillRequested === ManualFill.PASS);
+ _called.manualFillRequested = ManualFill.NONE;
+ }
}
}
};
-kpxc.initCredentialFields = function(forceCall) {
+kpxc.initCredentialFields = async function(forceCall) {
if (_called.initCredentialFields && !forceCall) {
return;
}
_called.initCredentialFields = true;
- browser.runtime.sendMessage({ 'action': 'page_clear_logins', args: [ _called.clearLogins ] }).then(() => {
- _called.clearLogins = true;
+ await browser.runtime.sendMessage({
+ action: 'page_clear_logins',
+ args: [ _called.clearLogins ]
+ });
- // Check site preferences
- kpxc.initializeSitePreferences();
- if (kpxc.settings.sitePreferences) {
- for (const site of kpxc.settings.sitePreferences) {
- try {
- if (siteMatch(site.url, window.top.location.href) || site.url === window.top.location.href) {
- if (site.ignore === IGNORE_FULL) {
- return;
- }
+ _called.clearLogins = true;
- _singleInputEnabledForPage = site.usernameOnly;
+ // Check site preferences
+ kpxc.initializeSitePreferences();
+ if (kpxc.settings.sitePreferences) {
+ for (const site of kpxc.settings.sitePreferences) {
+ try {
+ if (siteMatch(site.url, window.top.location.href) || site.url === window.top.location.href) {
+ if (site.ignore === IGNORE_FULL) {
+ return;
}
- } catch (err) {
- return;
+
+ _singleInputEnabledForPage = site.usernameOnly;
}
+ } catch (err) {
+ return;
}
}
+ }
- const inputs = kpxcFields.getAllFields();
- if (inputs.length === 0) {
- return;
- }
+ const inputs = kpxcFields.getAllFields();
+ if (inputs.length === 0) {
+ return;
+ }
- kpxcFields.prepareVisibleFieldsWithID('select');
- kpxc.initPasswordGenerator(inputs);
+ kpxcFields.prepareVisibleFieldsWithID('select');
+ kpxc.initPasswordGenerator(inputs);
- if (!kpxcFields.useDefinedCredentialFields()) {
- // Get all combinations of username + password fields
- kpxcFields.combinations = kpxcFields.getAllCombinations(inputs);
- }
- kpxcFields.prepareCombinations(kpxcFields.combinations);
+ if (!kpxcFields.useDefinedCredentialFields()) {
+ // Get all combinations of username + password fields
+ kpxcFields.combinations = kpxcFields.getAllCombinations(inputs);
+ }
+ kpxcFields.prepareCombinations(kpxcFields.combinations);
- if (kpxcFields.combinations.length === 0 && inputs.length === 0) {
- browser.runtime.sendMessage({
- action: 'show_default_browseraction'
- });
- return;
- }
+ if (kpxcFields.combinations.length === 0 && inputs.length === 0) {
+ browser.runtime.sendMessage({
+ action: 'show_default_browseraction'
+ });
+ return;
+ }
- kpxc.url = document.location.origin;
- kpxc.submitUrl = kpxc.getFormActionUrl(kpxcFields.combinations[0]);
+ kpxc.url = document.location.origin;
+ kpxc.submitUrl = kpxc.getFormActionUrl(kpxcFields.combinations[0]);
- // Get submitUrl for a single input
- if (!kpxc.submitUrl && kpxcFields.combinations.length === 1 && inputs.length === 1) {
- kpxc.submitUrl = kpxc.getFormActionUrlFromSingleInput(inputs[0]);
- }
+ // Get submitUrl for a single input
+ if (!kpxc.submitUrl && kpxcFields.combinations.length === 1 && inputs.length === 1) {
+ kpxc.submitUrl = kpxc.getFormActionUrlFromSingleInput(inputs[0]);
+ }
- if (kpxc.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (kpxc.url && kpxc.submitUrl)) {
- browser.runtime.sendMessage({
- action: 'retrieve_credentials',
- args: [ kpxc.url, kpxc.submitUrl ]
- }).then(kpxc.retrieveCredentialsCallback).catch((e) => {
- console.log(e);
- });
- } else if (_singleInputEnabledForPage) {
- kpxc.preparePageForMultipleCredentials(kpxc.credentials);
- }
- });
+ if (kpxc.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (kpxc.url && kpxc.submitUrl)) {
+ _called.retrieveCredentials = true;
+ kpxc.retrieveCredentialsCallback(await browser.runtime.sendMessage({
+ action: 'retrieve_credentials',
+ args: [ kpxc.url, kpxc.submitUrl ]
+ }));
+ } else if (_singleInputEnabledForPage) {
+ kpxc.preparePageForMultipleCredentials(kpxc.credentials);
+ }
};
kpxc.initPasswordGenerator = function(inputs) {
- if (kpxc.settings.usePasswordGenerator) {
- kpxcPassword.init();
+ kpxcPassword.init(kpxc.settings.usePasswordGeneratorIcons);
- for (let i = 0; i < inputs.length; i++) {
- if (inputs[i] && inputs[i].getAttribute('type') && inputs[i].getAttribute('type').toLowerCase() === 'password') {
- kpxcPassword.initField(inputs[i], inputs, i);
- }
+ for (let i = 0; i < inputs.length; i++) {
+ if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') {
+ kpxcPassword.initField(inputs[i], inputs, i);
}
}
};
-kpxc.receiveCredentialsIfNecessary = function() {
- return new Promise((resolve, reject) => {
- if (kpxc.credentials.length === 0 && _called.retrieveCredentials === false) {
- browser.runtime.sendMessage({
- action: 'retrieve_credentials',
- args: [ kpxc.url, kpxc.submitUrl, false, true ] // Sets triggerUnlock to true
- }).then((credentials) => {
- // If the database was locked, this is scope never met. In these cases the response is met at kpxc.detectDatabaseChange
- _called.manualFillRequested = 'none';
- kpxc.retrieveCredentialsCallback(credentials, false);
- resolve(credentials);
- });
- } else {
- resolve(kpxc.credentials);
- }
- });
+kpxc.receiveCredentialsIfNecessary = async function() {
+ if (kpxc.credentials.length === 0 && _called.retrieveCredentials === false) {
+ const credentials = await browser.runtime.sendMessage({
+ action: 'retrieve_credentials',
+ args: [ kpxc.url, kpxc.submitUrl, true ] // Sets triggerUnlock to true
+ });
+
+ // If the database was locked, this is scope never met. In these cases the response is met at kpxc.detectDatabaseChange
+ _called.manualFillRequested = ManualFill.NONE;
+ kpxc.retrieveCredentialsCallback(credentials, false);
+ return credentials;
+ }
+
+ return kpxc.credentials;
};
-kpxc.retrieveCredentialsCallback = function(credentials, dontAutoFillIn) {
+kpxc.retrieveCredentialsCallback = async function(credentials, dontAutoFillIn) {
if (kpxcFields.combinations.length > 0) {
kpxc.u = _f(kpxcFields.combinations[0].username);
kpxc.p = _f(kpxcFields.combinations[0].password);
@@ -918,8 +981,20 @@ kpxc.retrieveCredentialsCallback = function(credentials, dontAutoFillIn) {
if (credentials && credentials.length > 0) {
kpxc.credentials = credentials;
- kpxc.prepareFieldsForCredentials(!Boolean(dontAutoFillIn));
- _called.retrieveCredentials = true;
+ kpxc.prepareFieldsForCredentials(!dontAutoFillIn);
+ }
+
+ // Retrieve submitted credentials if available
+ const creds = await browser.runtime.sendMessage({
+ action: 'page_get_submitted'
+ });
+
+ if (creds && creds.submitted) {
+ await browser.runtime.sendMessage({
+ action: 'page_clear_submitted'
+ });
+
+ kpxc.rememberCredentials(creds.username, creds.password, creds.url, creds.oldCredentials);
}
};
@@ -959,6 +1034,10 @@ kpxc.prepareFieldsForCredentials = function(autoFillInForSingle) {
};
kpxc.preparePageForMultipleCredentials = function(credentials) {
+ if (credentials.length === 0) {
+ return;
+ }
+
function getLoginText(credential) {
const visibleLogin = (credential.login.length > 0) ? credential.login : tr('credentialsNoUsername');
if (credential.expired && credential.expired === 'true') {
@@ -1008,6 +1087,17 @@ kpxc.preparePageForMultipleCredentials = function(credentials) {
}
};
+// Returns the form that includes the inputField
+kpxc.getForm = function(inputField) {
+ for (const f of document.forms) {
+ for (const e of f.elements) {
+ if (e === inputField) {
+ return f;
+ }
+ }
+ }
+};
+
kpxc.getFormActionUrl = function(combination) {
if (!combination) {
return null;
@@ -1018,11 +1108,11 @@ kpxc.getFormActionUrl = function(combination) {
return null;
}
- const form = field.closest('form');
+ const form = kpxc.getForm(field);
let action = null;
if (form && form.length > 0) {
- action = form[0].action;
+ action = form.action;
}
if (typeof(action) !== 'string' || action === '') {
@@ -1047,7 +1137,7 @@ kpxc.getFormActionUrlFromSingleInput = function(field) {
};
// Get the form submit button instead if action URL is same as the page itself
-kpxc.getSubmitButton = function(form) {
+kpxc.getFormSubmitButton = function(form) {
const action = kpxc.submitUrl || form.action;
if (action.includes(document.location.origin + document.location.pathname)) {
for (const i of form.elements) {
@@ -1066,7 +1156,7 @@ kpxc.getSubmitButton = function(form) {
return undefined;
};
-kpxc.fillInCredentials = function(combination, onlyPassword, suppressWarnings) {
+kpxc.fillInCredentials = async function(combination, onlyPassword, suppressWarnings) {
const action = kpxc.getFormActionUrl(combination);
const u = _f(combination.username);
const p = _f(combination.password);
@@ -1076,9 +1166,9 @@ kpxc.fillInCredentials = function(combination, onlyPassword, suppressWarnings) {
const fieldId = combination.password || combination.username;
const field = _f(fieldId);
if (field) {
- const form2 = field.closest('form');
- if (form2 && form2.length > 0) {
- kpxcForm.init(form2, combination);
+ const form = kpxc.getForm(field);
+ if (form && form.length > 0) {
+ kpxcForm.init(form, combination);
}
}
}
@@ -1096,13 +1186,13 @@ kpxc.fillInCredentials = function(combination, onlyPassword, suppressWarnings) {
kpxc.url = document.location.origin;
kpxc.submitUrl = action;
- browser.runtime.sendMessage({
+ const credentials = await browser.runtime.sendMessage({
action: 'retrieve_credentials',
- args: [ kpxc.url, kpxc.submitUrl, false, true ]
- }).then((credentials) => {
- kpxc.retrieveCredentialsCallback(credentials, true);
- kpxc.fillIn(combination, onlyPassword, suppressWarnings);
+ args: [ kpxc.url, kpxc.submitUrl, true ] // Sets triggerUnlock to true
});
+
+ kpxc.retrieveCredentialsCallback(credentials, true);
+ kpxc.fillIn(combination, onlyPassword, suppressWarnings);
}
};
@@ -1132,11 +1222,7 @@ kpxc.fillInFromActiveElement = function(suppressWarnings, passOnly = false) {
if (passOnly) {
if (!_f(combination.password)) {
- const message = tr('fieldsNoPasswordField');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ kpxcUI.createNotification('warning', tr('fieldsNoPasswordField'));
return;
}
}
@@ -1146,30 +1232,30 @@ kpxc.fillInFromActiveElement = function(suppressWarnings, passOnly = false) {
kpxc.fillInCredentials(combination, passOnly, suppressWarnings);
};
-kpxc.fillInFromActiveElementTOTPOnly = function() {
+kpxc.fillInFromActiveElementTOTPOnly = async function() {
const el = document.activeElement;
kpxcFields.setUniqueId(el);
const fieldId = kpxcFields.prepareId(el.getAttribute('data-kpxc-id'));
- browser.runtime.sendMessage({
+ const index = await browser.runtime.sendMessage({
action: 'page_get_login_id'
- }).then((pos) => {
- if (pos >= 0 && kpxc.credentials[pos]) {
- // Check the value from stringFields (to be removed)
- const currentField = _fs(fieldId);
- if (kpxc.credentials[pos].stringFields && kpxc.credentials[pos].stringFields.length > 0) {
- const stringFields = kpxc.credentials[pos].stringFields;
- for (const s of stringFields) {
- const val = s['KPH: {TOTP}'];
- if (val) {
- kpxc.setValue(currentField, val);
- }
- }
- } else if (kpxc.credentials[pos].totp && kpxc.credentials[pos].totp.length > 0) {
- kpxc.setValue(currentField, kpxc.credentials[pos].totp);
- }
- }
});
+
+ if (index >= 0 && kpxc.credentials[index]) {
+ // Check the value from stringFields (to be removed)
+ const currentField = _fs(fieldId);
+ if (kpxc.credentials[index].stringFields && kpxc.credentials[index].stringFields.length > 0) {
+ const stringFields = kpxc.credentials[index].stringFields;
+ for (const s of stringFields) {
+ const val = s['KPH: {TOTP}'];
+ if (val) {
+ kpxc.setValue(currentField, val);
+ }
+ }
+ } else if (kpxc.credentials[index].totp && kpxc.credentials[index].totp.length > 0) {
+ kpxc.setValue(currentField, kpxc.credentials[index].totp);
+ }
+ }
};
kpxc.setValue = function(field, value) {
@@ -1219,14 +1305,40 @@ kpxc.setValueWithChange = function(field, value) {
field.dispatchEvent(new Event('change', { 'bubbles': true }));
};
+kpxc.fillWithSpecificLogin = function(id) {
+ if (kpxc.credentials[id]) {
+ let combination = null;
+ if (kpxc.u) {
+ kpxc.setValueWithChange(kpxc.u, kpxc.credentials[id].login);
+ combination = kpxcFields.getCombination('username', kpxc.u);
+ browser.runtime.sendMessage({
+ action: 'page_set_login_id',
+ args: [ id ]
+ });
+ kpxc.u.focus();
+ }
+ if (kpxc.p) {
+ kpxc.setValueWithChange(kpxc.p, kpxc.credentials[id].password);
+ browser.runtime.sendMessage({
+ action: 'page_set_login_id',
+ args: [ id ]
+ });
+ combination = kpxcFields.getCombination('password', kpxc.p);
+ }
+
+ let list = [];
+ if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[id].stringFields, list)) {
+ kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
+ }
+
+ kpxcAutocomplete.closeList();
+ }
+};
+
kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
// No credentials available
if (kpxc.credentials.length === 0 && !suppressWarnings) {
- const message = tr('credentialsNoLoginsFound');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ kpxcUI.createNotification('error', tr('credentialsNoLoginsFound'));
return;
}
@@ -1261,11 +1373,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
- const message = tr('fieldsFill');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ kpxcUI.createNotification('error', tr('fieldsFill'));
}
return;
}
@@ -1297,11 +1405,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (!filledIn) {
if (!suppressWarnings) {
- const message = tr('fieldsFill');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ kpxcUI.createNotification('error', tr('fieldsFill'));
}
return;
}
@@ -1351,6 +1455,10 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (countPasswords > 1) {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
+ if (!target) {
+ return;
+ }
+
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
@@ -1361,17 +1469,17 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
return;
} else if (countPasswords < 1) {
if (!suppressWarnings) {
- const message = tr('credentialsNoUsernameFound');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ kpxcUI.createNotification('error', tr('credentialsNoUsernameFound'));
}
return;
}
} else {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
+ if (!target) {
+ return;
+ }
+
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
@@ -1383,10 +1491,13 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
}
+ // Close autocomplete menu after fill
+ kpxcAutocomplete.closeList();
+
// Auto-submit
if (kpxc.settings.autoSubmit) {
const form = kpxc.u.form || kpxc.p.form;
- const submitButton = kpxc.getSubmitButton(form);
+ const submitButton = kpxc.getFormSubmitButton(form);
if (submitButton !== undefined) {
submitButton.click();
} else {
@@ -1395,7 +1506,7 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
}
};
-kpxc.contextMenuRememberCredentials = function() {
+kpxc.contextMenuRememberCredentials = async function() {
const el = document.activeElement;
if (el.tagName.toLowerCase() !== 'input') {
return;
@@ -1419,30 +1530,42 @@ kpxc.contextMenuRememberCredentials = function() {
if (usernameField) {
usernameValue = usernameField.value;
}
+
if (passwordField) {
passwordValue = passwordField.value;
}
- if (!kpxc.rememberCredentials(usernameValue, passwordValue)) {
- const message = tr('rememberNothingChanged');
- browser.runtime.sendMessage({
- action: 'show_notification',
- args: [ message ]
- });
+ const result = await kpxc.rememberCredentials(usernameValue, passwordValue);
+ if (result === undefined) {
+ kpxcUI.createNotification('error', tr('rememberNoPassword'));
+ return;
+ }
+
+ if (!result) {
+ kpxcUI.createNotification('warning', tr('rememberCredentialsExists'));
}
};
-kpxc.rememberCredentials = function(usernameValue, passwordValue) {
+/**
+ * Gets the credential list and shows the update banner
+ * @param {string} usernameValue Submitted username
+ * @param {string} passwordValue Submitted password
+ * @param {string} urlValue URL of the page where password change was detected
+ * @param {Array} oldCredentials Credentials saved from the password change page, if available
+ */
+kpxc.rememberCredentials = async function(usernameValue, passwordValue, urlValue, oldCredentials) {
+ const credentials = kpxc.credentials || oldCredentials;
+
// No password given or field cleaned by a site-running script
// --> no password to save
if (passwordValue === '') {
- return false;
+ return undefined;
}
let usernameExists = false;
let nothingChanged = false;
- for (const c of kpxc.credentials) {
+ for (const c of credentials) {
if (c.login === usernameValue && c.password === passwordValue) {
nothingChanged = true;
break;
@@ -1455,7 +1578,7 @@ kpxc.rememberCredentials = function(usernameValue, passwordValue) {
if (!nothingChanged) {
if (!usernameExists) {
- for (const c of kpxc.credentials) {
+ for (const c of credentials) {
if (c.login === usernameValue) {
usernameExists = true;
break;
@@ -1463,7 +1586,7 @@ kpxc.rememberCredentials = function(usernameValue, passwordValue) {
}
}
const credentialsList = [];
- for (const c of kpxc.credentials) {
+ for (const c of credentials) {
credentialsList.push({
login: c.login,
name: c.name,
@@ -1482,29 +1605,57 @@ kpxc.rememberCredentials = function(usernameValue, passwordValue) {
}
}
- browser.runtime.sendMessage({
- action: 'set_remember_credentials',
- args: [ usernameValue, passwordValue, url, usernameExists, credentialsList ]
- });
+ urlValue = urlValue || url;
+ // Set usernameValue to the first one in the list, or the selected entry
+ if (usernameValue === '') {
+ if (credentialsList.length === 1) {
+ usernameValue = credentialsList[0].login;
+ } else if (credentialsList.length > 1) {
+ const index = await browser.runtime.sendMessage({
+ action: 'page_get_login_id'
+ });
+
+ if (index >= 0) {
+ usernameValue = credentialsList[index].login;
+ }
+ }
+ }
+
+ // Show the banner
+ const newCredentials = {
+ username: usernameValue,
+ password: passwordValue,
+ url: urlValue,
+ usernameExists: usernameExists,
+ list: credentialsList
+ };
+
+ kpxcBanner.create(newCredentials);
return true;
}
return false;
};
-kpxc.ignoreSite = function(sites) {
+kpxc.getSite = function(sites) {
if (!sites || sites.length === 0) {
- return;
+ return '';
}
- let site = sites[0];
+ let site = trimURL(sites[0]);
kpxc.initializeSitePreferences();
if (slashNeededForUrl(site)) {
site += '/';
}
+ return site;
+};
+
+kpxc.ignoreSite = async function(sites) {
+ const site = kpxc.getSite(sites);
+
// Check if the site already exists
let siteExists = false;
for (const existingSite of kpxc.settings['sitePreferences']) {
@@ -1522,12 +1673,47 @@ kpxc.ignoreSite = function(sites) {
});
}
- browser.runtime.sendMessage({
+ await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
};
+kpxc.addToSitePreferences = async function(sites) {
+ kpxc.initializeSitePreferences();
+
+ // Returns a predefined URL for certain sites
+ const site = kpxcSites.definedURL(trimURL(window.top.location.href));
+
+ // Check if the site already exists -> update the current settings
+ let siteExists = false;
+ for (const existingSite of kpxc.settings['sitePreferences']) {
+ if (existingSite.url === site) {
+ existingSite.ignore = IGNORE_NOTHING;
+ existingSite.usernameOnly = true;
+ siteExists = true;
+ }
+ }
+
+ if (!siteExists) {
+ kpxc.settings['sitePreferences'].push({
+ url: site,
+ ignore: IGNORE_NOTHING,
+ usernameOnly: true
+ });
+ }
+
+ await browser.runtime.sendMessage({
+ action: 'save_settings',
+ args: [ kpxc.settings ]
+ });
+
+ browser.runtime.sendMessage({
+ action: 'username_field_detected',
+ args: [ false ]
+ });
+};
+
// Delete previously created Object if it exists. It will be replaced by an Array
kpxc.initializeSitePreferences = function() {
if (kpxc.settings['sitePreferences'] !== undefined && kpxc.settings['sitePreferences'].constructor === Object) {
@@ -1544,7 +1730,7 @@ kpxc.getDocumentLocation = function() {
};
-var kpxcEvents = {};
+const kpxcEvents = {};
kpxcEvents.clearCredentials = function() {
kpxc.credentials = [];
@@ -1563,18 +1749,21 @@ kpxcEvents.clearCredentials = function() {
}
};
-kpxcEvents.triggerActivatedTab = function() {
+kpxcEvents.triggerActivatedTab = async function() {
// Doesn't run a second time because of _called.initCredentialFields set to true
kpxc.init();
+ // Update username field lock state
+ const state = await browser.runtime.sendMessage({ action: 'check_database_hash' });
+ kpxcUsernameField.switchIcon(state === '');
+
// initCredentialFields calls also "retrieve_credentials", to prevent it
// check of init() was already called
if (_called.initCredentialFields && (kpxc.url && kpxc.submitUrl) && kpxc.settings.autoRetrieveCredentials) {
- browser.runtime.sendMessage({
+ _called.retrieveCredentials = true;
+ kpxc.retrieveCredentialsCallback(await browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ kpxc.url, kpxc.submitUrl ]
- }).then(kpxc.retrieveCredentialsCallback).catch((e) => {
- console.log(e);
- });
+ }));
}
};
diff --git a/keepassxc-browser/content/pwgen.js b/keepassxc-browser/content/pwgen.js
index b84e41b..a9b5a8a 100644
--- a/keepassxc-browser/content/pwgen.js
+++ b/keepassxc-browser/content/pwgen.js
@@ -11,41 +11,18 @@ kpxcPassword.diffX = 0;
kpxcPassword.diffY = 0;
kpxcPassword.dialog = null;
kpxcPassword.titleBar = null;
+kpxcPassword.useIcons = false;
-const DOMRectToArray = function(domRect) {
- return [ domRect.bottom, domRect.height, domRect.left, domRect.right, domRect.top, domRect.width, domRect.x, domRect.y ];
-};
-
-/**
-* Detects if the input field appears or disappears -> show/hide the icon
-* - boundingClientRect with slightly (< -10) negative values -> hidden
-* - intersectionRatio === 0 -> hidden
-* - isIntersecting === false -> hidden
-* - intersectionRatio > 0 -> shown
-* - isIntersecting === true -> shown
-*/
try {
kpxcPassword.observer = new IntersectionObserver((entries) => {
- for (const entry of entries) {
- const rect = DOMRectToArray(entry.boundingClientRect);
-
- if ((entry.intersectionRatio === 0 && !entry.isIntersecting) || (rect.some(x => x < -10))) {
- kpxcPassword.icon.style.display = 'none';
- } else if (entry.intersectionRatio > 0 && entry.isIntersecting) {
- kpxcPassword.icon.style.display = 'block';
-
- // Wait for possible DOM animations
- setTimeout(() => {
- kpxcPassword.setIconPosition(kpxcPassword.icon, entry.target);
- }, 500);
- }
- }
+ kpxcUI.updateFromIntersectionObserver(kpxcPassword, entries);
});
} catch (err) {
console.log(err);
}
-kpxcPassword.init = function() {
+kpxcPassword.init = function(useIcons) {
+ kpxcPassword.useIcons = useIcons;
if ('initPasswordGenerator' in _called) {
return;
}
@@ -58,24 +35,26 @@ kpxcPassword.initField = function(field, inputs, pos) {
return;
}
- // Observer the visibility
- if (kpxcPassword.observer) {
- kpxcPassword.observer.observe(field);
- }
-
if (field.getAttribute('kpxc-password-generator')) {
return;
}
field.setAttribute('kpxc-password-generator', true);
- kpxcPassword.createIcon(field);
+ if (kpxcPassword.useIcons) {
+ // Observer the visibility
+ if (kpxcPassword.observer) {
+ kpxcPassword.observer.observe(field);
+ }
+ kpxcPassword.createIcon(field);
+ }
+
kpxcPassword.inputField = field;
let found = false;
if (inputs) {
for (let i = pos + 1; i < inputs.length; i++) {
- if (inputs[i] && inputs[i].getAttribute('type') && inputs[i].getAttribute('type').toLowerCase() === 'password') {
+ if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') {
field.setAttribute('kpxc-pwgen-next-field-id', inputs[i].getAttribute('data-kpxc-id'));
field.setAttribute('kpxc-pwgen-next-is-password-field', (i === 0));
found = true;
@@ -101,27 +80,33 @@ kpxcPassword.createIcon = function(field) {
'offset': offset,
'kpxc-pwgen-field-id': field.getAttribute('data-kpxc-id')
});
- icon.style.zIndex = '9999';
- icon.style.width = String(size) + 'px';
- icon.style.height = String(size) + 'px';
+ icon.style.zIndex = '10000000';
+ icon.style.width = Pixels(size);
+ icon.style.height = Pixels(size);
icon.addEventListener('click', function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
e.preventDefault();
kpxcPassword.showDialog(field, icon);
});
- kpxcPassword.setIconPosition(icon, field);
+ kpxcUI.setIconPosition(icon, field);
kpxcPassword.icon = icon;
document.body.appendChild(icon);
};
-kpxcPassword.setIconPosition = function(icon, field) {
- const rect = field.getBoundingClientRect();
- const offset = Number(icon.getAttribute('offset'));
- const size = Number(icon.getAttribute('size'));
-
- icon.style.top = String((rect.top + document.scrollingElement.scrollTop) + offset + 1) + 'px';
- icon.style.left = String((rect.left + document.scrollingElement.scrollLeft) + field.offsetWidth - size - offset) + 'px';
+kpxcPassword.removeIcon = function(field) {
+ if (field.getAttribute('kpxc-password-generator')) {
+ const pwgenIcons = document.querySelectorAll('.kpxc-pwgen-icon');
+ for (const i of pwgenIcons) {
+ if (i.getAttribute('kpxc-pwgen-field-id') === field.getAttribute('data-kpxc-id')) {
+ document.body.removeChild(i);
+ }
+ }
+ }
};
kpxcPassword.createDialog = function() {
@@ -140,45 +125,50 @@ kpxcPassword.createDialog = function() {
const titleBar = kpxcUI.createElement('div', 'kpxc-pwgen-titlebar', {}, tr('passwordGeneratorTitle'));
const closeButton = kpxcUI.createElement('div', 'kpxc-pwgen-close', {}, '×');
closeButton.addEventListener('click', function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
kpxcPassword.openDialog();
});
titleBar.append(closeButton);
const passwordRow = kpxcUI.createElement('div', 'kpxc-pwgen-password-row');
const input = kpxcUI.createElement('input', 'kpxc-pwgen-input', { 'placeholder': tr('passwordGeneratorPlaceholder'), 'type': 'text', 'tabindex': '-1' });
- const inputLabel = kpxcUI.createElement('label', 'kpxc-pwgen-bits', {}, tr('passwordGeneratorBits', '???'));
- passwordRow.appendMultiple(input, inputLabel);
-
- const nextFillRow = kpxcUI.createElement('div', 'kpxc-pwgen-nextfill-row');
- const checkbox = kpxcUI.createElement('input', 'kpxc-pwgen-checkbox', { 'id': 'kpxc-pwgen-checkbox', 'type': 'checkbox' });
- const checkboxLabel = kpxcUI.createElement('label', 'kpxc-pwgen-checkbox-label', { 'for': 'kpxc-pwgen-checkbox' }, tr('passwordGeneratorLabel'));
- nextFillRow.appendMultiple(checkbox, checkboxLabel);
+ passwordRow.appendMultiple(input);
// Buttons
const buttonsRow = kpxcUI.createElement('div', 'kpxc-pwgen-buttons');
- const generateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-white-button', { 'id': 'kpxc-pwgen-btn-generate' }, tr('passwordGeneratorGenerate'));
- const copyButton = kpxcUI.createElement('button', 'kpxc-button', { 'id': 'kpxc-pwgen-btn-copy' }, tr('passwordGeneratorCopy'));
- const fillButton = kpxcUI.createElement('button', 'kpxc-button', { 'id': 'kpxc-pwgen-btn-fill' }, tr('passwordGeneratorFillAndCopy'));
+ const generateButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-pwgen-btn-generate' }, tr('passwordGeneratorGenerate'));
+ const copyButton = kpxcUI.createElement('button', 'kpxc-button kpxc-orange-button', { 'id': 'kpxc-pwgen-btn-copy' }, tr('passwordGeneratorCopy'));
+ const fillButton = kpxcUI.createElement('button', 'kpxc-button kpxc-green-button', { 'id': 'kpxc-pwgen-btn-fill' }, tr('passwordGeneratorFill'));
generateButton.addEventListener('click', function(e) {
kpxcPassword.generate(e);
});
+ fillButton.addEventListener('click', function(e) {
+ kpxcPassword.fill(e);
+ kpxcPassword.openDialog();
+ });
+
copyButton.addEventListener('click', function(e) {
kpxcPassword.copy(e);
});
- fillButton.addEventListener('click', function(e) {
- kpxcPassword.fill(e);
- });
-
buttonsRow.appendMultiple(generateButton, copyButton, fillButton);
- dialog.appendMultiple(titleBar, passwordRow, nextFillRow, buttonsRow);
+ dialog.appendMultiple(titleBar, passwordRow, buttonsRow);
wrapper.append(dialog);
const icon = $('.kpxc-pwgen-icon');
- dialog.style.top = String(icon.offsetTop + icon.offsetHeight) + 'px';
- dialog.style.left = icon.style.left;
+ if (icon) {
+ dialog.style.top = Pixels(icon.offsetTop + icon.offsetHeight);
+ dialog.style.left = icon.style.left;
+ } else {
+ const rect = document.activeElement.getBoundingClientRect();
+ dialog.style.top = Pixels(rect.top + rect.height);
+ dialog.style.left = Pixels(rect.left);
+ }
document.body.append(wrapper);
@@ -209,7 +199,7 @@ kpxcPassword.openDialog = function() {
};
kpxcPassword.trigger = function() {
- kpxcPassword.showDialog(kpxcPassword.inputField, kpxcPassword.icon);
+ kpxcPassword.showDialog(kpxcPassword.inputField || document.activeElement, kpxcPassword.icon);
};
kpxcPassword.showDialog = function(field, icon) {
@@ -224,8 +214,14 @@ kpxcPassword.showDialog = function(field, icon) {
// Adjust the dialog location
if (kpxcPassword.dialog) {
- kpxcPassword.dialog.style.top = String(icon.offsetTop + icon.offsetHeight) + 'px';
- kpxcPassword.dialog.style.left = icon.style.left;
+ if (icon) {
+ kpxcPassword.dialog.style.top = Pixels(icon.offsetTop + icon.offsetHeight);
+ kpxcPassword.dialog.style.left = icon.style.left;
+ } else {
+ const rect = document.activeElement.getBoundingClientRect();
+ kpxcPassword.dialog.style.top = Pixels(rect.top + rect.height);
+ kpxcPassword.dialog.style.left = Pixels(rect.left);
+ }
kpxcPassword.dialog.setAttribute('kpxc-pwgen-field-id', field.getAttribute('data-kpxc-id'));
kpxcPassword.dialog.setAttribute('kpxc-pwgen-next-field-id', field.getAttribute('kpxc-pwgen-next-field-id'));
@@ -244,27 +240,34 @@ kpxcPassword.showDialog = function(field, icon) {
}
};
-kpxcPassword.generate = function(e) {
+kpxcPassword.generate = async function(e) {
+ // This function can be also called from non-events
if (e) {
+ if (!e.isTrusted) {
+ return;
+ }
e.preventDefault();
}
- browser.runtime.sendMessage({
+ callbackGeneratedPassword(await browser.runtime.sendMessage({
action: 'generate_password'
- }).then(kpxcPassword.callbackGeneratedPassword).catch((err) => {
- console.log(err);
- });
+ }));
};
kpxcPassword.copy = function(e) {
- e.preventDefault();
- if (kpxcPassword.copyPasswordToClipboard()) {
- kpxcPassword.greenButton('#kpxc-pwgen-btn-copy');
- kpxcPassword.whiteButton('#kpxc-pwgen-btn-fill');
+ if (!e.isTrusted) {
+ return;
}
+
+ e.preventDefault();
+ kpxcPassword.copyPasswordToClipboard();
};
kpxcPassword.fill = function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
e.preventDefault();
// Use the active input field
@@ -285,19 +288,10 @@ kpxcPassword.fill = function(e) {
}
field.value = password.value;
- if ($('.kpxc-pwgen-checkbox').checked) {
- if (field.getAttribute('kpxc-pwgen-next-field-id')) {
- const nextFieldId = field.getAttribute('kpxc-pwgen-next-field-id');
- const nextField = $('input[data-kpxc-id=\'' + nextFieldId + '\']');
- if (nextField) {
- nextField.value = password.value;
- }
- }
- }
-
- if (kpxcPassword.copyPasswordToClipboard()) {
- kpxcPassword.greenButton('#kpxc-pwgen-btn-fill');
- kpxcPassword.whiteButton('#kpxc-pwgen-btn-copy');
+ const nextFieldId = field.getAttribute('kpxc-pwgen-next-field-id');
+ const nextField = $('input[data-kpxc-id=\'' + nextFieldId + '\']');
+ if (nextField) {
+ nextField.value = password.value;
}
}
};
@@ -312,33 +306,20 @@ kpxcPassword.copyPasswordToClipboard = function() {
return false;
};
-kpxcPassword.callbackGeneratedPassword = function(entries) {
+const callbackGeneratedPassword = function(entries) {
if (entries && entries.length >= 1) {
const errorMessage = $('#kpxc-pwgen-error');
if (errorMessage) {
- kpxcPassword.enableButtons();
-
- $('.kpxc-pwgen-checkbox').parentElement.style.display = 'block';
- $('.kpxc-pwgen-bits').style.display = 'block';
+ enableButtons();
const input = $('.kpxc-pwgen-input');
input.style.display = 'block';
errorMessage.remove();
}
- kpxcPassword.whiteButton('#kpxc-pwgen-btn-fill');
- kpxcPassword.whiteButton('#kpxc-pwgen-btn-copy');
$('.kpxc-pwgen-input').value = entries[0].password;
- if (entries[0].entropy) {
- $('.kpxc-pwgen-bits').textContent = tr('passwordGeneratorBits', (Number.isNaN(entries[0].entropy) ? '???' : String(entries[0].entropy.toFixed(2))));
- } else {
- $('.kpxc-pwgen-bits').textContent = tr('passwordGeneratorBits', (isNaN(entries[0].login) ? '???' : entries[0].login));
- }
} else {
if (document.querySelectorAll('div#kpxc-pwgen-error').length === 0) {
- $('.kpxc-pwgen-checkbox').parentElement.style.display = 'none';
- $('.kpxc-pwgen-bits').style.display = 'none';
-
const input = $('.kpxc-pwgen-input');
input.style.display = 'none';
@@ -347,34 +328,18 @@ kpxcPassword.callbackGeneratedPassword = function(entries) {
errorMessage.style.whiteSpace = 'pre';
input.parentElement.append(errorMessage);
- kpxcPassword.disableButtons();
+ disableButtons();
}
}
};
-kpxcPassword.onRequestPassword = function() {
- browser.runtime.sendMessage({
- action: 'generate_password'
- }).then(kpxcPassword.callbackGeneratedPassword);
-};
-
-kpxcPassword.greenButton = function(button) {
- $(button).classList.remove('kpxc-white-button');
- $(button).classList.add('kpxc-green-button');
-};
-
-kpxcPassword.whiteButton = function(button) {
- $(button).classList.remove('kpxc-green-button');
- $(button).classList.add('kpxc-white-button');
-};
-
-kpxcPassword.enableButtons = function() {
+const enableButtons = function() {
$('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorGenerate');
$('#kpxc-pwgen-btn-copy').style.display = 'inline-block';
$('#kpxc-pwgen-btn-fill').style.display = 'inline-block';
};
-kpxcPassword.disableButtons = function() {
+const disableButtons = function() {
$('#kpxc-pwgen-btn-generate').textContent = tr('passwordGeneratorTryAgain');
$('#kpxc-pwgen-btn-copy').style.display = 'none';
$('#kpxc-pwgen-btn-fill').style.display = 'none';
@@ -382,28 +347,10 @@ kpxcPassword.disableButtons = function() {
// Handle icon position on window resize
window.addEventListener('resize', function(e) {
- if (kpxcPassword.inputField && kpxcPassword.icon) {
- kpxcPassword.setIconPosition(kpxcPassword.icon, kpxcPassword.inputField);
- }
+ kpxcUI.updateIconPosition(kpxcPassword);
});
// Handle icon position on scroll
window.addEventListener('scroll', function(e) {
- if (kpxcPassword.inputField && kpxcPassword.icon) {
- kpxcPassword.setIconPosition(kpxcPassword.icon, kpxcPassword.inputField);
- }
-});
-
-// Closes the dialog when clicked outside of it)
-document.addEventListener('click', function(e) {
- if (kpxcPassword.dialog && kpxcPassword.dialog.style.display === 'block') {
- const dialogEndX = kpxcPassword.dialog.offsetLeft + kpxcPassword.dialog.offsetWidth;
- const dialogEndY = kpxcPassword.dialog.offsetTop + kpxcPassword.dialog.offsetHeight;
-
- if ((e.clientX < kpxcPassword.dialog.offsetLeft || e.clientX > dialogEndX) ||
- (e.clientY < kpxcPassword.dialog.offsetTop || e.clientY > dialogEndY) &&
- !e.target.classList.contains('kpxc-pwgen-icon')) {
- kpxcPassword.openDialog();
- }
- }
+ kpxcUI.updateIconPosition(kpxcPassword);
});
diff --git a/keepassxc-browser/content/sites.js b/keepassxc-browser/content/sites.js
new file mode 100644
index 0000000..0575b16
--- /dev/null
+++ b/keepassxc-browser/content/sites.js
@@ -0,0 +1,35 @@
+'use strict';
+
+// List of sites that need special handling. 'rep' is the replacement URL.
+const siteList = [
+ {
+ url: 'accounts.google.com',
+ rep: 'https://accounts.google.com/*'
+ }
+];
+
+var kpxcSites = {};
+
+// Returns a predefined URL for certain sites to ensure compatibility with Site Preferences
+kpxcSites.definedURL = function(url) {
+ for (const site of siteList) {
+ if (url.includes(site.url)) {
+ return site.rep;
+ }
+ }
+
+ return url;
+};
+
+// UNUSED: Adds all common sites with multi-page login to Site Preferences
+kpxcSites.addAllCommonSites = function() {
+ kpxc.initializeSitePreferences();
+
+ for (const site of siteList) {
+ kpxc.settings['sitePreferences'].push({
+ url: site.rep,
+ ignore: IGNORE_NOTHING,
+ usernameOnly: true
+ });
+ }
+};
diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js
index 5774eb5..96c5ac3 100644
--- a/keepassxc-browser/content/ui.js
+++ b/keepassxc-browser/content/ui.js
@@ -5,6 +5,11 @@ var $ = function(elem) {
return document.querySelector(elem);
};
+// Returns a string with 'px' for CSS styles
+var Pixels = function(value) {
+ return String(value) + 'px';
+};
+
var kpxcUI = {};
// Wrapper for creating elements
@@ -31,6 +36,83 @@ kpxcUI.createElement = function(type, classes, attributes, textContent) {
return element;
};
+kpxcUI.updateIconPosition = function(iconClass) {
+ if (iconClass.inputField && iconClass.icon) {
+ kpxcUI.setIconPosition(iconClass.icon, iconClass.inputField);
+ }
+};
+
+kpxcUI.setIconPosition = function(icon, field) {
+ const rect = field.getBoundingClientRect();
+ const offset = Number(icon.getAttribute('offset'));
+ const size = Number(icon.getAttribute('size'));
+
+ icon.style.top = Pixels((rect.top + document.scrollingElement.scrollTop) + offset + 1);
+ icon.style.left = Pixels((rect.left + document.scrollingElement.scrollLeft) + field.offsetWidth - size - offset);
+};
+
+/**
+* Detects if the input field appears or disappears -> show/hide the icon
+* - boundingClientRect with slightly (< -10) negative values -> hidden
+* - intersectionRatio === 0 -> hidden
+* - isIntersecting === false -> hidden
+* - intersectionRatio > 0 -> shown
+* - isIntersecting === true -> shown
+*/
+kpxcUI.updateFromIntersectionObserver = function(iconClass, entries) {
+ for (const entry of entries) {
+ const rect = DOMRectToArray(entry.boundingClientRect);
+
+ if ((entry.intersectionRatio === 0 && !entry.isIntersecting) || (rect.some(x => x < -10))) {
+ iconClass.icon.style.display = 'none';
+ } else if (entry.intersectionRatio > 0 && entry.isIntersecting) {
+ iconClass.icon.style.display = 'block';
+
+ // Wait for possible DOM animations
+ setTimeout(() => {
+ kpxcUI.setIconPosition(iconClass.icon, entry.target);
+ }, 500);
+ }
+ }
+};
+
+/**
+ * Creates a self-disappearing notification banner to DOM
+ * @param {string} type Notification type: (success, info, warning, error)
+ * @param {string} message The message shown
+ */
+kpxcUI.createNotification = function(type, message) {
+ if (!kpxc.settings.showNotifications || !type || !message) {
+ return;
+ }
+
+ const banner = kpxcUI.createElement('div', 'kpxc-notification kpxc-notification-' + type, {});
+ type = type.charAt(0).toUpperCase() + type.slice(1) + '!';
+
+ const className = (isFirefox() ? 'kpxc-banner-icon-moz' : 'kpxc-banner-icon');
+ const icon = kpxcUI.createElement('span', className, { 'alt': 'logo' });
+ const label = kpxcUI.createElement('span', 'kpxc-label', {}, type);
+ const msg = kpxcUI.createElement('span', '', {}, message);
+
+ banner.addEventListener('click', function() {
+ document.body.removeChild(banner);
+ });
+
+ banner.appendMultiple(icon, label, msg);
+ document.body.appendChild(banner);
+
+ // Destroy the banner after five seconds
+ setTimeout(() => {
+ if ($('.kpxc-notification')) {
+ document.body.removeChild(banner);
+ }
+ }, 5000);
+};
+
+const DOMRectToArray = function(domRect) {
+ return [ domRect.bottom, domRect.height, domRect.left, domRect.right, domRect.top, domRect.width, domRect.x, domRect.y ];
+};
+
// Enables dragging
document.addEventListener('mousemove', function(e) {
if (kpxcPassword.selected === kpxcPassword.titleBar) {
@@ -38,8 +120,8 @@ document.addEventListener('mousemove', function(e) {
const yPos = e.clientY - kpxcPassword.diffY;
if (kpxcPassword.selected !== null) {
- kpxcPassword.dialog.style.left = xPos + 'px';
- kpxcPassword.dialog.style.top = yPos + 'px';
+ kpxcPassword.dialog.style.left = Pixels(xPos);
+ kpxcPassword.dialog.style.top = Pixels(yPos);
}
}
@@ -48,8 +130,8 @@ document.addEventListener('mousemove', function(e) {
const yPos = e.clientY - kpxcDefine.diffY;
if (kpxcDefine.selected !== null) {
- kpxcDefine.dialog.style.left = xPos + 'px';
- kpxcDefine.dialog.style.top = yPos + 'px';
+ kpxcDefine.dialog.style.left = Pixels(xPos);
+ kpxcDefine.dialog.style.top = Pixels(yPos);
}
}
});
@@ -64,3 +146,7 @@ HTMLDivElement.prototype.appendMultiple = function(...args) {
this.append(a);
}
};
+
+Element.prototype.getLowerCaseAttribute = function(attr) {
+ return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined;
+};
diff --git a/keepassxc-browser/content/username-field.js b/keepassxc-browser/content/username-field.js
new file mode 100644
index 0000000..601dee7
--- /dev/null
+++ b/keepassxc-browser/content/username-field.js
@@ -0,0 +1,137 @@
+'use strict';
+
+var kpxcUsernameField = {};
+kpxcUsernameField.icon = null;
+kpxcUsernameField.inputField = null;
+
+try {
+ kpxcUsernameField.observer = new IntersectionObserver((entries) => {
+ kpxcUI.updateFromIntersectionObserver(kpxcUsernameField, entries);
+ });
+} catch (err) {
+ console.log(err);
+}
+
+kpxcUsernameField.initField = function(field, databaseClosed = true) {
+ if (!field || field.getAttribute('kpxc-username-field') === 'true') {
+ return;
+ }
+
+ field.setAttribute('kpxc-username-field', 'true');
+
+ // Observer the visibility
+ if (kpxcUsernameField.observer) {
+ kpxcUsernameField.observer.observe(field);
+ }
+
+ createIcon(field, databaseClosed);
+ kpxcUsernameField.inputField = field;
+};
+
+kpxcUsernameField.switchIcon = function(locked) {
+ const icons = $('.kpxc-username-icon');
+ if (!icons || icons.length === 0) {
+ return;
+ }
+ const icon = icons;
+
+ if (locked) {
+ icon.classList.remove(getIconClassName());
+ icon.classList.add(getIconClassName(true));
+ icon.title = tr('usernameLockedFieldText');
+ } else {
+ icon.classList.remove(getIconClassName(true));
+ icon.classList.add(getIconClassName());
+ icon.title = tr('usernameFieldText');
+ }
+};
+
+const createIcon = function(target, databaseClosed) {
+ // Remove any existing password generator icons from the input field
+ if (target.getAttribute('kpxc-password-generator')) {
+ kpxcPassword.removeIcon(target);
+ }
+
+ const field = target;
+ const className = getIconClassName(databaseClosed);
+ const size = (field.offsetHeight > 28) ? 24 : 16;
+ let offset = Math.floor((field.offsetHeight - size) / 3);
+ offset = (offset < 0) ? 0 : offset;
+
+ const icon = kpxcUI.createElement('div', 'kpxc kpxc-username-icon ' + className,
+ {
+ 'title': databaseClosed ? tr('usernameLockedFieldText') : tr('usernameFieldText'),
+ 'alt': tr('usernameFieldIcon'),
+ 'size': size,
+ 'offset': offset,
+ 'kpxc-pwgen-field-id': field.getAttribute('data-kpxc-id')
+ });
+ icon.style.zIndex = '10000000';
+ icon.style.width = Pixels(size);
+ icon.style.height = Pixels(size);
+
+ icon.addEventListener('click', async function(e) {
+ if (!e.isTrusted) {
+ return;
+ }
+
+ e.preventDefault();
+
+ if (!kpxcFields.isVisible(field)) {
+ document.body.removeChild(icon);
+ field.removeAttribute('kpxc-username-field');
+ return;
+ }
+
+ const connected = await browser.runtime.sendMessage({ action: 'is_connected' });
+ if (!connected) {
+ kpxcUI.createNotification('error', tr('errorNotConnected'));
+ return;
+ }
+
+ const databaseHash = await browser.runtime.sendMessage({ action: 'check_database_hash' });
+ if (databaseHash === '') {
+ // Triggers database unlock
+ _called.manualFillRequested = ManualFill.BOTH;
+ await browser.runtime.sendMessage({
+ action: 'get_database_hash',
+ args: [ false, true ] // Set triggerUnlock to true
+ });
+ }
+
+ if (icon.className.includes('unlock')) {
+ fillCredentials();
+ }
+ });
+
+ kpxcUI.setIconPosition(icon, field);
+ kpxcUsernameField.icon = icon;
+ document.body.appendChild(icon);
+};
+
+const getIconClassName = function(locked = false) {
+ if (locked) {
+ return (isFirefox() ? 'lock-moz' : 'lock');
+ }
+ return (isFirefox() ? 'unlock-moz' : 'unlock');
+};
+
+const fillCredentials = function() {
+ const fieldId = kpxcUsernameField.inputField.getAttribute('data-kpxc-id');
+ kpxcFields.prepareId(fieldId);
+
+ const givenType = kpxcUsernameField.inputField.type === 'password' ? 'password' : 'username';
+ const combination = kpxcFields.getCombination(givenType, fieldId);
+
+ kpxc.fillInCredentials(combination, givenType === 'password', false);
+};
+
+// Handle icon position on window resize
+window.addEventListener('resize', function(e) {
+ kpxcUI.updateIconPosition(kpxcUsernameField);
+});
+
+// Handle icon position on scroll
+window.addEventListener('scroll', function(e) {
+ kpxcUI.updateIconPosition(kpxcUsernameField);
+});
diff --git a/keepassxc-browser/css/banner.css b/keepassxc-browser/css/banner.css
new file mode 100644
index 0000000..3b05509
--- /dev/null
+++ b/keepassxc-browser/css/banner.css
@@ -0,0 +1,138 @@
+div.kpxc-banner {
+ animation-duration: 0.2s;
+ animation-name: slidein;
+ background-color: #ffffff;
+ border-bottom: 1px solid #38383d !important;
+ box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
+ box-sizing: border-box !important;
+ color: #000000 !important;
+ font-family: sans-serif !important;
+ font-size: 1em !important;
+ height: auto !important;
+ left: 0px !important;
+ line-height: 1 !important;
+ margin: 0 auto !important;
+ min-height: 2em !important;
+ max-height: 4em !important;
+ overflow-x: hidden !important;
+ padding: 4px !important;
+ position: fixed !important;
+ top: 0px !important;
+ width: 100% !important;
+ display: flex !important;
+ justify-content: space-between !important;
+}
+
+@keyframes slidein {
+ from {
+ max-height: 0em;
+ }
+
+ to {
+ max-height: 4em;
+ }
+}
+
+div.kpxc-banner .banner-info > span {
+ margin: 4px;
+}
+
+div.kpxc-banner div.banner-info, div.banner-buttons {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+}
+
+div.kpxc-banner .small {
+ font-size: 80%;
+ color: #787878;
+}
+
+div.kpxc-banner .kpxc-banner-icon {
+ width: 24px;
+ height: 24px;
+ overflow: hidden;
+ background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
+
+div.kpxc-banner .kpxc-banner-icon-moz {
+ width: 24px;
+ height: 24px;
+ overflow: hidden;
+ background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
+
+.separator {
+ border-left: 1px solid #ccc;
+ height: 100% !important;
+ margin: 10px !important;
+}
+
+div.kpxc-banner .kpxc-checkbox {
+ margin: 2px !important;
+}
+
+div.kpxc-banner label {
+ font-family: 'Lato', sans-serif !important;
+ font-size: 12px !important;
+ font-weight: normal !important;
+ margin: 4px !important;
+}
+
+div.kpxc-banner-dialog {
+ display: block !important;
+ position: fixed !important;
+ margin: 0 auto;
+ width: 680px;
+ max-height: 600px;
+ max-width: 460px;
+ background-color: white;
+ border: 1px solid #ccc;
+ border-radius: 0 0 4px 4px;
+ box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
+ padding: 8px;
+ z-index: 10000000;
+}
+
+div.kpxc-banner-dialog > ul {
+ list-style-type: disc !important;
+ margin-bottom: 0 !important;
+ margin-top: 0 !important;
+ padding-left: 0 !important;
+}
+
+div.kpxc-banner-dialog > p > span.strong {
+ font-weight: bold !important;
+}
+
+div.kpxc-banner-dialog .list-group {
+ font-size: .9em !important;
+}
+
+div.kpxc-banner-dialog .list-group-item {
+ border: 1px solid #ddd !important;
+ color: #000 !important;
+ display: block !important;
+ padding: 5px 10px;
+ position: relative !important;
+ text-decoration: none !important;
+}
+
+div.kpxc-banner-dialog .list-group-item:first-child {
+ border-top-left-radius: 4px !important;
+ border-top-right-radius: 4px !important;
+}
+
+div.kpxc-banner-dialog .list-group-item:last-child {
+ margin-bottom: 0 !important;
+ border-bottom-right-radius: 4px !important;
+ border-bottom-left-radius: 4px !important;
+}
+
+div.kpxc-banner-dialog .list-group-item:hover {
+ cursor: pointer !important;;
+ background-color: #eee !important;
+}
diff --git a/keepassxc-browser/css/button.css b/keepassxc-browser/css/button.css
index 16945ad..79df313 100644
--- a/keepassxc-browser/css/button.css
+++ b/keepassxc-browser/css/button.css
@@ -1,48 +1,55 @@
.kpxc-button {
+ background-color: #fff;
+ background-image: none !important;
+ border: 1px solid #ccc !important;
+ border-radius: .25em !important;
font-size: 12px !important;
font-family: 'Lato', sans-serif !important;
height: 28px !important;
margin: .2em !important;
- background-color: #fff;
- border: 1px solid #888 !important;
- border-radius: 4px !important;
+ padding: 1px 7px 2px !important;
}
.kpxc-white-button {
- color: #333 !important;
- background-color: #fff !important;
- border-color: #ccc !important;
+ background-color: #f8f9fa !important;
+ border-color: #f8f9fa !important;
+ color: #212529 !important;
}
.kpxc-red-button {
+ background-color: #dc3545 !important;
+ border-color: #dc3545 !important;
color: #fff !important;
- background-color: #d9534f !important;
- border-color: #d43f3a !important;
}
.kpxc-orange-button {
- color: #fff !important;
- background-color: #f0ad4e !important;
- border-color: #eea236 !important;
+ background-color: #ffc107 !important;
+ border-color: #ffc107 !important;
+ color: #212529 !important;
}
.kpxc-blue-button {
+ background-color: #007bff !important;
+ border-color: #007bff !important;
color: #fff !important;
- background-color: #337ab7 !important;
- border-color: #2e6da4 !important;
}
.kpxc-green-button {
+ background-color: #28a745 !important;
+ border-color: #28a745 !important;
color: #fff !important;
- background-color: #5cb85c !important;
- border-color: #4cae4c !important;
}
.kpxc-button:hover {
- background-color: #eee;
- cursor: pointer;
+ cursor: pointer !important;
+ filter: brightness(92%) !important;
+ transition: all .15s;
}
-.kpxc-button:active {
- background-color: #ddd;
-}
\ No newline at end of file
+.kpxc-button:disabled {
+ border-color: #ccc !important;
+}
+
+.kpxc-button:disabled:hover {
+ background-color: #fff !important;
+}
diff --git a/keepassxc-browser/css/notification.css b/keepassxc-browser/css/notification.css
new file mode 100644
index 0000000..55c74a3
--- /dev/null
+++ b/keepassxc-browser/css/notification.css
@@ -0,0 +1,69 @@
+.kpxc-notification {
+ border: 1px solid transparent;
+ border-radius: 4px;
+ box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
+ cursor: pointer;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 14px;
+ margin: 0px auto;
+ margin-bottom: 20px;
+ padding: 15px;
+ padding-right: 35px;
+ position: fixed;
+ right: 0px;
+ top: 0px;
+ width: 520px;
+ z-index: 9999;
+}
+
+.kpxc-notification span {
+ padding: 2px;
+}
+
+.kpxc-notification .kpxc-banner-icon {
+ width: 24px;
+ height: 24px;
+ padding: 10px;
+ margin-right: 4px;
+ overflow: hidden;
+ background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
+
+.kpxc-notification .kpxc-banner-icon-moz {
+ width: 24px;
+ height: 24px;
+ padding: 10px;
+ margin-right: 4px;
+ overflow: hidden;
+ background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
+
+.kpxc-notification .kpxc-label {
+ font-weight: bold;
+}
+
+.kpxc-notification-success {
+ color: #155724;
+ background-color: #d4edda;
+ border-color: #c3e6cb;
+}
+
+.kpxc-notification-warning {
+ color: #856404;
+ background-color: #fff3cd;
+ border-color: #ffeeba;
+}
+
+.kpxc-notification-info {
+ color: #004085;
+ background-color: #cce5ff;
+ border-color: #b8daff;
+}
+
+.kpxc-notification-error {
+ color: #721c24;
+ background-color: #f8d7da;
+ border-color: #f5c6cb;
+}
diff --git a/keepassxc-browser/css/pwgen.css b/keepassxc-browser/css/pwgen.css
index 97ae4dd..55fa1ee 100644
--- a/keepassxc-browser/css/pwgen.css
+++ b/keepassxc-browser/css/pwgen.css
@@ -1,67 +1,41 @@
.kpxc-pwgen-dialog {
- font-family: 'Lato', sans-serif !important;
- width: 350px;
- min-height: 80px;
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
- z-index: 2147483645;
+ font-family: 'Lato', sans-serif !important;
+ min-height: 80px;
padding: .1em .2em .1em .2em;
position: absolute !important;
+ width: 350px;
+ z-index: 10000000 !important;
}
.kpxc-pwgen-titlebar {
- font-size: 14px !important;
background-color: #6cac4d;
color: #fff;
- padding: .4em .2em .4em .8em;
- margin: .2em 0 .2em 0;
- position: relative;
cursor: move;
+ font-size: 12px !important;
+ margin: .2em 0 .2em 0;
+ padding: .4em .2em .4em .8em;
+ position: relative;
}
.kpxc-pwgen-password-row {
color: #fff;
- padding: .4em 1em;
- margin: .2em 0 .2em 0;
display: flex;
-}
-
-.kpxc-pwgen-nextfill-row {
- font-size: 12px !important;
- color: #000;
- padding: .4em 1em;
margin: .2em 0 .2em 0;
+ padding: .4em 1em;
}
.kpxc-pwgen-buttons {
+ align-items: center;
+ border-top: 1px solid #ccc;
color: #fff;
+ display: flex;
+ justify-content: left;
padding: .4em 1em;
margin: .2em 0 .2em 0;
- border-top: 1px solid #ccc;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.kpxc-pwgen-bits {
- font-size: 12px !important;
- font-style: normal !important;
- font-weight: normal !important;
- text-align: center;
- color: #000;
- background-color: #eee;
- border: 1px solid #ccc;
- padding: .4em;
- border-radius: 4px;
- width: 30%;
- min-width: 60px;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- border-collapse: separate;
- vertical-align: middle;
- white-space: nowrap;
}
.kpxc-pwgen-close {
@@ -80,9 +54,14 @@
}
.kpxc-pwgen-input {
- width: 100%;
+ background: none !important;
+ border-radius: 4px !important;
+ color: #000 !important;
font-size: 12px !important;
font-weight: normal !important;
+ height: auto !important;
+ padding: 4px !important;
+ width: 100% !important;
}
.kpxc-pwgen-checkbox-label {
@@ -100,8 +79,8 @@
}
.kpxc-pwgen-icon {
- position: absolute;
cursor: pointer;
+ position: absolute;
}
.kpxc-pwgen-icon.key {
diff --git a/keepassxc-browser/css/username.css b/keepassxc-browser/css/username.css
new file mode 100644
index 0000000..9321a03
--- /dev/null
+++ b/keepassxc-browser/css/username.css
@@ -0,0 +1,24 @@
+.kpxc-username-icon {
+ position: absolute;
+ cursor: pointer;
+}
+
+.kpxc-username-icon.lock {
+ background: url('chrome-extension://__MSG_@@extension_id__/icons/locked.svg') right no-repeat;
+ background-size: contain;
+}
+
+.kpxc-username-icon.lock-moz {
+ background: url('moz-extension://__MSG_@@extension_id__/icons/locked.svg') right no-repeat;
+ background-size: contain;
+}
+
+.kpxc-username-icon.unlock {
+ background: url('chrome-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
+
+.kpxc-username-icon.unlock-moz {
+ background: url('moz-extension://__MSG_@@extension_id__/icons/keepassxc.svg') right no-repeat;
+ background-size: contain;
+}
\ No newline at end of file
diff --git a/keepassxc-browser/global.js b/keepassxc-browser/global.js
index 58c29f5..e2b635c 100755
--- a/keepassxc-browser/global.js
+++ b/keepassxc-browser/global.js
@@ -21,6 +21,13 @@ var showNotification = function(message) {
});
};
+var AssociatedAction = {
+ NOT_ASSOCIATED: 0,
+ ASSOCIATED: 1,
+ NEW_ASSOCIATION: 2,
+ CANCELED: 3
+};
+
/**
* Transforms a valid match pattern into a regular expression
* which matches all URLs included by that pattern.
@@ -71,6 +78,8 @@ var matchPatternToRegExp = function(pattern) {
}
if (path) {
+ path = trimURL(path);
+
if (path === '*') {
regex += '(/.*)?';
} else if (path.charAt(0) !== '/') {
@@ -97,3 +106,8 @@ var slashNeededForUrl = function(pattern) {
function tr(key, params) {
return browser.i18n.getMessage(key, params);
}
+
+// Removes everything after '?' from URL
+var trimURL = function(url) {
+ return url.indexOf('?') !== -1 ? url.split('?')[0] : url;
+}
diff --git a/keepassxc-browser/icons/key.png b/keepassxc-browser/icons/key.png
deleted file mode 100644
index f21e46adc67b0a218f1799fe4eb33c316f38c964..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1063
zcmV+?1laqDP)7!gZZQ~)AxxNzcfWQ07@yVq4|u)qp69vu<3^B(ERz{n
zo*&ksQE-iCRYIImCoI=1g~H}VZD;N
z`7Btp#n25N#!zP(K2dXUwQV;RZnt6O?m;Zxl%l>Y;uQ!ORcM
z`0b(!S1RSm@$dtm;@P!vxGd7QQ5k1j(l^0r{RpG&>BxV@_pyu}v@Y2~#p~`~NOXV3
z#HVxNEeyl#jeM+5odGO$m>!cs
z^H$jM$!aN`Toub0%PaaO+--ju=S0D{_eCP+h9#IDItcUdQT%bW1`~aiI9?b!LGBf1
zv&A*(?6vcfa5M`;aQ{LSX8IE_el7_WNf9VX2w73(u;=#kX-v{%%A`~IE5&uUrLY^-XvuKa<
zqF>A<%lHdgY42jP2|TR;001j=MObu0Z*X~XX=iA307GwZd2(rIXmlW{ATls8I4v?T
hF)<)XPD4*pQy@oeVn`5
+
diff --git a/keepassxc-browser/manifest.json b/keepassxc-browser/manifest.json
index 15399b8..81f782f 100755
--- a/keepassxc-browser/manifest.json
+++ b/keepassxc-browser/manifest.json
@@ -54,18 +54,24 @@
],
"js": [
"browser-polyfill.min.js",
+ "content/banner.js",
"content/autocomplete.js",
"content/define.js",
"content/keepassxc-browser.js",
"content/pwgen.js",
+ "content/sites.js",
"content/ui.js",
+ "content/username-field.js",
"global.js"
],
"css": [
"css/autocomplete.css",
+ "css/banner.css",
"css/button.css",
"css/define.css",
- "css/pwgen.css"
+ "css/notification.css",
+ "css/pwgen.css",
+ "css/username.css"
],
"run_at": "document_idle",
"all_frames": true
@@ -102,7 +108,9 @@
}
},
"web_accessible_resources": [
- "icons/key.svg"
+ "icons/keepassxc.svg",
+ "icons/key.svg",
+ "icons/locked.svg"
],
"permissions": [
"activeTab",
diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html
index f956411..1f69278 100644
--- a/keepassxc-browser/options/options.html
+++ b/keepassxc-browser/options/options.html
@@ -38,56 +38,6 @@
: error
-
-
-
-
-
-
-
-
-
+
+