From 12563189fa709fd658b26c03a6d6d9660abffed5 Mon Sep 17 00:00:00 2001 From: varjolintu Date: Sat, 21 Sep 2024 15:10:46 +0300 Subject: [PATCH] Add support for Creating a new credential manually --- keepassxc-browser/_locales/en/messages.json | 24 ++++++ keepassxc-browser/background/event.js | 1 + keepassxc-browser/background/page.js | 74 ++++++++++++++++ keepassxc-browser/content/banner.js | 86 +++---------------- .../content/keepassxc-browser.js | 3 + keepassxc-browser/popups/popup.css | 7 +- keepassxc-browser/popups/popup.html | 53 ++++++++++++ keepassxc-browser/popups/popup.js | 4 +- keepassxc-browser/popups/popup_functions.js | 51 ++++++++++- keepassxc-browser/popups/popup_login.html | 54 +++++++++++- keepassxc-browser/popups/popup_login.js | 2 + 11 files changed, 280 insertions(+), 79 deletions(-) diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 7a9e078..046cd5f 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -350,6 +350,14 @@ "message": "Choose a Custom Login Field", "description": "Help text for Custom Login Field banner." }, + "url": { + "message": "URL", + "description": "General text for URL." + }, + "group": { + "message": "Group", + "description": "General text for group." + }, "username": { "message": "Username", "description": "General text for username." @@ -414,6 +422,22 @@ "message": "Settings", "description": "Popup Settings button text." }, + "popupAddCredentialsText": { + "message": "Add a new credential", + "description": "Popup add a new credential text." + }, + "popupAddCredentialsPasswordText": { + "message": "If empty, KeePassXC generates the password automatically.", + "description": "Popup add a new credential password help text." + }, + "popupAddCredentialGroupText": { + "message": "Separate the group with slashes. For example: Group/ChildGroup. If empty, the default group is used.", + "description": "Popup add a new credential group help text." + }, + "popupAddCredentialsTitlePlaceholder": { + "message": "Title", + "description": "Title placeholder." + }, "popupChooseCredentialsText": { "message": "Choose Custom Login Fields", "description": "Popup credential choosing button text." diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index 253ec71..cad7e5c 100755 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -267,6 +267,7 @@ kpxcEvent.messageHandlers = { 'load_keyring': kpxcEvent.onLoadKeyRing, 'load_settings': kpxcEvent.onLoadSettings, 'lock_database': kpxcEvent.lockDatabase, + 'page_add_new_credential': page.addNewCredential, 'page_clear_logins': kpxcEvent.pageClearLogins, 'page_clear_submitted': page.clearSubmittedCredentials, 'page_get_autosubmit_performed': page.getAutoSubmitPerformed, diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index a3bfe5e..6229ff9 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -40,6 +40,7 @@ const defaultSettings = { }; const AUTO_SUBMIT_TIMEOUT = 5000; +const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords'; const page = {}; page.autoSubmitPerformed = false; @@ -149,6 +150,79 @@ page.switchTab = async function(tab) { }); }; +// Adds a new credential and handles setting/creating the group +page.addNewCredential = async function(tab, args) { + if (!tab || !page.tabs[tab.id]) { + return; + } + + // Traverse the groups and ensure all paths are found + const getDefaultGroup = function(groups, defaultGroup) { + const getGroup = function(group, splitted, depth = -1) { + ++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 [ '', '' ]; + }; + + const splitted = defaultGroup.split('/'); + return getGroup(groups, splitted); + }; + + const saveToDefaultGroup = async function(creds) { + const args = [ creds.username, creds.password, creds.url, creds.group ]; + const res = await keepass.addCredentials(tab, args); + return res; + }; + + const result = await keepass.getDatabaseGroups(tab); + if (!result || !result.groups) { + const res = await saveToDefaultGroup(args); + return res; + } + + // Group has not been set + if (args?.group === '' + || (!args?.group && (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP))) { + const res = await saveToDefaultGroup(args); + return res; + } + + // A specified group is used + const [ groupName, groupUUID ] = getDefaultGroup(result.groups[0].children, args.group || result.defaultGroup); + if (groupName === '' && groupUUID === '') { + // Create a new group + const newGroup = await keepass.createNewGroup(tab, [ args.group || result.defaultGroup ]); + if (newGroup.name && newGroup.uuid) { + const res = await keepass.addCredentials(tab, [ + args.username, + args.password, + args.url, + newGroup.name, + newGroup.uuid, + ]); + return res; + } + + return 'canceled'; + } + + const res = await await keepass.addCredentials(tab, [ + args.username, + args.password, + args.url, + groupName, + groupUUID, + ]); + return res; +}; + page.clearCredentials = async function(tabId, complete) { if (!page.tabs[tabId]) { return; diff --git a/keepassxc-browser/content/banner.js b/keepassxc-browser/content/banner.js index eb732a3..244df8b 100644 --- a/keepassxc-browser/content/banner.js +++ b/keepassxc-browser/content/banner.js @@ -1,7 +1,5 @@ 'use strict'; -const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords'; - const kpxcBanner = {}; kpxcBanner.banner = undefined; kpxcBanner.created = false; @@ -170,64 +168,16 @@ kpxcBanner.create = async function(credentials = {}) { }; kpxcBanner.saveNewCredentials = async function(credentials = {}) { - const saveToDefaultGroup = async function(creds) { - const args = [ creds.username, creds.password, creds.url ]; - const res = await sendMessage('add_credentials', args); - kpxcBanner.verifyResult(res); - }; - const result = await sendMessage('get_database_groups'); if (!result || !result.groups) { logError('Empty result from get_database_groups'); - await saveToDefaultGroup(credentials); return; } if (!result.defaultGroupAlwaysAsk) { - if (result.defaultGroup === '' || result.defaultGroup === DEFAULT_BROWSER_GROUP) { - await saveToDefaultGroup(credentials); - return; - } else { - // A specified group is used - let gname = ''; - let guuid = ''; - - if (result.defaultGroup.toLowerCase() === 'root') { - result.defaultGroup = '/'; - gname = result.groups[0].name; - guuid = result.groups[0].uuid; - } else { - [ gname, guuid ] = kpxcBanner.getDefaultGroup(result.groups[0].children, result.defaultGroup); - if (gname === '' && guuid === '') { - // Create a new group - const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]); - if (newGroup.name && newGroup.uuid) { - const res = await sendMessage('add_credentials', [ - credentials.username, - credentials.password, - credentials.url, - newGroup.name, - newGroup.uuid, - ]); - kpxcBanner.verifyResult(res); - } else { - kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup')); - } - - return; - } - } - - const res = await sendMessage('add_credentials', [ - credentials.username, - credentials.password, - credentials.url, - gname, - guuid, - ]); - kpxcBanner.verifyResult(res); - return; - } + const res = await sendMessage('page_add_new_credential', credentials); + kpxcBanner.verifyResult(res, credentials.username); + return; } const addChildren = function(group, parentElement, depth = 0) { @@ -366,19 +316,23 @@ kpxcBanner.updateCredentials = async function(credentials = {}) { } }; -kpxcBanner.verifyResult = async function(code) { +kpxcBanner.verifyResult = async function(code, givenUsername) { + const username = givenUsername || kpxcBanner.credentials.username; + if (code === 'error') { kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials')); + } else if (code === 'error_new_group') { + kpxcUI.createNotification('error', tr('rememberErrorCreatingNewGroup')); } else if (code === 'created') { kpxcUI.createNotification( 'success', - tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')), + tr('rememberCredentialsSaved', username || tr('rememberEmptyUsername')), ); await kpxc.retrieveCredentials(true); // Forced reload } else if (code === 'updated') { kpxcUI.createNotification( 'success', - tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')), + tr('rememberCredentialsUpdated', username || tr('rememberEmptyUsername')), ); await kpxc.retrieveCredentials(true); // Forced reload } else if (code === 'canceled') { @@ -386,28 +340,10 @@ kpxcBanner.verifyResult = async function(code) { } 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 = -1) { - ++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 [ '', '' ]; - }; - - const splitted = defaultGroup.split('/'); - return getGroup(groups, splitted); -}; - kpxcBanner.createCredentialDialog = async function() { kpxcBanner.shadowSelector('#kpxc-banner-btn-new').hidden = true; kpxcBanner.shadowSelector('#kpxc-banner-btn-update').hidden = true; diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 3ab81b9..fb89533 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -945,6 +945,9 @@ browser.runtime.onMessage.addListener(async function(req, sender) { kpxc.triggerActivatedTab(); } else if (req.action === 'add_allow_iframes_option') { kpxc.addToSitePreferences('allowIframes'); + } else if (req.action === 'add_new_credential') { + const res = await sendMessage('page_add_new_credential', req.args); + kpxcBanner.verifyResult(res, req.args.username); } else if (req.action === 'add_username_only_option') { kpxc.addToSitePreferences('usernameOnly', true); } else if (req.action === 'check_database_hash' && 'hash' in req) { diff --git a/keepassxc-browser/popups/popup.css b/keepassxc-browser/popups/popup.css index 3f0929f..7653551 100644 --- a/keepassxc-browser/popups/popup.css +++ b/keepassxc-browser/popups/popup.css @@ -130,7 +130,7 @@ code { display: none; } -#options-button { +#options-button, #add-credentials-button { height: 31px; width: 2.5rem; } @@ -205,3 +205,8 @@ code { color: var(--kpxc-text-color) !important; } } + +/* Add new credentials */ +.help-text { + margin-inline-start: 1.725em; +} diff --git a/keepassxc-browser/popups/popup.html b/keepassxc-browser/popups/popup.html index 7f842d0..de48d9b 100644 --- a/keepassxc-browser/popups/popup.html +++ b/keepassxc-browser/popups/popup.html @@ -23,6 +23,9 @@ +
+ + + diff --git a/keepassxc-browser/popups/popup.js b/keepassxc-browser/popups/popup.js index 2823284..40edfda 100644 --- a/keepassxc-browser/popups/popup.js +++ b/keepassxc-browser/popups/popup.js @@ -10,7 +10,7 @@ HTMLElement.prototype.hide = function() { this.style.display = 'none'; }; -function statusResponse(r) { +async function statusResponse(r) { $('#initial-state').hide(); $('#error-encountered').hide(); $('#need-reconfigure').hide(); @@ -20,6 +20,7 @@ function statusResponse(r) { $('#lock-database-button').hide(); $('#getting-started-guide').hide(); $('#database-not-opened').hide(); + $('#add-credentials-button').hide(); if (!r.keePassXCAvailable) { $('#error-message').textContent = r.error; @@ -52,6 +53,7 @@ function statusResponse(r) { $('#configured-and-associated').show(); $('#associated-identifier').textContent = r.identifier; $('#lock-database-button').show(); + $('#add-credentials-button').show(); if (r.usernameFieldDetected) { $('#username-field-detected').show(); diff --git a/keepassxc-browser/popups/popup_functions.js b/keepassxc-browser/popups/popup_functions.js index 82ad454..7a37f18 100644 --- a/keepassxc-browser/popups/popup_functions.js +++ b/keepassxc-browser/popups/popup_functions.js @@ -11,11 +11,13 @@ function updateAvailableResponse(available) { } async function initSettings() { + const tab = await getCurrentTab(); + $('#settings #options-button').addEventListener('click', () => { browser.runtime.openOptionsPage().then(close()); }); - const customLoginFieldsButton = document.body.querySelector('#settings #choose-custom-login-fields-button'); + const customLoginFieldsButton = $('#settings #choose-custom-login-fields-button'); if (isFirefox()) { customLoginFieldsButton.id = 'choose-custom-login-fields-button-moz'; } @@ -27,6 +29,36 @@ async function initSettings() { }); close(); }); + + $('#settings #add-credentials-button').addEventListener('click', () => { + $('#add-credentials-title').value = tab?.title; + $('#add-credentials-url').value = tab?.url; + if ($('#add-credentials')?.style?.display === 'none') { + $('#add-credentials').show(); + } else { + $('#add-credentials').hide(); + } + + if ($('#credentialsList')?.style?.display !== 'none') { + $('#credentialsList')?.hide(); + } else { + $('#credentialsList')?.show(); + } + }); + + $('#add-credentials-save-button').addEventListener('click', async (e) => { + if (e?.currentTarget?.form?.checkValidity()) { + await addNewCredential(); + close(); + } + }); + + $('#add-credentials-cancel-button').addEventListener('click', async () => { + $('#add-credentials').hide(); + if ($('#credentialsList')?.style?.display === 'none') { + $('#credentialsList').show(); + } + }); } async function initColorTheme() { @@ -53,6 +85,23 @@ async function getLoginData() { return logins; } +async function addNewCredential() { + const tab = await getCurrentTab(); + if (!tab) { + return []; + } + + browser.tabs.sendMessage(tab?.id, { + action: 'add_new_credential', args: { + username: $('input#add-credentials-username')?.value, + password: $('input#add-credentials-password')?.value, + group: $('input#add-credentials-group')?.value, + title: $('input#add-credentials-title')?.value, + url: $('input#add-credentials-url')?.value, + } + }); +} + (async () => { if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) { await initSettings(); diff --git a/keepassxc-browser/popups/popup_login.html b/keepassxc-browser/popups/popup_login.html index 57dad2f..777b36f 100644 --- a/keepassxc-browser/popups/popup_login.html +++ b/keepassxc-browser/popups/popup_login.html @@ -23,6 +23,9 @@ +
-
+

@@ -58,6 +61,55 @@

+ + + diff --git a/keepassxc-browser/popups/popup_login.js b/keepassxc-browser/popups/popup_login.js index 4703b9a..41ca94f 100644 --- a/keepassxc-browser/popups/popup_login.js +++ b/keepassxc-browser/popups/popup_login.js @@ -68,6 +68,8 @@ $('#credentialsList').hide(); $('#database-not-opened').show(); $('#lock-database-button').hide(); + $('#add-credentials').hide(); + $('#add-credentials-button').hide(); $('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened'); });