diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 7a9e07835..046cd5f20 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 253ec71c2..89141898a 100755 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -27,11 +27,12 @@ kpxcEvent.showStatus = async function(tab, configured, internalPoll) { const errorMessage = page.tabs[tab.id]?.errorMessage ?? undefined; const usernameFieldDetected = page.tabs[tab.id]?.usernameFieldDetected ?? false; const iframeDetected = page.tabs[tab.id]?.iframeDetected ?? false; + const compareResult = keepass.compareMultipleVersions([ '2.7.11' ], keepass.currentKeePassXC); return { associated: keepass.isAssociated(), - configured: configured, + compareResult: compareResult, databaseClosed: keepass.isDatabaseClosed, encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized, error: errorMessage, @@ -267,6 +268,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/keepass.js b/keepassxc-browser/background/keepass.js index e4bf97938..efd69b1ff 100755 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -46,13 +46,13 @@ browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': n //-------------------------------------------------------------------------- keepass.addCredentials = async function(tab, args = []) { - const [ username, password, url, group, groupUuid ] = args; - return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]); + const [ username, password, url, group, groupUuid, generatePassword ] = args; + return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid, generatePassword ]); }; keepass.updateCredentials = async function(tab, args = []) { try { - const [ entryId, username, password, url, group, groupUuid ] = args; + const [ entryId, username, password, url, group, groupUuid, generatePassword ] = args; const taResponse = await keepass.testAssociation(tab); if (!taResponse) { browserAction.showDefault(tab); @@ -69,7 +69,8 @@ keepass.updateCredentials = async function(tab, args = []) { login: username, password: password, url: url, - submitUrl: url + submitUrl: url, + generatePassword: generatePassword }; if (entryId) { diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index a3bfe5eb2..3c91af2fe 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,87 @@ 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 res = await keepass.addCredentials(tab, [ + creds.username, + creds.password, + creds.url, + creds.group, + undefined, + creds?.generatePassword + ]); + 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, + args?.generatePassword + ]); + return res; + } + + return 'canceled'; + } + + const res = await await keepass.addCredentials(tab, [ + args.username, + args.password, + args.url, + groupName, + groupUUID, + args?.generatePassword + ]); + 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 eb732a322..244df8b75 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,26 +340,8 @@ 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.destroy(); }; kpxcBanner.createCredentialDialog = async function() { diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 3ab81b9fb..fb8953383 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 3f0929fea..7653551a6 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 7f842d0c4..2f6771ccb 100644 --- a/keepassxc-browser/popups/popup.html +++ b/keepassxc-browser/popups/popup.html @@ -23,6 +23,9 @@ +
+ + +