Skip to content

Commit

Permalink
Merge pull request #44 from buttercup/fix/stability
Browse files Browse the repository at this point in the history
Remove username field guessing
  • Loading branch information
perry-mitchell authored Mar 26, 2024
2 parents ed3e0f2 + 72dd520 commit c7d3228
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 49 deletions.
20 changes: 11 additions & 9 deletions source/LoginTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class LoginTarget extends EventEmitter<LoginTargetEvents> {
[LoginTargetFeature.Form]: null
};
protected _forceSubmitDelay: number = FORCE_SUBMIT_DELAY;
protected _form: HTMLFormElement | null = null;
protected _form: HTMLFormElement | HTMLDivElement | null = null;
protected _otpField: HTMLInputElement | null = null;
protected _passwordField: HTMLInputElement | null = null;
protected _submitButton: HTMLElement | null = null;
Expand Down Expand Up @@ -100,7 +100,7 @@ export class LoginTarget extends EventEmitter<LoginTargetEvents> {
this._forceSubmitDelay = delay;
}

set form(form: HTMLFormElement) {
set form(form: HTMLFormElement | HTMLDivElement) {
if (form) {
this._form = form;
this._listenForUpdates(LoginTargetFeature.Form, form);
Expand Down Expand Up @@ -228,13 +228,15 @@ export class LoginTarget extends EventEmitter<LoginTargetEvents> {
* @returns A promise that resolves once submission has been completed
*/
async submit(force: boolean = false): Promise<void> {
if (!this.submitButton) {
if (this.submitButton) {
// Click button
this.submitButton.click();
} else if (this.form.tagName.toLowerCase() === "form") {
// No button, just try submitting
this.form.submit();
return Promise.resolve();
(this.form as HTMLFormElement).submit();
} else {
throw new Error("Invalid form: Not form element and no valid submit button");
}
// Click button
this.submitButton.click();
if (force) {
await this._waitForNoUnload();
}
Expand Down Expand Up @@ -313,9 +315,9 @@ export class LoginTarget extends EventEmitter<LoginTargetEvents> {
});
})
]);
if (!hasUnloaded) {
if (!hasUnloaded && this.form.tagName.toLowerCase() === "form") {
// No unload events detected, so we need for force submit
this.form.submit();
(this.form as HTMLFormElement).submit();
}
}
}
62 changes: 22 additions & 40 deletions source/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { LocustInputEvent } from "./LocustInputEvent.js";

export interface FetchedForm {
form: HTMLFormElement;
form: HTMLFormElement | HTMLDivElement;
usernameFields: Array<HTMLInputElement>;
otpFields: Array<HTMLInputElement>;
passwordFields: Array<HTMLInputElement>;
Expand Down Expand Up @@ -77,32 +77,27 @@ function fetchForms(queryEl: Document | HTMLElement = document): Array<HTMLFormE
return Array.prototype.slice.call(queryEl.querySelectorAll(FORM_QUERIES.join(",")));
}

export function fetchFormsWithInputs(queryEl: Document | HTMLElement = document) {
return fetchForms(queryEl)
.map((formEl) => {
let usernameFields = fetchUsernameInputs(formEl);
const passwordFields = fetchPasswordInputs(formEl);
const otpFields = fetchOTPInputs(formEl);
if (otpFields.length > 0 && passwordFields.length === 0) {
// No password fields, so filter out any OTP fields from the potential username fields
usernameFields = usernameFields.filter(field => otpFields.includes(field) === false);
}
const form: FetchedForm = {
form: formEl,
usernameFields,
passwordFields,
otpFields,
submitButtons: fetchSubmitButtons(formEl)
};
if (form.usernameFields.length <= 0 && otpFields.length <= 0) {
const input = guessUsernameInput(formEl);
if (input) {
form.usernameFields.push(input);
}
}
return form;
})
.filter((form) => form.otpFields.length + form.passwordFields.length + form.usernameFields.length > 0);
export function fetchFormsWithInputs(queryEl: Document | HTMLElement = document): Array<FetchedForm> {
return fetchForms(queryEl).reduce((output: Array<FetchedForm>, formEl: HTMLFormElement | HTMLDivElement) => {
let usernameFields = fetchUsernameInputs(formEl);
const passwordFields = fetchPasswordInputs(formEl);
const otpFields = fetchOTPInputs(formEl);
if (otpFields.length > 0 && passwordFields.length === 0) {
// No password fields, so filter out any OTP fields from the potential username fields
usernameFields = usernameFields.filter(field => otpFields.includes(field) === false);
}
const form: FetchedForm = {
form: formEl,
usernameFields,
passwordFields,
otpFields,
submitButtons: fetchSubmitButtons(formEl)
};
if (form.usernameFields.length <= 0 && otpFields.length <= 0 && passwordFields.length <= 0) {
return output;
}
return [...output, form];
}, []);
}

function fetchOTPInputs(queryEl: Document | HTMLElement = document): Array<HTMLInputElement> {
Expand All @@ -129,19 +124,6 @@ function fetchUsernameInputs(queryEl: Document | HTMLElement = document): Array<
return sortFormElements(inputs, "username");
}

function guessUsernameInput(formEl: HTMLFormElement): HTMLInputElement | null {
const elements = /^form$/i.test(formEl.tagName)
? [...formEl.elements]
: [...formEl.querySelectorAll("input")];
const possibleInputs = elements.filter((el) => {
if (el.tagName.toLowerCase() !== "input") return false;
if (["email", "text"].indexOf(el.getAttribute("type")) === -1) return false;
if (/pass(word)?/.test(el.outerHTML)) return false;
return true;
});
return possibleInputs.length > 0 ? possibleInputs[0] as HTMLInputElement : null;
}

function isInput(el: Element): boolean {
return el.tagName?.toLowerCase() === "input";
}
Expand Down

0 comments on commit c7d3228

Please sign in to comment.