Add support for custom password change forms (#2383)

Add support for custom password change forms
This commit is contained in:
Sami Vänttinen 2024-11-11 16:27:22 +02:00 committed by GitHub
parent 9066f67e53
commit ef2225f6b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 52 deletions

View file

@ -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;
@ -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) {
@ -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;

View file

@ -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);
@ -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;
@ -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) {
@ -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

View file

@ -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();