Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions keepassxc-browser/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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."
Expand Down
4 changes: 3 additions & 1 deletion keepassxc-browser/background/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions keepassxc-browser/background/keepass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -69,7 +69,8 @@ keepass.updateCredentials = async function(tab, args = []) {
login: username,
password: password,
url: url,
submitUrl: url
submitUrl: url,
generatePassword: generatePassword
};

if (entryId) {
Expand Down
82 changes: 82 additions & 0 deletions keepassxc-browser/background/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const defaultSettings = {
};

const AUTO_SUBMIT_TIMEOUT = 5000;
const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords';

const page = {};
page.autoSubmitPerformed = false;
Expand Down Expand Up @@ -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;
Expand Down
86 changes: 11 additions & 75 deletions keepassxc-browser/content/banner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

const DEFAULT_BROWSER_GROUP = 'KeePassXC-Browser Passwords';

const kpxcBanner = {};
kpxcBanner.banner = undefined;
kpxcBanner.created = false;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -366,46 +316,32 @@ 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') {
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 = -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() {
Expand Down
3 changes: 3 additions & 0 deletions keepassxc-browser/content/keepassxc-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion keepassxc-browser/popups/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ code {
display: none;
}

#options-button {
#options-button, #add-credentials-button {
height: 31px;
width: 2.5rem;
}
Expand Down Expand Up @@ -205,3 +205,8 @@ code {
color: var(--kpxc-text-color) !important;
}
}

/* Add new credentials */
.help-text {
margin-inline-start: 1.725em;
}
Loading