web: improve compatibility with password managers
This commit is contained in:
parent
d99451b45c
commit
899cf392f4
|
@ -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 a new issue