Skip to content
Merged
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
15 changes: 9 additions & 6 deletions keepassxc-browser/common/sites.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ kpxcSites.expectedTOTPMaxLength = function() {

/**
* Handles a few exceptions for certain sites where form submit button is not regognized properly.
* @param {object} form Form element
* @param {object} form Form element (optional)
* @returns {object} Button element
*/
kpxcSites.formSubmitButtonExceptionFound = function(form) {
if (form.action.startsWith(googleUrl)) {
if (form?.action?.startsWith(googleUrl)) {
const findDiv = $('#identifierNext, #passwordNext');
if (!findDiv) {
return undefined;
Expand All @@ -162,14 +162,14 @@ kpxcSites.formSubmitButtonExceptionFound = function(form) {
const buttons = findDiv.getElementsByTagName('button');
kpxcSites.savedForm = form;
return buttons.length > 0 ? buttons[0] : undefined;
} else if (form.action.startsWith('https://www.ebay.')) {
} else if (form?.action?.startsWith('https://www.ebay.')) {
// For eBay we must return the first button.
for (const i of form.elements) {
if (i.type === 'button') {
return i;
}
}
} else if (form.action.includes('signin.aws.amazon.com')) {
} else if (form?.action?.includes('signin.aws.amazon.com')) {
// For Amazon AWS the button is outside the form.
const button = $('#signin_button');
if (button) {
Expand All @@ -182,14 +182,17 @@ kpxcSites.formSubmitButtonExceptionFound = function(form) {
'odc.officeapps.live.com',
'login.microsoftonline.com',
'login.microsoftonline.us',
].some(u => form.action.includes(u))) {
].some(u => form?.action?.includes(u))) {
const buttons = Array.from(form.querySelectorAll(kpxcForm.formButtonQuery));
if (buttons?.length > 1) {
return buttons[1];
}
} else if (form.action.startsWith('https://barmerid.id.bconnect.barmer.de')) {
} else if (form?.action?.startsWith('https://barmerid.id.bconnect.barmer.de')) {
const loginButton = $('#btn-login');
return loginButton?.shadowRoot?.children?.[0];
} else if (!form && document.location.href.includes('reddit.com/settings')) {
// Reddit change password popup
return $('.button[slot=primary-button]');
}

return undefined;
Expand Down
122 changes: 78 additions & 44 deletions keepassxc-browser/content/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,39 @@
*/
const kpxcForm = {};
kpxcForm.formButtonQuery = 'button[type=button], button[type=submit], input[type=button], input[type=submit], button:not([type]), div[role=button]';
kpxcForm.savedCustomInputs = [];
kpxcForm.savedForms = [];
kpxcForm.submitTriggered = false;

// Activate the Credential Banner if existing credentials are not found
kpxcForm.activateCredentialBanner = async function(usernameValue, passwordInputs, passwordField) {
let passwordValue = '';
// Check if the form has three password fields -> a possible password change form
if (passwordInputs && passwordInputs.length >= 2) {
passwordValue = kpxcForm.getNewPassword(passwordInputs);
} 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)) {
kpxcForm.submitTriggered = false;
return;
}

if (passwordField) {
await kpxc.setPasswordFilled(true);
}

const url = trimURL(kpxc.settings.saveDomainOnlyNewCreds ? window.top.location.origin : window.top.location.href);
await sendMessage('page_set_submitted', [ true, usernameValue, passwordValue, url, kpxc.credentials ]);

// Show the banner if the page does not reload
kpxc.rememberCredentials(usernameValue, passwordValue);
kpxcForm.submitTriggered = false;
};

// Returns true if form has been already saved
kpxcForm.formIdentified = function(form) {
return kpxcForm.savedForms.some(f => f.form === form);
Expand Down Expand Up @@ -94,8 +124,29 @@ kpxcForm.getNewPassword = function(passwordInputs = []) {
return '';
};

// Returns the username value from an input field or selected login
kpxcForm.getUsernameValue = async function(usernameField) {
if (usernameField) {
return 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
return kpxc.credentials[0].login;
} else {
// Multiple entries found for the page, try to find out which one might have been used
const pageUuid = await sendMessage('page_get_login_id');
if (pageUuid) {
const credential = kpxc.credentials.find(c => c.uuid === pageUuid);
if (credential) {
return credential.login;
}
}
}

return '';
};

// Initializes form and attaches the submit button to our own callback
kpxcForm.init = function(form, credentialFields) {
kpxcForm.initForm = function(form, credentialFields) {
if (!form.action || typeof form.action !== 'string') {
logDebug('Error: Form action is not found.');
return;
Expand All @@ -113,6 +164,30 @@ kpxcForm.init = function(form, credentialFields) {
}
};

// Initialize a "form" where three different password input fields are in a combination
kpxcForm.initCustomForm = function(combinations) {
if (combinations?.length >= 2 && combinations?.every(c => !c?.form && !c.username && c.password)) {
kpxcForm.savedCustomInputs = [];
const submitButton = kpxcSites.formSubmitButtonExceptionFound();
if (submitButton) {
kpxcForm.savedCustomInputs = combinations?.map(c => c.password);
submitButton.addEventListener('click', kpxcForm.onCustomFormSubmit);
}
}
};

// Triggers when a custom form has been identified with a specific form submit button
kpxcForm.onCustomFormSubmit = async function(e) {
if (!e.isTrusted || kpxcForm.savedCustomInputs?.length === 0) {
return;
}

kpxcForm.submitTriggered = true;

const usernameValue = await kpxcForm.getUsernameValue();
await kpxcForm.activateCredentialBanner(usernameValue, kpxcForm.savedCustomInputs);
};

// Triggers when form is submitted. Shows the credential banner
kpxcForm.onSubmit = async function(e) {
if (!e.isTrusted) {
Expand Down Expand Up @@ -154,49 +229,8 @@ kpxcForm.onSubmit = async function(e) {
}

const [ usernameField, passwordField, passwordInputs ] = kpxcForm.getCredentialFieldsFromForm(form);
let usernameValue = '';
let passwordValue = '';

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;
} else {
// Multiple entries found for the page, try to find out which one might have been used
const pageUuid = await sendMessage('page_get_login_id');
if (pageUuid) {
const credential = kpxc.credentials.find(c => c.uuid === pageUuid);
if (credential) {
usernameValue = credential.login;
}
}
}

// Check if the form has three password fields -> a possible password change form
if (passwordInputs && passwordInputs.length >= 2) {
passwordValue = kpxcForm.getNewPassword(passwordInputs);
} 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)) {
kpxcForm.submitTriggered = false;
return;
}

if (passwordField) {
await kpxc.setPasswordFilled(true);
}

const url = trimURL(kpxc.settings.saveDomainOnlyNewCreds ? window.top.location.origin : window.top.location.href);
await sendMessage('page_set_submitted', [ true, usernameValue, passwordValue, url, kpxc.credentials ]);

// Show the banner if the page does not reload
kpxc.rememberCredentials(usernameValue, passwordValue);
kpxcForm.submitTriggered = false;
const usernameValue = await kpxcForm.getUsernameValue(usernameField);
await kpxcForm.activateCredentialBanner(usernameValue, passwordInputs, passwordField);
};

// Save form to Object array
Expand Down
9 changes: 7 additions & 2 deletions keepassxc-browser/content/keepassxc-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,20 @@ kpxc.initCombinations = async function(inputs = []) {
const field = c.username || c.password;
if (field && c.form) {
// Initialize form-submit for remembering credentials
kpxcForm.init(c.form, c);
kpxcForm.initForm(c.form, c);
}

// Don't allow duplicates
if (!kpxc.combinations.some(f => f.username === c.username && f.password === c.password && f.totp === c.totp && f.form === c.form)) {
if (!kpxc.combinations.some(f =>
f.username === c.username && f.password === c.password && f.totp === c.totp && f.form === c.form
)) {
kpxc.combinations.push(c);
}
}

// Identify a custom password change form
kpxcForm.initCustomForm(combinations);

// Update the fields in Custom Login Fields banner if it's open
if (kpxcCustomLoginFieldsBanner.created) {
kpxcCustomLoginFieldsBanner.updateFieldSelections();
Expand Down