293 lines
8.7 KiB
TypeScript
293 lines
8.7 KiB
TypeScript
import "@material/mwc-button";
|
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
import { customElement, property, state } from "lit/decorators";
|
|
import "../../components/ha-circular-progress";
|
|
import "../../components/ha-dialog";
|
|
import "../../components/ha-form/ha-form";
|
|
import "../../components/ha-qr-code";
|
|
import { autocompleteLoginFields } from "../../data/auth";
|
|
import {
|
|
DataEntryFlowStep,
|
|
DataEntryFlowStepForm,
|
|
} from "../../data/data_entry_flow";
|
|
import { haStyleDialog } from "../../resources/styles";
|
|
import { HomeAssistant } from "../../types";
|
|
|
|
let instance = 0;
|
|
|
|
@customElement("ha-mfa-module-setup-flow")
|
|
class HaMfaModuleSetupFlow extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@state() private _dialogClosedCallback?: (params: {
|
|
flowFinished: boolean;
|
|
}) => void;
|
|
|
|
@state() private _instance?: number;
|
|
|
|
@state() private _loading = false;
|
|
|
|
@state() private _opened = false;
|
|
|
|
@state() private _stepData: any = {};
|
|
|
|
@state() private _step?: DataEntryFlowStep;
|
|
|
|
@state() private _errorMessage?: string;
|
|
|
|
public showDialog({ continueFlowId, mfaModuleId, dialogClosedCallback }) {
|
|
this._instance = instance++;
|
|
this._dialogClosedCallback = dialogClosedCallback;
|
|
this._opened = true;
|
|
|
|
const fetchStep = continueFlowId
|
|
? this.hass.callWS({
|
|
type: "auth/setup_mfa",
|
|
flow_id: continueFlowId,
|
|
})
|
|
: this.hass.callWS({
|
|
type: "auth/setup_mfa",
|
|
mfa_module_id: mfaModuleId,
|
|
});
|
|
|
|
const curInstance = this._instance;
|
|
|
|
fetchStep.then((step) => {
|
|
if (curInstance !== this._instance) return;
|
|
|
|
this._processStep(step);
|
|
});
|
|
}
|
|
|
|
public closeDialog() {
|
|
// Closed dialog by clicking on the overlay
|
|
if (this._step) {
|
|
this._flowDone();
|
|
}
|
|
this._opened = false;
|
|
}
|
|
|
|
protected render() {
|
|
if (!this._opened) {
|
|
return nothing;
|
|
}
|
|
|
|
return html`
|
|
<ha-dialog
|
|
open
|
|
.heading=${this._computeStepTitle()}
|
|
@closed=${this.closeDialog}
|
|
>
|
|
<div>
|
|
${this._errorMessage
|
|
? html`<div class="error">${this._errorMessage}</div>`
|
|
: ""}
|
|
${!this._step
|
|
? html`<div class="init-spinner">
|
|
<ha-circular-progress indeterminate></ha-circular-progress>
|
|
</div>`
|
|
: html`${this._step.type === "abort"
|
|
? html`<p>
|
|
${this.hass.localize(
|
|
`component.auth.mfa_setup.${this._step.handler}.abort.${this._step.reason}`
|
|
)}
|
|
</p> `
|
|
: this._step.type === "create_entry"
|
|
? html`<p>
|
|
${this.hass.localize(
|
|
"ui.panel.profile.mfa_setup.step_done",
|
|
{ step: this._step.title || this._step.handler }
|
|
)}
|
|
</p>`
|
|
: this._step.type === "form"
|
|
? html` <p>
|
|
${this.hass.localize(
|
|
`component.auth.mfa_setup.${
|
|
this._step!.handler
|
|
}.step.${
|
|
(this._step! as DataEntryFlowStepForm).step_id
|
|
}.description`
|
|
)}
|
|
</p>
|
|
<p>
|
|
${this.hass.localize(
|
|
`ui.panel.profile.mfa_setup.totp.qr_code`
|
|
)}
|
|
</p>
|
|
<p>
|
|
<a href=${this._step!.description_placeholders!.url}>
|
|
<ha-qr-code
|
|
.data=${this._step!.description_placeholders!.url}
|
|
errorCorrectionLevel="quartile"
|
|
scale="5"
|
|
margin="1"
|
|
></ha-qr-code>
|
|
</a>
|
|
<small>
|
|
${this.hass.localize(
|
|
`ui.panel.profile.mfa_setup.totp.key_code`
|
|
)}
|
|
${this._step!.description_placeholders!.code}
|
|
</small>
|
|
</p>
|
|
<p>
|
|
${this.hass.localize(
|
|
`ui.panel.profile.mfa_setup.totp.verification_code`
|
|
)}
|
|
</p>
|
|
<ha-form
|
|
.hass=${this.hass}
|
|
.data=${this._stepData}
|
|
.schema=${autocompleteLoginFields(
|
|
this._step.data_schema
|
|
)}
|
|
.error=${this._step.errors}
|
|
.computeLabel=${this._computeLabel}
|
|
.computeError=${this._computeError}
|
|
@value-changed=${this._stepDataChanged}
|
|
></ha-form>`
|
|
: ""}`}
|
|
</div>
|
|
${["abort", "create_entry"].includes(this._step?.type || "")
|
|
? html`<mwc-button slot="primaryAction" @click=${this.closeDialog}
|
|
>${this.hass.localize(
|
|
"ui.panel.profile.mfa_setup.close"
|
|
)}</mwc-button
|
|
>`
|
|
: ""}
|
|
${this._step?.type === "form"
|
|
? html`<mwc-button
|
|
slot="primaryAction"
|
|
.disabled=${this._loading}
|
|
@click=${this._submitStep}
|
|
>${this.hass.localize(
|
|
"ui.panel.profile.mfa_setup.submit"
|
|
)}</mwc-button
|
|
>`
|
|
: ""}
|
|
</ha-dialog>
|
|
`;
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return [
|
|
haStyleDialog,
|
|
css`
|
|
.error {
|
|
color: red;
|
|
}
|
|
ha-dialog {
|
|
max-width: 500px;
|
|
}
|
|
.init-spinner {
|
|
padding: 10px 100px 34px;
|
|
text-align: center;
|
|
}
|
|
.submit-spinner {
|
|
margin-right: 16px;
|
|
margin-inline-end: 16px;
|
|
margin-inline-start: initial;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
|
|
protected firstUpdated(changedProperties) {
|
|
super.firstUpdated(changedProperties);
|
|
this.hass.loadBackendTranslation("mfa_setup", "auth");
|
|
this.addEventListener("keypress", (ev) => {
|
|
if (ev.key === "Enter") {
|
|
this._submitStep();
|
|
}
|
|
});
|
|
}
|
|
|
|
private _stepDataChanged(ev: CustomEvent) {
|
|
this._stepData = ev.detail.value;
|
|
}
|
|
|
|
private _submitStep() {
|
|
this._loading = true;
|
|
this._errorMessage = undefined;
|
|
|
|
const curInstance = this._instance;
|
|
|
|
this.hass
|
|
.callWS({
|
|
type: "auth/setup_mfa",
|
|
flow_id: this._step!.flow_id,
|
|
user_input: this._stepData,
|
|
})
|
|
.then(
|
|
(step) => {
|
|
if (curInstance !== this._instance) {
|
|
return;
|
|
}
|
|
|
|
this._processStep(step);
|
|
this._loading = false;
|
|
},
|
|
(err) => {
|
|
this._errorMessage =
|
|
(err && err.body && err.body.message) || "Unknown error occurred";
|
|
this._loading = false;
|
|
}
|
|
);
|
|
}
|
|
|
|
private _processStep(step) {
|
|
if (!step.errors) step.errors = {};
|
|
this._step = step;
|
|
// We got a new form if there are no errors.
|
|
if (Object.keys(step.errors).length === 0) {
|
|
this._stepData = {};
|
|
}
|
|
}
|
|
|
|
private _flowDone() {
|
|
const flowFinished = Boolean(
|
|
this._step && ["create_entry", "abort"].includes(this._step.type)
|
|
);
|
|
|
|
this._dialogClosedCallback!({
|
|
flowFinished,
|
|
});
|
|
|
|
this._errorMessage = undefined;
|
|
this._step = undefined;
|
|
this._stepData = {};
|
|
this._dialogClosedCallback = undefined;
|
|
this.closeDialog();
|
|
}
|
|
|
|
private _computeStepTitle() {
|
|
return this._step?.type === "abort"
|
|
? this.hass.localize("ui.panel.profile.mfa_setup.title_aborted")
|
|
: this._step?.type === "create_entry"
|
|
? this.hass.localize("ui.panel.profile.mfa_setup.title_success")
|
|
: this._step?.type === "form"
|
|
? this.hass.localize(
|
|
`component.auth.mfa_setup.${this._step.handler}.step.${this._step.step_id}.title`
|
|
)
|
|
: "";
|
|
}
|
|
|
|
private _computeLabel = (schema) =>
|
|
this.hass.localize(
|
|
`component.auth.mfa_setup.${this._step!.handler}.step.${
|
|
(this._step! as DataEntryFlowStepForm).step_id
|
|
}.data.${schema.name}`
|
|
) || schema.name;
|
|
|
|
private _computeError = (error) =>
|
|
this.hass.localize(
|
|
`component.auth.mfa_setup.${this._step!.handler}.error.${error}`
|
|
) || error;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-mfa-module-setup-flow": HaMfaModuleSetupFlow;
|
|
}
|
|
}
|