web: improve compatibility with password managers
This commit is contained in:
		@ -5,6 +5,12 @@ html {
 | 
				
			|||||||
    --pf-c-nav__link--PaddingLeft: 0.5rem;
 | 
					    --pf-c-nav__link--PaddingLeft: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html > input {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: -2000px;
 | 
				
			||||||
 | 
					    left: -2000px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pf-c-page__header {
 | 
					.pf-c-page__header {
 | 
				
			||||||
    z-index: 0;
 | 
					    z-index: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import { BaseStage } from "../base";
 | 
				
			|||||||
import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
 | 
					import { AuthenticatorValidateStage, AuthenticatorValidateStageChallenge, DeviceChallenge } from "./AuthenticatorValidateStage";
 | 
				
			||||||
import "../form";
 | 
					import "../form";
 | 
				
			||||||
import "../../../elements/utils/LoadingState";
 | 
					import "../../../elements/utils/LoadingState";
 | 
				
			||||||
 | 
					import { PasswordManagerPrefill } from "../identification/IdentificationStage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-stage-authenticator-validate-code")
 | 
					@customElement("ak-stage-authenticator-validate-code")
 | 
				
			||||||
export class AuthenticatorValidateStageWebCode extends BaseStage {
 | 
					export class AuthenticatorValidateStageWebCode extends BaseStage {
 | 
				
			||||||
@ -53,6 +54,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
 | 
				
			|||||||
                        autofocus=""
 | 
					                        autofocus=""
 | 
				
			||||||
                        autocomplete="one-time-code"
 | 
					                        autocomplete="one-time-code"
 | 
				
			||||||
                        class="pf-c-form-control"
 | 
					                        class="pf-c-form-control"
 | 
				
			||||||
 | 
					                        value="${PasswordManagerPrefill.totp || ""}"
 | 
				
			||||||
                        required="">
 | 
					                        required="">
 | 
				
			||||||
                </ak-form-element>
 | 
					                </ak-form-element>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,14 @@ import "../form";
 | 
				
			|||||||
import "../../../elements/utils/LoadingState";
 | 
					import "../../../elements/utils/LoadingState";
 | 
				
			||||||
import { Challenge } from "../../../api/Flows";
 | 
					import { Challenge } from "../../../api/Flows";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PasswordManagerPrefill: {
 | 
				
			||||||
 | 
					    password: string | undefined;
 | 
				
			||||||
 | 
					    totp: string | undefined;
 | 
				
			||||||
 | 
					} = {
 | 
				
			||||||
 | 
					    password: undefined,
 | 
				
			||||||
 | 
					    totp: undefined,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IdentificationChallenge extends Challenge {
 | 
					export interface IdentificationChallenge extends Challenge {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    input_type: string;
 | 
					    input_type: string;
 | 
				
			||||||
@ -41,6 +49,69 @@ export class IdentificationStage extends BaseStage {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    firstUpdated(): void {
 | 
				
			||||||
 | 
					        // This is a workaround for the fact that we're in a shadow dom
 | 
				
			||||||
 | 
					        // adapted from https://github.com/home-assistant/frontend/issues/3133
 | 
				
			||||||
 | 
					        const username = document.createElement("input");
 | 
				
			||||||
 | 
					        username.setAttribute("type", "text");
 | 
				
			||||||
 | 
					        username.setAttribute("name", "username"); // username as name for high compatibility
 | 
				
			||||||
 | 
					        username.setAttribute("autocomplete", "username");
 | 
				
			||||||
 | 
					        username.onkeyup = (ev: Event) => {
 | 
				
			||||||
 | 
					            const el = ev.target as HTMLInputElement;
 | 
				
			||||||
 | 
					            (this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
 | 
				
			||||||
 | 
					                input.value = el.value;
 | 
				
			||||||
 | 
					                // Because we assume only one input field exists that matches this
 | 
				
			||||||
 | 
					                // call focus so the user can press enter
 | 
				
			||||||
 | 
					                input.focus();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        document.documentElement.appendChild(username);
 | 
				
			||||||
 | 
					        const password = document.createElement("input");
 | 
				
			||||||
 | 
					        password.setAttribute("type", "password");
 | 
				
			||||||
 | 
					        password.setAttribute("name", "password");
 | 
				
			||||||
 | 
					        password.setAttribute("autocomplete", "current-password");
 | 
				
			||||||
 | 
					        password.onkeyup = (ev: KeyboardEvent) => {
 | 
				
			||||||
 | 
					            if (ev.key == "Enter") {
 | 
				
			||||||
 | 
					                this.submitForm(ev);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const el = ev.target as HTMLInputElement;
 | 
				
			||||||
 | 
					            // Because the password field is not actually on this page,
 | 
				
			||||||
 | 
					            // and we want to 'prefill' the password for the user,
 | 
				
			||||||
 | 
					            // save it globally
 | 
				
			||||||
 | 
					            PasswordManagerPrefill.password = el.value;
 | 
				
			||||||
 | 
					            // Because password managers fill username, then password,
 | 
				
			||||||
 | 
					            // we need to re-focus the uid_field here too
 | 
				
			||||||
 | 
					            (this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
 | 
				
			||||||
 | 
					                // Because we assume only one input field exists that matches this
 | 
				
			||||||
 | 
					                // call focus so the user can press enter
 | 
				
			||||||
 | 
					                input.focus();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        document.documentElement.appendChild(password);
 | 
				
			||||||
 | 
					        const totp = document.createElement("input");
 | 
				
			||||||
 | 
					        totp.setAttribute("type", "text");
 | 
				
			||||||
 | 
					        totp.setAttribute("name", "code");
 | 
				
			||||||
 | 
					        totp.setAttribute("autocomplete", "one-time-code");
 | 
				
			||||||
 | 
					        totp.onkeyup = (ev: KeyboardEvent) => {
 | 
				
			||||||
 | 
					            if (ev.key == "Enter") {
 | 
				
			||||||
 | 
					                this.submitForm(ev);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const el = ev.target as HTMLInputElement;
 | 
				
			||||||
 | 
					            // Because the totp field is not actually on this page,
 | 
				
			||||||
 | 
					            // and we want to 'prefill' the totp for the user,
 | 
				
			||||||
 | 
					            // save it globally
 | 
				
			||||||
 | 
					            PasswordManagerPrefill.totp = el.value;
 | 
				
			||||||
 | 
					            // Because totp managers fill username, then password, then optionally,
 | 
				
			||||||
 | 
					            // we need to re-focus the uid_field here too
 | 
				
			||||||
 | 
					            (this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
 | 
				
			||||||
 | 
					                // Because we assume only one input field exists that matches this
 | 
				
			||||||
 | 
					                // call focus so the user can press enter
 | 
				
			||||||
 | 
					                input.focus();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        document.documentElement.appendChild(totp);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderSource(source: UILoginButton): TemplateResult {
 | 
					    renderSource(source: UILoginButton): TemplateResult {
 | 
				
			||||||
        let icon = html`<i class="pf-icon pf-icon-arrow" title="${source.name}"></i>`;
 | 
					        let icon = html`<i class="pf-icon pf-icon-arrow" title="${source.name}"></i>`;
 | 
				
			||||||
        if (source.icon_url) {
 | 
					        if (source.icon_url) {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import { COMMON_STYLES } from "../../../common/styles";
 | 
				
			|||||||
import { BaseStage } from "../base";
 | 
					import { BaseStage } from "../base";
 | 
				
			||||||
import "../form";
 | 
					import "../form";
 | 
				
			||||||
import "../../../elements/utils/LoadingState";
 | 
					import "../../../elements/utils/LoadingState";
 | 
				
			||||||
 | 
					import { PasswordManagerPrefill } from "../identification/IdentificationStage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PasswordChallenge extends WithUserInfoChallenge {
 | 
					export interface PasswordChallenge extends WithUserInfoChallenge {
 | 
				
			||||||
    recovery_url?: string;
 | 
					    recovery_url?: string;
 | 
				
			||||||
@ -43,6 +44,7 @@ export class PasswordStage extends BaseStage {
 | 
				
			|||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <input name="username" autocomplete="username" type="hidden" value="${this.challenge.pending_user}">
 | 
				
			||||||
                    <ak-form-element
 | 
					                    <ak-form-element
 | 
				
			||||||
                        label="${gettext("Password")}"
 | 
					                        label="${gettext("Password")}"
 | 
				
			||||||
                        ?required="${true}"
 | 
					                        ?required="${true}"
 | 
				
			||||||
@ -54,7 +56,8 @@ export class PasswordStage extends BaseStage {
 | 
				
			|||||||
                            autofocus=""
 | 
					                            autofocus=""
 | 
				
			||||||
                            autocomplete="current-password"
 | 
					                            autocomplete="current-password"
 | 
				
			||||||
                            class="pf-c-form-control"
 | 
					                            class="pf-c-form-control"
 | 
				
			||||||
                            required="">
 | 
					                            required=""
 | 
				
			||||||
 | 
					                            value=${PasswordManagerPrefill.password || ""}>
 | 
				
			||||||
                    </ak-form-element>
 | 
					                    </ak-form-element>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ${this.challenge.recovery_url ?
 | 
					                    ${this.challenge.recovery_url ?
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user