From 0c74829a2cb37d989620ccb5b0ceddb0e7498544 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Sun, 22 Jul 2018 21:16:41 -0700 Subject: [PATCH 1/3] Add ha-mfa-modules-card and setup flow --- .../profile/ha-mfa-module-setup-flow.js | 271 ++++++++++++++++++ src/panels/profile/ha-mfa-modules-card.js | 127 ++++++++ src/panels/profile/ha-panel-profile.js | 2 + 3 files changed, 400 insertions(+) create mode 100644 src/panels/profile/ha-mfa-module-setup-flow.js create mode 100644 src/panels/profile/ha-mfa-modules-card.js diff --git a/src/panels/profile/ha-mfa-module-setup-flow.js b/src/panels/profile/ha-mfa-module-setup-flow.js new file mode 100644 index 000000000000..da40f9b8de9f --- /dev/null +++ b/src/panels/profile/ha-mfa-module-setup-flow.js @@ -0,0 +1,271 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js'; +import '@polymer/paper-dialog/paper-dialog.js'; +import '@polymer/paper-spinner/paper-spinner.js'; +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../../components/ha-form.js'; +import '../../components/ha-markdown.js'; +import '../../resources/ha-style.js'; + +import EventsMixin from '../../mixins/events-mixin.js'; +import LocalizeMixin from '../../mixins/localize-mixin.js'; + +let instance = 0; + +/* + * @appliesMixin LocalizeMixin + * @appliesMixin EventsMixin + */ +class HaMfaModuleSetupFlow extends + LocalizeMixin(EventsMixin(PolymerElement)) { + static get template() { + return html` + + +

+ + + +

+ + + + + +
+ + + +
+
+`; + } + + 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.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); diff --git a/src/panels/profile/ha-mfa-modules-card.js b/src/panels/profile/ha-mfa-modules-card.js new file mode 100644 index 000000000000..7241a063ef36 --- /dev/null +++ b/src/panels/profile/ha-mfa-modules-card.js @@ -0,0 +1,127 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-card/paper-card.js'; +import '@polymer/paper-item/paper-item-body.js'; +import '@polymer/paper-item/paper-item.js'; +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../../resources/ha-style.js'; + +import EventsMixin from '../../mixins/events-mixin.js'; +import LocalizeMixin from '../../mixins/localize-mixin.js'; + +let registeredDialog = false; + +/* + * @appliesMixin EventsMixin + * @appliesMixin LocalizeMixin + */ +class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) { + static get template() { + return html` + + + + +`; + } + + 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.js'), + }); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubEvents) this._unsubEvents(); + } + + _enable(ev) { + this.fire('show-mfa-module-setup-flow', { + hass: this.hass, + mfaModuleId: ev.model.module.id, + dialogClosedCallback: () => this._refreshCurrentUser(), + }); + } + + _disable(ev) { + if (!confirm(`Are you sure you want to disable ${ev.model.module.name}?`)) return; + + const mfaModuleId = ev.model.module.id; + + this.hass.callWS({ + type: 'auth/depose_mfa', + mfa_module_id: mfaModuleId, + }).then(() => { + this._refreshCurrentUser(); + }); + } + + async _refreshCurrentUser() { + const user = await this.hass.callWS({ + type: 'auth/current_user', + }); + this.mfaModules = user.mfa_modules; + } +} + +customElements.define('ha-mfa-modules-card', HaMfaModulesCard); diff --git a/src/panels/profile/ha-panel-profile.js b/src/panels/profile/ha-panel-profile.js index fc37444193c9..e4e6d8442db0 100644 --- a/src/panels/profile/ha-panel-profile.js +++ b/src/panels/profile/ha-panel-profile.js @@ -14,6 +14,7 @@ import '../../resources/ha-style.js'; import EventsMixin from '../../mixins/events-mixin.js'; import './ha-change-password-card.js'; +import './ha-mfa-modules-card.js'; import './ha-pick-language-row.js'; import './ha-pick-theme-row.js'; import './ha-push-notifications-row.js'; @@ -83,6 +84,7 @@ class HaPanelProfile extends EventsMixin(PolymerElement) { + `; From 789e9847f809ab4fc6a9d23da07d8a7c012cc600 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Thu, 23 Aug 2018 22:58:05 -0700 Subject: [PATCH 2/3] Add hass-refresh-current-user event --- src/layouts/app/auth-mixin.js | 5 +++++ src/panels/profile/ha-mfa-modules-card.js | 15 ++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/layouts/app/auth-mixin.js b/src/layouts/app/auth-mixin.js index a4de9e7f8c62..430ccc6346f1 100644 --- a/src/layouts/app/auth-mixin.js +++ b/src/layouts/app/auth-mixin.js @@ -6,6 +6,7 @@ export default superClass => class extends superClass { ready() { super.ready(); this.addEventListener('hass-logout', () => this._handleLogout()); + this.addEventListener('hass-refresh-current-user', () => this._getCurrentUser()); afterNextRender(null, () => { if (askWrite()) { @@ -20,6 +21,10 @@ export default superClass => class extends superClass { hassConnected() { super.hassConnected(); + this._getCurrentUser(); + } + + _getCurrentUser() { // only for new auth if (this.hass.connection.options.accessToken) { this.hass.callWS({ diff --git a/src/panels/profile/ha-mfa-modules-card.js b/src/panels/profile/ha-mfa-modules-card.js index 7241a063ef36..30770e97a148 100644 --- a/src/panels/profile/ha-mfa-modules-card.js +++ b/src/panels/profile/ha-mfa-modules-card.js @@ -90,11 +90,6 @@ class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) { } } - disconnectedCallback() { - super.disconnectedCallback(); - if (this._unsubEvents) this._unsubEvents(); - } - _enable(ev) { this.fire('show-mfa-module-setup-flow', { hass: this.hass, @@ -117,10 +112,12 @@ class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) { } async _refreshCurrentUser() { - const user = await this.hass.callWS({ - type: 'auth/current_user', - }); - this.mfaModules = user.mfa_modules; + this.fire('hass-refresh-current-user'); + // const user = await this.hass.callWS({ + // type: 'auth/current_user', + // }); + // await this._updateHass({ user }); + // this.mfaModules = user.mfa_modules; } } From b2c7e0990c73b7df258155a8a9d00f9d6c199eb4 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Fri, 24 Aug 2018 07:27:03 -0700 Subject: [PATCH 3/3] Address code review comment --- src/panels/profile/ha-mfa-modules-card.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/panels/profile/ha-mfa-modules-card.js b/src/panels/profile/ha-mfa-modules-card.js index 30770e97a148..84ce9c204395 100644 --- a/src/panels/profile/ha-mfa-modules-card.js +++ b/src/panels/profile/ha-mfa-modules-card.js @@ -111,13 +111,8 @@ class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) { }); } - async _refreshCurrentUser() { + _refreshCurrentUser() { this.fire('hass-refresh-current-user'); - // const user = await this.hass.callWS({ - // type: 'auth/current_user', - // }); - // await this._updateHass({ user }); - // this.mfaModules = user.mfa_modules; } }