mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add local login flow (#18717)
This commit is contained in:
parent
eaf7e29b8a
commit
58e0179321
@ -29,18 +29,18 @@ export class HaAuthFlow extends LitElement {
|
|||||||
|
|
||||||
@property() public localize!: LocalizeFunc;
|
@property() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) private storeToken = false;
|
||||||
|
|
||||||
@state() private _state: State = "loading";
|
@state() private _state: State = "loading";
|
||||||
|
|
||||||
@state() private _stepData?: Record<string, any>;
|
@state() private _stepData?: Record<string, any>;
|
||||||
|
|
||||||
@state() private _step?: DataEntryFlowStep;
|
|
||||||
|
|
||||||
@state() private _errorMessage?: string;
|
@state() private _errorMessage?: string;
|
||||||
|
|
||||||
@state() private _submitting = false;
|
@state() private _submitting = false;
|
||||||
|
|
||||||
@state() private _storeToken = false;
|
|
||||||
|
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -48,27 +48,29 @@ export class HaAuthFlow extends LitElement {
|
|||||||
willUpdate(changedProps: PropertyValues) {
|
willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
if (!changedProps.has("_step")) {
|
if (!changedProps.has("step")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._step) {
|
if (!this.step) {
|
||||||
this._stepData = undefined;
|
this._stepData = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldStep = changedProps.get("_step") as HaAuthFlow["_step"];
|
this._state = "step";
|
||||||
|
|
||||||
|
const oldStep = changedProps.get("step") as HaAuthFlow["step"];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!oldStep ||
|
!oldStep ||
|
||||||
this._step.flow_id !== oldStep.flow_id ||
|
this.step.flow_id !== oldStep.flow_id ||
|
||||||
(this._step.type === "form" &&
|
(this.step.type === "form" &&
|
||||||
oldStep.type === "form" &&
|
oldStep.type === "form" &&
|
||||||
this._step.step_id !== oldStep.step_id)
|
this.step.step_id !== oldStep.step_id)
|
||||||
) {
|
) {
|
||||||
this._stepData =
|
this._stepData =
|
||||||
this._step.type === "form"
|
this.step.type === "form"
|
||||||
? computeInitialHaFormData(this._step.data_schema)
|
? computeInitialHaFormData(this.step.data_schema)
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +119,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
this._providerChanged(this.authProvider);
|
this._providerChanged(this.authProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!changedProps.has("_step") || this._step?.type !== "form") {
|
if (!changedProps.has("step") || this.step?.type !== "form") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,18 +135,18 @@ export class HaAuthFlow extends LitElement {
|
|||||||
private _renderForm() {
|
private _renderForm() {
|
||||||
switch (this._state) {
|
switch (this._state) {
|
||||||
case "step":
|
case "step":
|
||||||
if (this._step == null) {
|
if (this.step == null) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
${this._renderStep(this._step)}
|
${this._renderStep(this.step)}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
raised
|
raised
|
||||||
@click=${this._handleSubmit}
|
@click=${this._handleSubmit}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
>
|
>
|
||||||
${this._step.type === "form"
|
${this.step.type === "form"
|
||||||
? this.localize("ui.panel.page-authorize.form.next")
|
? this.localize("ui.panel.page-authorize.form.next")
|
||||||
: this.localize("ui.panel.page-authorize.form.start_over")}
|
: this.localize("ui.panel.page-authorize.form.start_over")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
@ -205,7 +207,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
.label=${this.localize("ui.panel.page-authorize.store_token")}
|
.label=${this.localize("ui.panel.page-authorize.store_token")}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this._storeToken}
|
.checked=${this.storeToken}
|
||||||
@change=${this._storeTokenChanged}
|
@change=${this._storeTokenChanged}
|
||||||
></ha-checkbox>
|
></ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
@ -218,12 +220,12 @@ 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) {
|
||||||
if (this._step && this._step.type === "form") {
|
if (this.step && this.step.type === "form") {
|
||||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
fetch(`/auth/login_flow/${this.step.flow_id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -260,7 +262,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._step = data;
|
this.step = data;
|
||||||
this._state = "step";
|
this._state = "step";
|
||||||
} else {
|
} else {
|
||||||
this._state = "error";
|
this._state = "error";
|
||||||
@ -288,7 +290,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
if (this.oauth2State) {
|
if (this.oauth2State) {
|
||||||
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
||||||
}
|
}
|
||||||
if (this._storeToken) {
|
if (this.storeToken) {
|
||||||
url += `&storeToken=true`;
|
url += `&storeToken=true`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,10 +333,10 @@ export class HaAuthFlow extends LitElement {
|
|||||||
|
|
||||||
private async _handleSubmit(ev: Event) {
|
private async _handleSubmit(ev: Event) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (this._step == null) {
|
if (this.step == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._step.type !== "form") {
|
if (this.step.type !== "form") {
|
||||||
this._providerChanged(this.authProvider);
|
this._providerChanged(this.authProvider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -343,7 +345,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
const postData = { ...this._stepData, client_id: this.clientId };
|
const postData = { ...this._stepData, client_id: this.clientId };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
const response = await fetch(`/auth/login_flow/${this.step.flow_id}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
body: JSON.stringify(postData),
|
body: JSON.stringify(postData),
|
||||||
@ -361,7 +363,7 @@ export class HaAuthFlow extends LitElement {
|
|||||||
this._redirect(newStep.result);
|
this._redirect(newStep.result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._step = newStep;
|
this.step = newStep;
|
||||||
this._state = "step";
|
this._state = "step";
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -13,6 +13,7 @@ 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");
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
|
|
||||||
@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;
|
||||||
@ -121,32 +124,43 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
})}
|
})}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: html`<p>${this.localize("ui.panel.page-authorize.authorizing")}</p>`}
|
: html`<p>${this.localize("ui.panel.page-authorize.authorizing")}</p>`}
|
||||||
${inactiveProviders.length > 0
|
${!this._forceDefaultLogin &&
|
||||||
? html`<p>
|
this._authProvider!.users &&
|
||||||
${this.localize("ui.panel.page-authorize.logging_in_with", {
|
this.clientId != null &&
|
||||||
authProviderName: html`<b>${this._authProvider!.name}</b>`,
|
this.redirectUri != null
|
||||||
})}
|
? html`<ha-local-auth-flow
|
||||||
</p>`
|
.clientId=${this.clientId}
|
||||||
: nothing}
|
.redirectUri=${this.redirectUri}
|
||||||
|
.oauth2State=${this.oauth2State}
|
||||||
<ha-auth-flow
|
.authProvider=${this._authProvider}
|
||||||
.clientId=${this.clientId}
|
.authProviders=${this._authProviders}
|
||||||
.redirectUri=${this.redirectUri}
|
.localize=${this.localize}
|
||||||
.oauth2State=${this.oauth2State}
|
@default-login-flow=${this._handleDefaultLoginFlow}
|
||||||
.authProvider=${this._authProvider}
|
></ha-local-auth-flow>`
|
||||||
.localize=${this.localize}
|
: html`${inactiveProviders.length > 0
|
||||||
></ha-auth-flow>
|
? html`<p>
|
||||||
|
${this.localize("ui.panel.page-authorize.logging_in_with", {
|
||||||
${inactiveProviders.length > 0
|
authProviderName: html`<b>${this._authProvider!.name}</b>`,
|
||||||
? html`
|
})}
|
||||||
<ha-pick-auth-provider
|
</p>`
|
||||||
.localize=${this.localize}
|
: nothing}
|
||||||
|
<ha-auth-flow
|
||||||
.clientId=${this.clientId}
|
.clientId=${this.clientId}
|
||||||
.authProviders=${inactiveProviders}
|
.redirectUri=${this.redirectUri}
|
||||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
.oauth2State=${this.oauth2State}
|
||||||
></ha-pick-auth-provider>
|
.authProvider=${this._authProvider}
|
||||||
`
|
.localize=${this.localize}
|
||||||
: ""}
|
></ha-auth-flow>
|
||||||
|
${inactiveProviders.length > 0
|
||||||
|
? html`
|
||||||
|
<ha-pick-auth-provider
|
||||||
|
.localize=${this.localize}
|
||||||
|
.clientId=${this.clientId}
|
||||||
|
.authProviders=${inactiveProviders}
|
||||||
|
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||||
|
></ha-pick-auth-provider>
|
||||||
|
`
|
||||||
|
: ""}`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +259,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleDefaultLoginFlow() {
|
||||||
|
this._forceDefaultLogin = true;
|
||||||
|
}
|
||||||
|
|
||||||
private async _handleAuthProviderPick(ev) {
|
private async _handleAuthProviderPick(ev) {
|
||||||
this._authProvider = ev.detail;
|
this._authProvider = ev.detail;
|
||||||
}
|
}
|
||||||
|
334
src/auth/ha-local-auth-flow.ts
Normal file
334
src/auth/ha-local-auth-flow.ts
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/* eslint-disable lit/prefer-static-styles */
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
|
import "../components/ha-alert";
|
||||||
|
import "../components/user/ha-person-badge";
|
||||||
|
import { AuthProvider } from "../data/auth";
|
||||||
|
import { listPersons } from "../data/person";
|
||||||
|
import "./ha-auth-textfield";
|
||||||
|
import type { HaAuthTextField } from "./ha-auth-textfield";
|
||||||
|
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
|
@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() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@state() private _step?: DataEntryFlowStep;
|
||||||
|
|
||||||
|
@state() private _submitting = false;
|
||||||
|
|
||||||
|
@state() private _persons?: Promise<Record<string, string>>;
|
||||||
|
|
||||||
|
@state() private _selectedUser?: string;
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate(changedProps: PropertyValues) {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this._load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.authProvider?.users || !this._persons) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
.persons {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.person {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.person p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
ha-person-badge {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
--person-badge-font-size: 3em;
|
||||||
|
}
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login-form .person {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.login-form ha-auth-textfield {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
margin: 24px 0 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
ha-list-item {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
${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"
|
||||||
|
.value=${this.authProvider.users[this._selectedUser]}
|
||||||
|
/>
|
||||||
|
<ha-auth-textfield
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
></ha-auth-textfield>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<mwc-button
|
||||||
|
raised
|
||||||
|
@click=${this._handleSubmit}
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.localize("ui.panel.page-authorize.form.next")}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</form>`
|
||||||
|
: html`<div class="persons">
|
||||||
|
${Object.keys(this.authProvider.users).map((userId) => {
|
||||||
|
const person = this._persons![userId];
|
||||||
|
return html`<div
|
||||||
|
class="person"
|
||||||
|
.userId=${userId}
|
||||||
|
@click=${this._personSelected}
|
||||||
|
>
|
||||||
|
<ha-person-badge .person=${person}></ha-person-badge>
|
||||||
|
<p>${person.name}</p>
|
||||||
|
</div>`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<ha-list-item hasMeta role="button" @click=${this._otherLogin}>
|
||||||
|
Other options
|
||||||
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
|
</ha-list-item>`}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
this._persons = await (await listPersons()).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _personSelected(ev) {
|
||||||
|
const userId = ev.currentTarget.userId;
|
||||||
|
if (this.authProviders?.find((prv) => prv.type === "trusted_networks")) {
|
||||||
|
try {
|
||||||
|
const flowResponse = await fetch("/auth/login_flow", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: JSON.stringify({
|
||||||
|
client_id: this.clientId,
|
||||||
|
handler: ["trusted_networks", null],
|
||||||
|
redirect_uri: this.redirectUri,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await flowResponse.json();
|
||||||
|
|
||||||
|
if (data.type === "create_entry") {
|
||||||
|
this._redirect(data.result);
|
||||||
|
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 fetch(`/auth/login_flow/${data.flow_id}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: JSON.stringify(postData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.type === "create_entry") {
|
||||||
|
this._redirect(result.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid response");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
fetch(`/auth/login_flow/${data.flow_id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
credentials: "same-origin",
|
||||||
|
}).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._submitting = true;
|
||||||
|
|
||||||
|
const flowResponse = await fetch("/auth/login_flow", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: JSON.stringify({
|
||||||
|
client_id: this.clientId,
|
||||||
|
handler: ["homeassistant", null],
|
||||||
|
redirect_uri: this.redirectUri,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 fetch(`/auth/login_flow/${data.flow_id}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "same-origin",
|
||||||
|
body: JSON.stringify(postData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const newStep = await response.json();
|
||||||
|
|
||||||
|
if (response.status === 403) {
|
||||||
|
this._error = newStep.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStep.type === "create_entry") {
|
||||||
|
this._redirect(newStep.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStep.errors.base) {
|
||||||
|
this._error = this.localize(
|
||||||
|
`ui.panel.page-authorize.form.providers.homeassistant.error.${newStep.errors.base}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._step = newStep;
|
||||||
|
} catch (err: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Error submitting step", err);
|
||||||
|
this._error = this.localize("ui.panel.page-authorize.form.unknown_error");
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _redirect(authCode: string) {
|
||||||
|
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||||
|
let url = this.redirectUri!;
|
||||||
|
if (!url.includes("?")) {
|
||||||
|
url += "?";
|
||||||
|
} else if (!url.endsWith("&")) {
|
||||||
|
url += "&";
|
||||||
|
}
|
||||||
|
|
||||||
|
url += `code=${encodeURIComponent(authCode)}`;
|
||||||
|
|
||||||
|
if (this.oauth2State) {
|
||||||
|
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
||||||
|
}
|
||||||
|
url += `&storeToken=true`;
|
||||||
|
|
||||||
|
document.location.assign(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _otherLogin() {
|
||||||
|
fireEvent(this, "default-login-flow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-local-auth-flow": HaLocalAuthFlow;
|
||||||
|
}
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"default-login-flow": undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -35,8 +35,8 @@ export class HaPickAuthProvider extends LitElement {
|
|||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}</mwc-list
|
)}
|
||||||
>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,25 +33,29 @@ class PersonBadge extends LitElement {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
.picture {
|
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.picture {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
.initials {
|
.initials {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 40px;
|
width: 100%;
|
||||||
line-height: 40px;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
text-align: center;
|
|
||||||
background-color: var(--light-primary-color);
|
background-color: var(--light-primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text-light-primary-color, var(--primary-text-color));
|
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-size: var(--person-badge-font-size, 1em);
|
||||||
}
|
}
|
||||||
.initials.long {
|
.initials.long {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
|
@ -11,6 +11,7 @@ export interface AuthProvider {
|
|||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
users?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Credential {
|
export interface Credential {
|
||||||
|
@ -21,6 +21,11 @@ export const fetchPersons = (hass: HomeAssistant) =>
|
|||||||
config: Person[];
|
config: Person[];
|
||||||
}>({ type: "person/list" });
|
}>({ type: "person/list" });
|
||||||
|
|
||||||
|
export const listPersons = () =>
|
||||||
|
fetch("/api/person/list", {
|
||||||
|
credentials: "same-origin",
|
||||||
|
});
|
||||||
|
|
||||||
export const createPerson = (
|
export const createPerson = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
values: PersonMutableParams
|
values: PersonMutableParams
|
||||||
|
Loading…
x
Reference in New Issue
Block a user