Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/layouts/app/auth-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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({
Expand Down
271 changes: 271 additions & 0 deletions src/panels/profile/ha-mfa-module-setup-flow.js
Original file line number Diff line number Diff line change
@@ -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`
<style include="ha-style-dialog">
.error {
color: red;
}
paper-dialog {
max-width: 500px;
}
ha-markdown img:first-child:last-child {
display: block;
margin: 0 auto;
}
.init-spinner {
padding: 10px 100px 34px;
text-align: center;
}
.submit-spinner {
margin-right: 16px;
}
</style>
<paper-dialog id="dialog" with-backdrop="" opened="{{_opened}}" on-opened-changed="_openedChanged">
<h2>
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
Aborted
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
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'><paper-spinner active></paper-spinner></div>
</template>
<template is="dom-if" if="[[_step]]">
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
</template>

<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
<p>Setup done for [[_step.title]]</p>
</template>

<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
<ha-markdown 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')]]">
<paper-button on-click="_flowDone">Close</paper-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
<paper-button on-click="_flowDone">Close</paper-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_loading]]">
<div class='submit-spinner'><paper-spinner active></paper-spinner></div>
</template>
<template is="dom-if" if="[[!_loading]]">
<paper-button on-click="_submitStep">Submit</paper-button>
</template>
</template>
</div>
</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.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);
Loading