Skip to content

Commit

Permalink
Adjust password manager polyfill for form injections (#17830)
Browse files Browse the repository at this point in the history
  • Loading branch information
steverep authored Sep 19, 2023
1 parent 713ebfc commit e46f2cd
Showing 1 changed file with 68 additions and 23 deletions.
91 changes: 68 additions & 23 deletions src/auth/ha-password-manager-polyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,47 @@ export class HaPasswordManagerPolyfill extends LitElement {
super.connectedCallback();
this._styleElement = document.createElement("style");
this._styleElement.textContent = css`
/* Polyfill form is sized and vertically aligned with true form, then positioned offscreen
rather than hiding so it does not create a new stacking context */
.password-manager-polyfill {
position: absolute;
opacity: 0;
z-index: -1;
box-sizing: border-box;
}
/* Excluding our wrapper, move any children back on screen, including anything injected that might not already be positioned */
.password-manager-polyfill > *:not(.wrapper),
.password-manager-polyfill > .wrapper > * {
position: relative;
left: 10000px;
}
.password-manager-polyfill input {
/* Size and hide our polyfill fields */
.password-manager-polyfill .underneath {
display: block;
box-sizing: border-box;
width: 100%;
height: 62px;
padding: 0;
padding: 0 16px;
border: 0;
z-index: -1;
height: 21px;
/* Transparency is only needed to hide during paint or in case of misalignment,
but LastPass will fail if it's 0, so we use 1% */
opacity: 0.01;
}
.password-manager-polyfill input.underneath {
height: 28px;
margin-bottom: 30.5px;
}
/* Button position is not important, but size should not be zero */
.password-manager-polyfill > input.underneath[type="submit"] {
width: 1px;
height: 1px;
margin: 0 auto;
overflow: hidden;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
/* Ensure injected elements will be on top */
.password-manager-polyfill > *:not(.underneath, .wrapper),
.password-manager-polyfill > .wrapper > *:not(.underneath) {
isolation: isolate;
z-index: auto;
}
`.toString();
document.head.append(this._styleElement);
Expand Down Expand Up @@ -77,16 +104,25 @@ export class HaPasswordManagerPolyfill extends LitElement {
class="password-manager-polyfill"
style=${styleMap({
top: `${this.boundingRect?.y || 148}px`,
left: `calc(50% - ${(this.boundingRect?.width || 360) / 2}px)`,
left: `calc(50% - ${
(this.boundingRect?.width || 360) / 2
}px - 10000px)`,
width: `${this.boundingRect?.width || 360}px`,
})}
aria-hidden="true"
action="/auth"
method="post"
@submit=${this._handleSubmit}
>
${autocompleteLoginFields(this.step.data_schema).map((input) =>
this.render_input(input)
)}
<input type="submit" />
<input
type="submit"
value="Login"
class="underneath"
tabindex="-2"
aria-hidden="true"
/>
</form>
`;
}
Expand All @@ -99,26 +135,35 @@ export class HaPasswordManagerPolyfill extends LitElement {
return "";
}
return html`
<input
tabindex="-1"
.id=${schema.name}
.name=${schema.name}
.type=${inputType}
.value=${this.stepData[schema.name] || ""}
.autocomplete=${schema.autocomplete}
@input=${this._valueChanged}
@change=${this._valueChanged}
/>
<!-- Label is a sibling so it can be stacked underneath without affecting injections adjacent to input (e.g. LastPass) -->
<label for=${schema.name} class="underneath" aria-hidden="true">
${schema.name}
</label>
<!-- LastPass fails if the input is hidden directly, so we trick it and hide a wrapper instead -->
<div class="wrapper" aria-hidden="true">
<!-- LastPass fails with tabindex of -1, so we trick with -2 -->
<input
class="underneath"
tabindex="-2"
.id=${schema.name}
.name=${schema.name}
.type=${inputType}
.value=${this.stepData[schema.name] || ""}
.autocomplete=${schema.autocomplete}
@input=${this._valueChanged}
@change=${this._valueChanged}
/>
</div>
`;
}

private _handleSubmit(ev: Event) {
private _handleSubmit(ev: SubmitEvent) {
ev.preventDefault();
fireEvent(this, "form-submitted");
}

private _valueChanged(ev: Event) {
const target = ev.target! as HTMLInputElement;
const target = ev.target as HTMLInputElement;
this.stepData = { ...this.stepData, [target.id]: target.value };
fireEvent(this, "value-changed", {
value: this.stepData,
Expand Down

0 comments on commit e46f2cd

Please sign in to comment.