mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Clean up local auth flow, preselect remember me (#19354)
This commit is contained in:
parent
03751d2581
commit
02a7d0e797
@ -21,7 +21,6 @@ import {
|
|||||||
DataEntryFlowStepForm,
|
DataEntryFlowStepForm,
|
||||||
} from "../data/data_entry_flow";
|
} from "../data/data_entry_flow";
|
||||||
import "./ha-auth-form";
|
import "./ha-auth-form";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
|
|
||||||
type State = "loading" | "error" | "step";
|
type State = "loading" | "error" | "step";
|
||||||
|
|
||||||
@ -39,7 +38,9 @@ export class HaAuthFlow extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||||
|
|
||||||
@property({ type: Boolean }) private storeToken = false;
|
@property({ type: Boolean }) private initStoreToken = false;
|
||||||
|
|
||||||
|
@state() private _storeToken = false;
|
||||||
|
|
||||||
@state() private _state: State = "loading";
|
@state() private _state: State = "loading";
|
||||||
|
|
||||||
@ -56,6 +57,10 @@ export class HaAuthFlow extends LitElement {
|
|||||||
willUpdate(changedProps: PropertyValues) {
|
willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this._storeToken = this.initStoreToken;
|
||||||
|
}
|
||||||
|
|
||||||
if (!changedProps.has("step")) {
|
if (!changedProps.has("step")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -155,11 +160,6 @@ export class HaAuthFlow extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderForm() {
|
private _renderForm() {
|
||||||
const showBack =
|
|
||||||
this.step?.type === "form" &&
|
|
||||||
this.authProvider?.users &&
|
|
||||||
!["select_mfa_module", "mfa"].includes(this.step.step_id);
|
|
||||||
|
|
||||||
switch (this._state) {
|
switch (this._state) {
|
||||||
case "step":
|
case "step":
|
||||||
if (this.step == null) {
|
if (this.step == null) {
|
||||||
@ -168,12 +168,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this._renderStep(this.step)}
|
${this._renderStep(this.step)}
|
||||||
<div class="action ${showBack ? "space-between" : ""}">
|
<div class="action">
|
||||||
${showBack
|
|
||||||
? html`<mwc-button @click=${this._localFlow}>
|
|
||||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
|
||||||
</mwc-button>`
|
|
||||||
: nothing}
|
|
||||||
<mwc-button
|
<mwc-button
|
||||||
raised
|
raised
|
||||||
@click=${this._handleSubmit}
|
@click=${this._handleSubmit}
|
||||||
@ -227,7 +222,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
</h1>
|
</h1>
|
||||||
${this._computeStepDescription(step)}
|
${this._computeStepDescription(step)}
|
||||||
<ha-auth-form
|
<ha-auth-form
|
||||||
.data=${this._stepData}
|
.data=${this._stepData!}
|
||||||
.schema=${autocompleteLoginFields(step.data_schema)}
|
.schema=${autocompleteLoginFields(step.data_schema)}
|
||||||
.error=${step.errors}
|
.error=${step.errors}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
@ -246,7 +241,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.storeToken}
|
.checked=${this._storeToken}
|
||||||
@change=${this._storeTokenChanged}
|
@change=${this._storeTokenChanged}
|
||||||
></ha-checkbox>
|
></ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
@ -269,7 +264,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
|
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
|
||||||
this.storeToken = (e.currentTarget as HTMLInputElement).checked;
|
this._storeToken = (e.currentTarget as HTMLInputElement).checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _providerChanged(newProvider?: AuthProvider) {
|
private async _providerChanged(newProvider?: AuthProvider) {
|
||||||
@ -303,7 +298,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
this.redirectUri!,
|
this.redirectUri!,
|
||||||
data.result,
|
data.result,
|
||||||
this.oauth2State,
|
this.oauth2State,
|
||||||
this.storeToken
|
this._storeToken
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -385,7 +380,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
this.redirectUri!,
|
this.redirectUri!,
|
||||||
newStep.result,
|
newStep.result,
|
||||||
this.oauth2State,
|
this.oauth2State,
|
||||||
this.storeToken
|
this._storeToken
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -400,10 +395,6 @@ export class HaAuthFlow extends LitElement {
|
|||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _localFlow() {
|
|
||||||
fireEvent(this, "default-login-flow", { value: false });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||||
import { registerServiceWorker } from "../util/register-service-worker";
|
import { registerServiceWorker } from "../util/register-service-worker";
|
||||||
import "./ha-auth-flow";
|
import "./ha-auth-flow";
|
||||||
import "./ha-local-auth-flow";
|
|
||||||
|
|
||||||
import("./ha-pick-auth-provider");
|
import("./ha-pick-auth-provider");
|
||||||
|
|
||||||
@ -36,12 +35,12 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _authProviders?: AuthProvider[];
|
@state() private _authProviders?: AuthProvider[];
|
||||||
|
|
||||||
|
@state() private _preselectStoreToken = false;
|
||||||
|
|
||||||
@state() private _ownInstance = false;
|
@state() private _ownInstance = false;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _forceDefaultLogin = false;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||||
@ -84,8 +83,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
ha-auth-flow,
|
ha-auth-flow {
|
||||||
ha-local-auth-flow {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -176,44 +174,29 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
|
||||||
<div
|
<div class="card-content">
|
||||||
class="card-content"
|
|
||||||
@default-login-flow=${this._handleDefaultLoginFlow}
|
|
||||||
>
|
|
||||||
${!this._authProvider
|
${!this._authProvider
|
||||||
? html`<p>
|
? html`<p>
|
||||||
${this.localize("ui.panel.page-authorize.initializing")}
|
${this.localize("ui.panel.page-authorize.initializing")}
|
||||||
</p> `
|
</p> `
|
||||||
: !this._forceDefaultLogin &&
|
: html`<ha-auth-flow
|
||||||
this._authProvider!.users &&
|
|
||||||
this.clientId != null &&
|
|
||||||
this.redirectUri != null
|
|
||||||
? html`<ha-local-auth-flow
|
|
||||||
.clientId=${this.clientId}
|
.clientId=${this.clientId}
|
||||||
.redirectUri=${this.redirectUri}
|
.redirectUri=${this.redirectUri}
|
||||||
.oauth2State=${this.oauth2State}
|
.oauth2State=${this.oauth2State}
|
||||||
.authProvider=${this._authProvider}
|
.authProvider=${this._authProvider}
|
||||||
.authProviders=${this._authProviders}
|
|
||||||
.localize=${this.localize}
|
.localize=${this.localize}
|
||||||
.ownInstance=${this._ownInstance}
|
.initStoreToken=${this._preselectStoreToken}
|
||||||
></ha-local-auth-flow>`
|
></ha-auth-flow>
|
||||||
: html`<ha-auth-flow
|
${inactiveProviders!.length > 0
|
||||||
.clientId=${this.clientId}
|
? html`
|
||||||
.redirectUri=${this.redirectUri}
|
<ha-pick-auth-provider
|
||||||
.oauth2State=${this.oauth2State}
|
.localize=${this.localize}
|
||||||
.authProvider=${this._authProvider}
|
.clientId=${this.clientId}
|
||||||
.localize=${this.localize}
|
.authProviders=${inactiveProviders!}
|
||||||
></ha-auth-flow>
|
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||||
${inactiveProviders!.length > 0
|
></ha-pick-auth-provider>
|
||||||
? html`
|
`
|
||||||
<ha-pick-auth-provider
|
: ""}`}
|
||||||
.localize=${this.localize}
|
|
||||||
.clientId=${this.clientId}
|
|
||||||
.authProviders=${inactiveProviders}
|
|
||||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
|
||||||
></ha-pick-auth-provider>
|
|
||||||
`
|
|
||||||
: ""}`}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<ha-language-picker
|
<ha-language-picker
|
||||||
@ -319,13 +302,14 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authProviders.length === 0) {
|
if (authProviders.providers.length === 0) {
|
||||||
this._error = "No auth providers returned. Unable to finish login.";
|
this._error = "No auth providers returned. Unable to finish login.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._authProviders = authProviders;
|
this._authProviders = authProviders.providers;
|
||||||
this._authProvider = authProviders[0];
|
this._authProvider = authProviders.providers[0];
|
||||||
|
this._preselectStoreToken = authProviders.preselect_remember_me;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = "Unable to fetch auth providers.";
|
this._error = "Unable to fetch auth providers.";
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -333,10 +317,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleDefaultLoginFlow(ev) {
|
|
||||||
this._forceDefaultLogin = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleAuthProviderPick(ev) {
|
private async _handleAuthProviderPick(ev) {
|
||||||
this._authProvider = ev.detail;
|
this._authProvider = ev.detail;
|
||||||
}
|
}
|
||||||
|
@ -1,485 +0,0 @@
|
|||||||
/* eslint-disable lit/prefer-static-styles */
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
|
||||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
|
||||||
import "../components/ha-alert";
|
|
||||||
import "../components/ha-button";
|
|
||||||
import "../components/ha-icon-button";
|
|
||||||
import "../components/user/ha-person-badge";
|
|
||||||
import {
|
|
||||||
AuthProvider,
|
|
||||||
createLoginFlow,
|
|
||||||
deleteLoginFlow,
|
|
||||||
redirectWithAuthCode,
|
|
||||||
submitLoginFlow,
|
|
||||||
} from "../data/auth";
|
|
||||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
|
||||||
import { BasePerson, listUserPersons } from "../data/person";
|
|
||||||
import "./ha-auth-textfield";
|
|
||||||
import type { HaAuthTextField } from "./ha-auth-textfield";
|
|
||||||
|
|
||||||
@customElement("ha-local-auth-flow")
|
|
||||||
export class HaLocalAuthFlow extends LitElement {
|
|
||||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public authProviders?: AuthProvider[];
|
|
||||||
|
|
||||||
@property() public clientId?: string;
|
|
||||||
|
|
||||||
@property() public redirectUri?: string;
|
|
||||||
|
|
||||||
@property() public oauth2State?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public ownInstance = false;
|
|
||||||
|
|
||||||
@property() public localize!: LocalizeFunc;
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
@state() private _step?: DataEntryFlowStep;
|
|
||||||
|
|
||||||
@state() private _submitting = false;
|
|
||||||
|
|
||||||
@state() private _persons?: Record<string, BasePerson>;
|
|
||||||
|
|
||||||
@state() private _selectedUser?: string;
|
|
||||||
|
|
||||||
@state() private _unmaskedPassword = false;
|
|
||||||
|
|
||||||
createRenderRoot() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
willUpdate(changedProps: PropertyValues) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this.authProvider?.users || !this._persons) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
const userIds = Object.keys(this.authProvider.users).filter(
|
|
||||||
(userId) => userId in this._persons!
|
|
||||||
);
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.content {
|
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
.persons {
|
|
||||||
margin-top: 24px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.persons.force-small {
|
|
||||||
max-width: 350px;
|
|
||||||
}
|
|
||||||
.person {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
.person[role="button"] {
|
|
||||||
outline: none;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.person[role="button"]:focus-visible {
|
|
||||||
background: rgba(var(--rgb-primary-color), 0.1);
|
|
||||||
}
|
|
||||||
.person p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
ha-person-badge {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
--person-badge-font-size: 2em;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
ha-auth-textfield {
|
|
||||||
display: block !important;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
ha-auth-textfield ha-icon-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 4px;
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
.login-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 336px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
.login-form .person {
|
|
||||||
cursor: default;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.login-form .person p {
|
|
||||||
font-size: 28px;
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
.login-form ha-person-badge {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
--person-badge-font-size: 3em;
|
|
||||||
}
|
|
||||||
ha-list-item {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
ha-button {
|
|
||||||
--mdc-typography-button-text-transform: none;
|
|
||||||
}
|
|
||||||
.forgot-password-container {
|
|
||||||
text-align: right;
|
|
||||||
padding: 8px 0 16px 0;
|
|
||||||
}
|
|
||||||
a.forgot-password {
|
|
||||||
color: var(--primary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
color: var(--primary-color);
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 8px;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
button:focus-visible {
|
|
||||||
background: rgba(var(--rgb-primary-color), 0.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
${this._error
|
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
|
||||||
: nothing}
|
|
||||||
${this._step
|
|
||||||
? html`<ha-auth-flow
|
|
||||||
.clientId=${this.clientId}
|
|
||||||
.redirectUri=${this.redirectUri}
|
|
||||||
.oauth2State=${this.oauth2State}
|
|
||||||
.step=${this._step}
|
|
||||||
storeToken
|
|
||||||
.localize=${this.localize}
|
|
||||||
></ha-auth-flow>`
|
|
||||||
: this._selectedUser
|
|
||||||
? html`<div class="login-form">
|
|
||||||
<div class="person">
|
|
||||||
<ha-person-badge
|
|
||||||
.person=${this._persons[this._selectedUser]}
|
|
||||||
></ha-person-badge>
|
|
||||||
<p>${this._persons[this._selectedUser].name}</p>
|
|
||||||
</div>
|
|
||||||
<form>
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="username"
|
|
||||||
autocomplete="username"
|
|
||||||
readonly
|
|
||||||
.value=${this.authProvider.users[this._selectedUser]}
|
|
||||||
/>
|
|
||||||
<ha-auth-textfield
|
|
||||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
|
||||||
autocomplete="current-password"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
.label=${this.localize(
|
|
||||||
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
|
|
||||||
)}
|
|
||||||
required
|
|
||||||
autoValidate
|
|
||||||
iconTrailing
|
|
||||||
validationMessage="Required"
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
toggles
|
|
||||||
.label=${this.localize(
|
|
||||||
this._unmaskedPassword
|
|
||||||
? "ui.panel.page-authorize.form.hide_password"
|
|
||||||
: "ui.panel.page-authorize.form.show_password"
|
|
||||||
) ||
|
|
||||||
(this._unmaskedPassword
|
|
||||||
? "Hide password"
|
|
||||||
: "Show password")}
|
|
||||||
@click=${this._toggleUnmaskedPassword}
|
|
||||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
|
||||||
></ha-icon-button>
|
|
||||||
</ha-auth-textfield>
|
|
||||||
<div class="forgot-password-container">
|
|
||||||
<a
|
|
||||||
class="forgot-password"
|
|
||||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>${this.localize(
|
|
||||||
"ui.panel.page-authorize.forgot_password"
|
|
||||||
)}</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="action space-between">
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._restart}
|
|
||||||
.disabled=${this._submitting}
|
|
||||||
>
|
|
||||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
raised
|
|
||||||
@click=${this._handleSubmit}
|
|
||||||
.disabled=${this._submitting}
|
|
||||||
>
|
|
||||||
${this.localize("ui.panel.page-authorize.form.next")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>`
|
|
||||||
: html`<h1>
|
|
||||||
${this.localize("ui.panel.page-authorize.welcome_home")}
|
|
||||||
</h1>
|
|
||||||
${this.localize("ui.panel.page-authorize.who_is_logging_in")}
|
|
||||||
<div
|
|
||||||
class="persons ${userIds.length < 10 && userIds.length % 4 === 1
|
|
||||||
? "force-small"
|
|
||||||
: ""}"
|
|
||||||
>
|
|
||||||
${userIds.map((userId) => {
|
|
||||||
const person = this._persons![userId];
|
|
||||||
|
|
||||||
return html`<div
|
|
||||||
class="person"
|
|
||||||
.userId=${userId}
|
|
||||||
@click=${this._personSelected}
|
|
||||||
@keyup=${this._handleKeyUp}
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<ha-person-badge .person=${person}></ha-person-badge>
|
|
||||||
<p>${person.name}</p>
|
|
||||||
</div>`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div class="action">
|
|
||||||
<button @click=${this._otherLogin} tabindex="0">
|
|
||||||
${this.localize("ui.panel.page-authorize.other_options")}
|
|
||||||
</button>
|
|
||||||
</div>`}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
|
|
||||||
this.addEventListener("keypress", (ev) => {
|
|
||||||
if (ev.key === "Enter") {
|
|
||||||
this._handleSubmit(ev);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (changedProps.has("_selectedUser") && this._selectedUser) {
|
|
||||||
const passwordElement = this.renderRoot.querySelector(
|
|
||||||
"#password"
|
|
||||||
) as HaAuthTextField;
|
|
||||||
passwordElement.updateComplete.then(() => {
|
|
||||||
passwordElement.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _load() {
|
|
||||||
try {
|
|
||||||
this._persons = await listUserPersons();
|
|
||||||
} catch {
|
|
||||||
this._persons = {};
|
|
||||||
this._error = "Failed to fetch persons";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _restart() {
|
|
||||||
this._selectedUser = undefined;
|
|
||||||
this._error = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _toggleUnmaskedPassword() {
|
|
||||||
this._unmaskedPassword = !this._unmaskedPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleKeyUp(ev: KeyboardEvent) {
|
|
||||||
if (ev.key === "Enter" || ev.key === " ") {
|
|
||||||
this._personSelected(ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _personSelected(ev) {
|
|
||||||
const userId = ev.currentTarget.userId;
|
|
||||||
if (
|
|
||||||
this.ownInstance &&
|
|
||||||
this.authProviders?.find((prv) => prv.type === "trusted_networks")
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const flowResponse = await createLoginFlow(
|
|
||||||
this.clientId,
|
|
||||||
this.redirectUri,
|
|
||||||
["trusted_networks", null]
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await flowResponse.json();
|
|
||||||
|
|
||||||
if (data.type === "create_entry") {
|
|
||||||
redirectWithAuthCode(
|
|
||||||
this.redirectUri!,
|
|
||||||
data.result,
|
|
||||||
this.oauth2State,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!data.data_schema[0].options.find((opt) => opt[0] === userId)) {
|
|
||||||
throw new Error("User not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
const postData = { user: userId, client_id: this.clientId };
|
|
||||||
|
|
||||||
const response = await submitLoginFlow(data.flow_id, postData);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.type === "create_entry") {
|
|
||||||
redirectWithAuthCode(
|
|
||||||
this.redirectUri!,
|
|
||||||
result.result,
|
|
||||||
this.oauth2State,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid response");
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
deleteLoginFlow(data.flow_id).catch((err) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error("Error delete obsoleted auth flow", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._selectedUser = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleSubmit(ev: Event) {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
if (!this.authProvider?.users || !this._selectedUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._error = undefined;
|
|
||||||
this._submitting = true;
|
|
||||||
|
|
||||||
const flowResponse = await createLoginFlow(
|
|
||||||
this.clientId,
|
|
||||||
this.redirectUri,
|
|
||||||
["homeassistant", null]
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await flowResponse.json();
|
|
||||||
|
|
||||||
const postData = {
|
|
||||||
username: this.authProvider.users[this._selectedUser],
|
|
||||||
password: (this.renderRoot.querySelector("#password") as HaAuthTextField)
|
|
||||||
.value,
|
|
||||||
client_id: this.clientId,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await submitLoginFlow(data.flow_id, postData);
|
|
||||||
|
|
||||||
const newStep = await response.json();
|
|
||||||
|
|
||||||
if (response.status === 403) {
|
|
||||||
this._error = newStep.message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newStep.type === "create_entry") {
|
|
||||||
redirectWithAuthCode(
|
|
||||||
this.redirectUri!,
|
|
||||||
newStep.result,
|
|
||||||
this.oauth2State,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newStep.errors.base) {
|
|
||||||
this._error = this.localize(
|
|
||||||
`ui.panel.page-authorize.form.providers.homeassistant.error.${newStep.errors.base}`
|
|
||||||
);
|
|
||||||
throw new Error(this._error);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._step = newStep;
|
|
||||||
} catch {
|
|
||||||
deleteLoginFlow(data.flow_id).catch((err) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error("Error delete obsoleted auth flow", err);
|
|
||||||
});
|
|
||||||
if (!this._error) {
|
|
||||||
this._error = this.localize(
|
|
||||||
"ui.panel.page-authorize.form.unknown_error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this._submitting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _otherLogin() {
|
|
||||||
fireEvent(this, "default-login-flow", { value: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-local-auth-flow": HaLocalAuthFlow;
|
|
||||||
}
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"default-login-flow": { value: boolean };
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,16 +24,6 @@ export const fetchPersons = (hass: HomeAssistant) =>
|
|||||||
config: Person[];
|
config: Person[];
|
||||||
}>({ type: "person/list" });
|
}>({ type: "person/list" });
|
||||||
|
|
||||||
export const listUserPersons = (): Promise<Record<string, BasePerson>> =>
|
|
||||||
fetch("/api/person/list", {
|
|
||||||
credentials: "same-origin",
|
|
||||||
}).then((resp) => {
|
|
||||||
if (resp.ok) {
|
|
||||||
return resp.json();
|
|
||||||
}
|
|
||||||
throw new Error(resp.statusText);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createPerson = (
|
export const createPerson = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
values: PersonMutableParams
|
values: PersonMutableParams
|
||||||
|
Loading…
x
Reference in New Issue
Block a user