Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove username field guessing #44

Merged
merged 2 commits into from
Mar 26, 2024
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
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
Loading