mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Migrate mfa to Lit (#8276)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
e33aff7cf3
commit
627424b8b9
281
src/panels/profile/dialog-ha-mfa-module-setup-flow.ts
Normal file
281
src/panels/profile/dialog-ha-mfa-module-setup-flow.ts
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import { html, TemplateResult } from "lit-html";
|
||||||
|
import { localizeKey } from "../../common/translations/localize";
|
||||||
|
import "../../components/ha-circular-progress";
|
||||||
|
import "../../components/ha-form/ha-form";
|
||||||
|
import "../../components/ha-markdown";
|
||||||
|
import {
|
||||||
|
DataEntryFlowStep,
|
||||||
|
DataEntryFlowStepForm,
|
||||||
|
} from "../../data/data_entry_flow";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../../components/ha-dialog";
|
||||||
|
|
||||||
|
let instance = 0;
|
||||||
|
|
||||||
|
@customElement("ha-mfa-module-setup-flow")
|
||||||
|
class HaMfaModuleSetupFlow extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@internalProperty() private _dialogClosedCallback?: (params: {
|
||||||
|
flowFinished: boolean;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
@internalProperty() private _instance?: number;
|
||||||
|
|
||||||
|
@internalProperty() private _loading = false;
|
||||||
|
|
||||||
|
@internalProperty() private _opened = false;
|
||||||
|
|
||||||
|
@internalProperty() private _stepData: any = {};
|
||||||
|
|
||||||
|
@internalProperty() private _step?: DataEntryFlowStep;
|
||||||
|
|
||||||
|
@internalProperty() 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(): TemplateResult {
|
||||||
|
if (!this._opened) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
.heading=${this._computeStepTitle()}
|
||||||
|
@closing=${this.closeDialog}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
${this._errorMessage
|
||||||
|
? html`<div class="error">${this._errorMessage}</div>`
|
||||||
|
: ""}
|
||||||
|
${!this._step
|
||||||
|
? html`<div class="init-spinner">
|
||||||
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
|
</div>`
|
||||||
|
: html`${this._step.type === "abort"
|
||||||
|
? html` <ha-markdown
|
||||||
|
allowsvg
|
||||||
|
breaks
|
||||||
|
.content=${this.hass.localize(
|
||||||
|
`component.auth.mfa_setup.${this._step.handler}.abort.${this._step.reason}`
|
||||||
|
)}
|
||||||
|
></ha-markdown>`
|
||||||
|
: this._step.type === "create_entry"
|
||||||
|
? html`<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.profile.mfa_setup.step_done",
|
||||||
|
"step",
|
||||||
|
this._step.title
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
: this._step.type === "form"
|
||||||
|
? html` <ha-markdown
|
||||||
|
allowsvg
|
||||||
|
breaks
|
||||||
|
.content=${localizeKey(
|
||||||
|
this.hass.localize,
|
||||||
|
`component.auth.mfa_setup.${this._step!.handler}.step.${
|
||||||
|
(this._step! as DataEntryFlowStepForm).step_id
|
||||||
|
}.description`,
|
||||||
|
this._step!.description_placeholders
|
||||||
|
)}
|
||||||
|
></ha-markdown>
|
||||||
|
<ha-form
|
||||||
|
.data=${this._stepData}
|
||||||
|
.schema=${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(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
ha-dialog {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
ha-markdown {
|
||||||
|
--markdown-svg-background-color: white;
|
||||||
|
--markdown-svg-color: black;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
ha-markdown a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.init-spinner {
|
||||||
|
padding: 10px 100px 34px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.submit-spinner {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,322 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../components/dialog/ha-paper-dialog";
|
|
||||||
import "../../components/ha-circular-progress";
|
|
||||||
import "../../components/ha-form/ha-form";
|
|
||||||
import "../../components/ha-markdown";
|
|
||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
import "../../styles/polymer-ha-style-dialog";
|
|
||||||
|
|
||||||
let instance = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaMfaModuleSetupFlow extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style-dialog">
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
ha-markdown {
|
|
||||||
--markdown-svg-background-color: white;
|
|
||||||
--markdown-svg-color: black;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
ha-markdown a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.init-spinner {
|
|
||||||
padding: 10px 100px 34px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.submit-spinner {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-paper-dialog
|
|
||||||
id="dialog"
|
|
||||||
with-backdrop=""
|
|
||||||
opened="{{_opened}}"
|
|
||||||
on-opened-changed="_openedChanged"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
|
||||||
[[localize('ui.panel.profile.mfa_setup.title_aborted')]]
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
|
||||||
[[localize('ui.panel.profile.mfa_setup.title_success')]]
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
|
||||||
[[_computeStepTitle(localize, _step)]]
|
|
||||||
</template>
|
|
||||||
</h2>
|
|
||||||
<paper-dialog-scrollable>
|
|
||||||
<template is="dom-if" if="[[_errorMsg]]">
|
|
||||||
<div class="error">[[_errorMsg]]</div>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[!_step]]">
|
|
||||||
<div class="init-spinner">
|
|
||||||
<ha-circular-progress active></ha-circular-progress>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_step]]">
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
|
||||||
<ha-markdown
|
|
||||||
allowsvg
|
|
||||||
breaks
|
|
||||||
content="[[_computeStepAbortedReason(localize, _step)]]"
|
|
||||||
></ha-markdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.profile.mfa_setup.step_done', 'step',
|
|
||||||
_step.title)]]
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
|
||||||
<template
|
|
||||||
is="dom-if"
|
|
||||||
if="[[_computeStepDescription(localize, _step)]]"
|
|
||||||
>
|
|
||||||
<ha-markdown
|
|
||||||
allowsvg
|
|
||||||
breaks
|
|
||||||
content="[[_computeStepDescription(localize, _step)]]"
|
|
||||||
></ha-markdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ha-form
|
|
||||||
data="{{_stepData}}"
|
|
||||||
schema="[[_step.data_schema]]"
|
|
||||||
error="[[_step.errors]]"
|
|
||||||
compute-label="[[_computeLabelCallback(localize, _step)]]"
|
|
||||||
compute-error="[[_computeErrorCallback(localize, _step)]]"
|
|
||||||
></ha-form>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</paper-dialog-scrollable>
|
|
||||||
<div class="buttons">
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
|
|
||||||
<mwc-button on-click="_flowDone"
|
|
||||||
>[[localize('ui.panel.profile.mfa_setup.close')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
|
|
||||||
<mwc-button on-click="_flowDone"
|
|
||||||
>[[localize('ui.panel.profile.mfa_setup.close')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
|
|
||||||
<template is="dom-if" if="[[_loading]]">
|
|
||||||
<div class="submit-spinner">
|
|
||||||
<ha-circular-progress active></ha-circular-progress>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[!_loading]]">
|
|
||||||
<mwc-button on-click="_submitStep"
|
|
||||||
>[[localize('ui.panel.profile.mfa_setup.submit')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</ha-paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
_hass: Object,
|
|
||||||
_dialogClosedCallback: Function,
|
|
||||||
_instance: Number,
|
|
||||||
|
|
||||||
_loading: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Error message when can't talk to server etc
|
|
||||||
_errorMsg: String,
|
|
||||||
|
|
||||||
_opened: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
_step: {
|
|
||||||
type: Object,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Store user entered data.
|
|
||||||
*/
|
|
||||||
_stepData: Object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this.hass.loadBackendTranslation("mfa_setup", "auth");
|
|
||||||
this.addEventListener("keypress", (ev) => {
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._submitStep();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showDialog({ hass, continueFlowId, mfaModuleId, dialogClosedCallback }) {
|
|
||||||
this.hass = hass;
|
|
||||||
this._instance = instance++;
|
|
||||||
this._dialogClosedCallback = dialogClosedCallback;
|
|
||||||
this._createdFromHandler = !!mfaModuleId;
|
|
||||||
this._loading = true;
|
|
||||||
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);
|
|
||||||
this._loading = false;
|
|
||||||
// When the flow changes, center the dialog.
|
|
||||||
// Don't do it on each step or else the dialog keeps bouncing.
|
|
||||||
setTimeout(() => this.$.dialog.center(), 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_submitStep() {
|
|
||||||
this._loading = true;
|
|
||||||
this._errorMsg = null;
|
|
||||||
|
|
||||||
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._errorMsg =
|
|
||||||
(err && err.body && err.body.message) || "Unknown error occurred";
|
|
||||||
this._loading = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_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 = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_flowDone() {
|
|
||||||
this._opened = false;
|
|
||||||
const flowFinished =
|
|
||||||
this._step && ["create_entry", "abort"].includes(this._step.type);
|
|
||||||
|
|
||||||
if (this._step && !flowFinished && this._createdFromHandler) {
|
|
||||||
// console.log('flow not finish');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._dialogClosedCallback({
|
|
||||||
flowFinished,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._errorMsg = null;
|
|
||||||
this._step = null;
|
|
||||||
this._stepData = {};
|
|
||||||
this._dialogClosedCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_equals(a, b) {
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
|
|
||||||
_openedChanged(ev) {
|
|
||||||
// Closed dialog by clicking on the overlay
|
|
||||||
if (this._step && !ev.detail.value) {
|
|
||||||
this._flowDone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStepAbortedReason(localize, step) {
|
|
||||||
return localize(
|
|
||||||
`component.auth.mfa_setup.${step.handler}.abort.${step.reason}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStepTitle(localize, step) {
|
|
||||||
return (
|
|
||||||
localize(
|
|
||||||
`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.title`
|
|
||||||
) || "Setup Multi-factor Authentication"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStepDescription(localize, step) {
|
|
||||||
const args = [
|
|
||||||
`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.description`,
|
|
||||||
];
|
|
||||||
const placeholders = step.description_placeholders || {};
|
|
||||||
Object.keys(placeholders).forEach((key) => {
|
|
||||||
args.push(key);
|
|
||||||
args.push(placeholders[key]);
|
|
||||||
});
|
|
||||||
return localize(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeLabelCallback(localize, step) {
|
|
||||||
// Returns a callback for ha-form to calculate labels per schema object
|
|
||||||
return (schema) =>
|
|
||||||
localize(
|
|
||||||
`component.auth.mfa_setup.${step.handler}.step.${step.step_id}.data.${schema.name}`
|
|
||||||
) || schema.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeErrorCallback(localize, step) {
|
|
||||||
// Returns a callback for ha-form to calculate error messages
|
|
||||||
return (error) =>
|
|
||||||
localize(`component.auth.mfa_setup.${step.handler}.error.${error}`) ||
|
|
||||||
error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-mfa-module-setup-flow", HaMfaModuleSetupFlow);
|
|
@ -1,130 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../components/ha-card";
|
|
||||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
|
||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
import "../../styles/polymer-ha-style";
|
|
||||||
|
|
||||||
let registeredDialog = false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.error,
|
|
||||||
.status {
|
|
||||||
position: absolute;
|
|
||||||
top: -4px;
|
|
||||||
}
|
|
||||||
mwc-button {
|
|
||||||
margin-right: -0.57em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-card header="[[localize('ui.panel.profile.mfa.header')]]">
|
|
||||||
<template is="dom-repeat" items="[[mfaModules]]" as="module">
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
<div>[[module.name]]</div>
|
|
||||||
<div secondary="">[[module.id]]</div>
|
|
||||||
</paper-item-body>
|
|
||||||
<template is="dom-if" if="[[module.enabled]]">
|
|
||||||
<mwc-button on-click="_disable"
|
|
||||||
>[[localize('ui.panel.profile.mfa.disable')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[!module.enabled]]">
|
|
||||||
<mwc-button on-click="_enable"
|
|
||||||
>[[localize('ui.panel.profile.mfa.enable')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</paper-item>
|
|
||||||
</template>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
|
|
||||||
_loading: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Error message when can't talk to server etc
|
|
||||||
_statusMsg: String,
|
|
||||||
_errorMsg: String,
|
|
||||||
|
|
||||||
mfaModules: Array,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
if (!registeredDialog) {
|
|
||||||
registeredDialog = true;
|
|
||||||
this.fire("register-dialog", {
|
|
||||||
dialogShowEvent: "show-mfa-module-setup-flow",
|
|
||||||
dialogTag: "ha-mfa-module-setup-flow",
|
|
||||||
dialogImport: () => import("./ha-mfa-module-setup-flow"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_enable(ev) {
|
|
||||||
this.fire("show-mfa-module-setup-flow", {
|
|
||||||
hass: this.hass,
|
|
||||||
mfaModuleId: ev.model.module.id,
|
|
||||||
dialogClosedCallback: () => this._refreshCurrentUser(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _disable(ev) {
|
|
||||||
const mfamodule = ev.model.module;
|
|
||||||
if (
|
|
||||||
!(await showConfirmationDialog(this, {
|
|
||||||
text: this.localize(
|
|
||||||
"ui.panel.profile.mfa.confirm_disable",
|
|
||||||
"name",
|
|
||||||
mfamodule.name
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mfaModuleId = mfamodule.id;
|
|
||||||
|
|
||||||
this.hass
|
|
||||||
.callWS({
|
|
||||||
type: "auth/depose_mfa",
|
|
||||||
mfa_module_id: mfaModuleId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this._refreshCurrentUser();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_refreshCurrentUser() {
|
|
||||||
this.fire("hass-refresh-current-user");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-mfa-modules-card", HaMfaModulesCard);
|
|
101
src/panels/profile/ha-mfa-modules-card.ts
Normal file
101
src/panels/profile/ha-mfa-modules-card.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-card";
|
||||||
|
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
import { HomeAssistant, MFAModule } from "../../types";
|
||||||
|
import { showMfaModuleSetupFlowDialog } from "./show-ha-mfa-module-setup-flow-dialog";
|
||||||
|
|
||||||
|
@customElement("ha-mfa-modules-card")
|
||||||
|
class HaMfaModulesCard extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public mfaModules!: MFAModule[];
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card .header=${this.hass.localize("ui.panel.profile.mfa.header")}>
|
||||||
|
${this.mfaModules.map(
|
||||||
|
(module) => html`<paper-item>
|
||||||
|
<paper-item-body two-line="">
|
||||||
|
<div>${module.name}</div>
|
||||||
|
<div secondary>${module.id}</div>
|
||||||
|
</paper-item-body>
|
||||||
|
${module.enabled
|
||||||
|
? html`<mwc-button .module=${module} @click=${this._disable}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.profile.mfa.disable"
|
||||||
|
)}</mwc-button
|
||||||
|
>`
|
||||||
|
: html`<mwc-button .module=${module} @click=${this._enable}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.profile.mfa.enable"
|
||||||
|
)}</mwc-button
|
||||||
|
>`}
|
||||||
|
</paper-item>`
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
mwc-button {
|
||||||
|
margin-right: -0.57em;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _enable(ev) {
|
||||||
|
showMfaModuleSetupFlowDialog(this, {
|
||||||
|
mfaModuleId: ev.currentTarget.module.id,
|
||||||
|
dialogClosedCallback: () => this._refreshCurrentUser(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _disable(ev) {
|
||||||
|
const mfamodule = ev.currentTarget.module;
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.profile.mfa.confirm_disable",
|
||||||
|
"name",
|
||||||
|
mfamodule.name
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mfaModuleId = mfamodule.id;
|
||||||
|
|
||||||
|
this.hass
|
||||||
|
.callWS({
|
||||||
|
type: "auth/depose_mfa",
|
||||||
|
mfa_module_id: mfaModuleId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this._refreshCurrentUser();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _refreshCurrentUser() {
|
||||||
|
fireEvent(this, "hass-refresh-current-user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-mfa-modules-card": HaMfaModulesCard;
|
||||||
|
}
|
||||||
|
}
|
21
src/panels/profile/show-ha-mfa-module-setup-flow-dialog.ts
Normal file
21
src/panels/profile/show-ha-mfa-module-setup-flow-dialog.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface MfaModuleSetupFlowDialogParams {
|
||||||
|
continueFlowId?: string;
|
||||||
|
mfaModuleId?: string;
|
||||||
|
dialogClosedCallback: (params: { flowFinished: boolean }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadMfaModuleSetupFlowDialog = () =>
|
||||||
|
import("./dialog-ha-mfa-module-setup-flow");
|
||||||
|
|
||||||
|
export const showMfaModuleSetupFlowDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: MfaModuleSetupFlowDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "ha-mfa-module-setup-flow",
|
||||||
|
dialogImport: loadMfaModuleSetupFlowDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user