2023-08-19 22:41:39 +02:00
|
|
|
import punycode from "punycode";
|
2023-08-08 14:43:55 +02:00
|
|
|
import {
|
|
|
|
css,
|
|
|
|
CSSResultGroup,
|
|
|
|
html,
|
|
|
|
LitElement,
|
|
|
|
nothing,
|
|
|
|
PropertyValues,
|
|
|
|
} from "lit";
|
2023-02-21 20:28:21 +01:00
|
|
|
import { customElement, property, state } from "lit/decorators";
|
2021-04-26 23:54:47 +02:00
|
|
|
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
2021-01-13 17:17:12 +01:00
|
|
|
import { extractSearchParamsObject } from "../common/url/search-params";
|
2023-08-08 14:43:55 +02:00
|
|
|
import "../components/ha-alert";
|
2020-06-17 19:32:27 +02:00
|
|
|
import {
|
|
|
|
AuthProvider,
|
|
|
|
AuthUrlSearchParams,
|
2021-01-13 17:17:12 +01:00
|
|
|
fetchAuthProviders,
|
2020-06-17 19:32:27 +02:00
|
|
|
} from "../data/auth";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
2019-03-30 00:43:42 +01:00
|
|
|
import { registerServiceWorker } from "../util/register-service-worker";
|
2020-04-14 18:05:45 +02:00
|
|
|
import "./ha-auth-flow";
|
2018-11-26 14:10:01 +01:00
|
|
|
|
2020-11-23 10:39:40 +01:00
|
|
|
import("./ha-pick-auth-provider");
|
2018-11-26 14:10:01 +01:00
|
|
|
|
2023-08-08 14:43:55 +02:00
|
|
|
const appNames = {
|
|
|
|
"https://home-assistant.io/iOS": "iOS",
|
|
|
|
"https://home-assistant.io/android": "Android",
|
|
|
|
};
|
|
|
|
|
2023-02-21 20:28:21 +01:00
|
|
|
@customElement("ha-authorize")
|
|
|
|
export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
2019-10-19 07:50:27 +02:00
|
|
|
@property() public clientId?: string;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2019-10-19 07:50:27 +02:00
|
|
|
@property() public redirectUri?: string;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2019-10-19 07:50:27 +02:00
|
|
|
@property() public oauth2State?: string;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
@state() private _authProvider?: AuthProvider;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
@state() private _authProviders?: AuthProvider[];
|
2018-11-26 14:10:01 +01:00
|
|
|
|
2023-08-08 14:43:55 +02:00
|
|
|
@state() private _ownInstance = false;
|
|
|
|
|
|
|
|
@state() private _error?: string;
|
|
|
|
|
2018-11-26 14:10:01 +01:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.translationFragment = "page-authorize";
|
2020-06-17 19:32:27 +02:00
|
|
|
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
2018-11-26 14:10:01 +01:00
|
|
|
if (query.client_id) {
|
|
|
|
this.clientId = query.client_id;
|
|
|
|
}
|
|
|
|
if (query.redirect_uri) {
|
|
|
|
this.redirectUri = query.redirect_uri;
|
|
|
|
}
|
|
|
|
if (query.state) {
|
|
|
|
this.oauth2State = query.state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-13 08:32:53 +01:00
|
|
|
protected render() {
|
2023-08-08 14:43:55 +02:00
|
|
|
if (this._error) {
|
|
|
|
return html`<ha-alert alert-type="error"
|
|
|
|
>${this._error} ${this.redirectUri}</ha-alert
|
|
|
|
>`;
|
|
|
|
}
|
|
|
|
|
2018-11-26 14:10:01 +01:00
|
|
|
if (!this._authProviders) {
|
|
|
|
return html`
|
2019-03-15 17:23:21 +01:00
|
|
|
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
|
2018-11-26 14:10:01 +01:00
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const inactiveProviders = this._authProviders.filter(
|
|
|
|
(prv) => prv !== this._authProvider
|
|
|
|
);
|
|
|
|
|
2023-08-08 14:43:55 +02:00
|
|
|
const app = this.clientId && this.clientId in appNames;
|
|
|
|
|
2018-11-26 14:10:01 +01:00
|
|
|
return html`
|
2023-08-08 14:43:55 +02:00
|
|
|
${!this._ownInstance
|
|
|
|
? html`<ha-alert .alertType=${app ? "info" : "warning"}>
|
|
|
|
${app
|
|
|
|
? this.localize("ui.panel.page-authorize.authorizing_app", {
|
|
|
|
app: appNames[this.clientId!],
|
|
|
|
})
|
|
|
|
: this.localize("ui.panel.page-authorize.authorizing_client", {
|
|
|
|
clientId: html`<b
|
|
|
|
>${this.clientId
|
|
|
|
? punycode.toASCII(this.clientId)
|
|
|
|
: this.clientId}</b
|
|
|
|
>`,
|
|
|
|
})}
|
|
|
|
</ha-alert>`
|
|
|
|
: html`<p>${this.localize("ui.panel.page-authorize.authorizing")}</p>`}
|
|
|
|
${inactiveProviders.length > 0
|
|
|
|
? html`<p>
|
|
|
|
${this.localize("ui.panel.page-authorize.logging_in_with", {
|
|
|
|
authProviderName: html`<b>${this._authProvider!.name}</b>`,
|
|
|
|
})}
|
|
|
|
</p>`
|
|
|
|
: nothing}
|
2018-11-26 14:10:01 +01:00
|
|
|
|
|
|
|
<ha-auth-flow
|
2021-09-30 12:39:03 +02:00
|
|
|
.resources=${this.resources}
|
|
|
|
.clientId=${this.clientId}
|
|
|
|
.redirectUri=${this.redirectUri}
|
|
|
|
.oauth2State=${this.oauth2State}
|
|
|
|
.authProvider=${this._authProvider}
|
2023-04-14 20:38:34 +02:00
|
|
|
.localize=${this.localize}
|
2018-11-26 14:10:01 +01:00
|
|
|
></ha-auth-flow>
|
|
|
|
|
2019-01-27 04:34:07 +01:00
|
|
|
${inactiveProviders.length > 0
|
|
|
|
? html`
|
|
|
|
<ha-pick-auth-provider
|
2023-04-14 20:38:34 +02:00
|
|
|
.localize=${this.localize}
|
2021-09-30 12:39:03 +02:00
|
|
|
.clientId=${this.clientId}
|
|
|
|
.authProviders=${inactiveProviders}
|
|
|
|
@pick-auth-provider=${this._handleAuthProviderPick}
|
2019-01-27 04:34:07 +01:00
|
|
|
></ha-pick-auth-provider>
|
|
|
|
`
|
|
|
|
: ""}
|
2018-11-26 14:10:01 +01:00
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
2019-03-30 00:43:42 +01:00
|
|
|
protected firstUpdated(changedProps: PropertyValues) {
|
|
|
|
super.firstUpdated(changedProps);
|
2023-08-08 14:43:55 +02:00
|
|
|
|
|
|
|
if (!this.redirectUri) {
|
|
|
|
this._error = "Invalid redirect URI";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let url: URL;
|
|
|
|
|
|
|
|
try {
|
|
|
|
url = new URL(this.redirectUri);
|
|
|
|
} catch (err) {
|
|
|
|
this._error = "Invalid redirect URI";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
// eslint-disable-next-line no-script-url
|
|
|
|
["javascript:", "data:", "vbscript:", "file:", "about:"].includes(
|
|
|
|
url.protocol
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
this._error = "Invalid redirect URI";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-30 00:43:42 +01:00
|
|
|
this._fetchAuthProviders();
|
|
|
|
|
2021-04-26 23:54:47 +02:00
|
|
|
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
2022-03-08 19:13:08 +01:00
|
|
|
applyThemesOnElement(
|
|
|
|
document.documentElement,
|
|
|
|
{
|
|
|
|
default_theme: "default",
|
|
|
|
default_dark_theme: null,
|
|
|
|
themes: {},
|
|
|
|
darkMode: true,
|
|
|
|
theme: "default",
|
|
|
|
},
|
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
true
|
|
|
|
);
|
2021-04-26 23:54:47 +02:00
|
|
|
}
|
|
|
|
|
2019-03-30 00:43:42 +01:00
|
|
|
// If we are logging into the instance that is hosting this auth form
|
|
|
|
// we will register the service worker to start preloading.
|
2023-08-08 14:43:55 +02:00
|
|
|
if (url.host === location.host) {
|
|
|
|
this._ownInstance = true;
|
2020-05-13 22:17:47 +02:00
|
|
|
registerServiceWorker(this, false);
|
2019-03-30 00:43:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 12:36:06 +02:00
|
|
|
protected updated(changedProps: PropertyValues) {
|
|
|
|
super.updated(changedProps);
|
|
|
|
if (changedProps.has("language")) {
|
|
|
|
document.querySelector("html")!.setAttribute("lang", this.language!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-30 00:43:42 +01:00
|
|
|
private async _fetchAuthProviders() {
|
2018-11-26 14:10:01 +01:00
|
|
|
// Fetch auth providers
|
|
|
|
try {
|
2019-05-02 20:35:46 +02:00
|
|
|
// We prefetch this data on page load in authorize.html.template for modern builds
|
|
|
|
const response = await ((window as any).providersPromise ||
|
|
|
|
fetchAuthProviders());
|
2018-11-26 14:10:01 +01:00
|
|
|
const authProviders = await response.json();
|
|
|
|
|
|
|
|
// Forward to main screen which will redirect to right onboarding page.
|
|
|
|
if (
|
|
|
|
response.status === 400 &&
|
|
|
|
authProviders.code === "onboarding_required"
|
|
|
|
) {
|
2020-06-17 19:32:27 +02:00
|
|
|
location.href = `/onboarding.html${location.search}`;
|
2018-11-26 14:10:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (authProviders.length === 0) {
|
2023-08-08 14:43:55 +02:00
|
|
|
this._error = "No auth providers returned. Unable to finish login.";
|
2018-11-26 14:10:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._authProviders = authProviders;
|
|
|
|
this._authProvider = authProviders[0];
|
2021-09-30 12:39:03 +02:00
|
|
|
} catch (err: any) {
|
2023-08-08 14:43:55 +02:00
|
|
|
this._error = "Unable to fetch auth providers.";
|
2020-04-14 18:05:45 +02:00
|
|
|
// eslint-disable-next-line
|
2018-11-26 14:10:01 +01:00
|
|
|
console.error("Error loading auth providers", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async _handleAuthProviderPick(ev) {
|
|
|
|
this._authProvider = ev.detail;
|
|
|
|
}
|
2019-03-30 00:43:42 +01:00
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
static get styles(): CSSResultGroup {
|
2019-03-30 00:43:42 +01:00
|
|
|
return css`
|
|
|
|
ha-pick-auth-provider {
|
|
|
|
display: block;
|
|
|
|
margin-top: 48px;
|
|
|
|
}
|
2021-10-07 21:21:35 +02:00
|
|
|
ha-auth-flow {
|
|
|
|
display: block;
|
|
|
|
margin-top: 24px;
|
|
|
|
}
|
2023-08-08 14:43:55 +02:00
|
|
|
ha-alert {
|
|
|
|
display: block;
|
|
|
|
margin: 16px 0;
|
|
|
|
}
|
2023-07-18 15:00:41 +02:00
|
|
|
p {
|
|
|
|
font-size: 14px;
|
|
|
|
line-height: 20px;
|
|
|
|
}
|
2019-03-30 00:43:42 +01:00
|
|
|
`;
|
|
|
|
}
|
2018-11-26 14:10:01 +01:00
|
|
|
}
|