From d8ec18b7c49197dfce5d1c835cdf93c8c8c5f801 Mon Sep 17 00:00:00 2001 From: Mattias Persson Date: Thu, 18 Apr 2024 21:10:44 +0200 Subject: [PATCH] Add long-lived access token support --- src/lib/Components/TokenModal.svelte | 99 ++++++++++++++++++++++++++++ src/lib/Settings/Index.svelte | 11 +++- src/lib/Settings/Token.svelte | 45 +++++++++++++ src/lib/Socket.ts | 41 ++++++++---- src/lib/Types.ts | 1 + src/routes/+page.svelte | 21 ++++-- 6 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 src/lib/Components/TokenModal.svelte create mode 100644 src/lib/Settings/Token.svelte diff --git a/src/lib/Components/TokenModal.svelte b/src/lib/Components/TokenModal.svelte new file mode 100644 index 00000000..0d5c1f39 --- /dev/null +++ b/src/lib/Components/TokenModal.svelte @@ -0,0 +1,99 @@ + + +{#if isOpen} + +

{$lang('login')}

+ +
+

+ A long-lived access token is required for authentication when using both Ingress and the + Home Assistant Companion app simultaneously. +

+

+ Open your user profile, + {$configuration?.hassUrl}/profile/security + + to create and copy a new long-lived access token. +

+
+ +
+

{$lang('token')}

+ + + + +
+
+{/if} + + diff --git a/src/lib/Settings/Index.svelte b/src/lib/Settings/Index.svelte index 75110739..f8782b85 100644 --- a/src/lib/Settings/Index.svelte +++ b/src/lib/Settings/Index.svelte @@ -1,12 +1,13 @@ + +

{$lang('token')}

+ +

+ {$lang('docs')} - + {href} +

+ + + + diff --git a/src/lib/Socket.ts b/src/lib/Socket.ts index 42b72cc0..efb23a1f 100644 --- a/src/lib/Socket.ts +++ b/src/lib/Socket.ts @@ -1,5 +1,6 @@ import { getAuth, + createLongLivedTokenAuth, createConnection, subscribeConfig, subscribeEntities, @@ -9,12 +10,12 @@ import { ERR_HASS_HOST_REQUIRED, ERR_INVALID_HTTPS_TO_HTTP } from 'home-assistant-js-websocket'; -import type { SaveTokensFunc } from 'home-assistant-js-websocket'; +import type { Auth, AuthData } from 'home-assistant-js-websocket'; import { states, connection, config, connected, event, persistentNotifications } from '$lib/Stores'; -import { closeModal } from 'svelte-modals'; -import type { PersistentNotification } from '$lib/Types'; +import { openModal, closeModal } from 'svelte-modals'; +import type { Configuration, PersistentNotification } from '$lib/Types'; -export const options = { +const options = { hassUrl: undefined as string | undefined, async loadTokens() { try { @@ -23,7 +24,7 @@ export const options = { return undefined; } }, - saveTokens(tokens: SaveTokensFunc) { + saveTokens(tokens: AuthData | null) { localStorage.hassTokens = JSON.stringify(tokens); }, clearTokens() { @@ -31,19 +32,31 @@ export const options = { } }; -export async function authentication(options: { hassUrl?: string }) { - let auth; +export async function authentication(configuration: Configuration) { + if (!configuration?.hassUrl) { + console.error('hassUrl is undefined...'); + return; + } + + let auth: Auth | undefined; try { - auth = await getAuth(options); - if (auth.expired) { - auth.refreshAccessToken(); + // long lived access token + if (configuration?.token) { + auth = createLongLivedTokenAuth(configuration?.hassUrl, configuration?.token); + + // companion app and ingress causes issues with auth redirect + // open special modal to enter long lived access token + } else if (navigator.userAgent.includes('Home Assistant')) { + openModal(() => import('$lib/Components/TokenModal.svelte')); + return; + + // default auth flow + } else { + auth = await getAuth({ ...options, hassUrl: configuration?.hassUrl }); + if (auth.expired) auth.refreshAccessToken(); } - } catch (_error) { - handleError(_error); - } - try { // connection const conn = await createConnection({ auth }); connection.set(conn); diff --git a/src/lib/Types.ts b/src/lib/Types.ts index aca520fe..d76eb79b 100644 --- a/src/lib/Types.ts +++ b/src/lib/Types.ts @@ -6,6 +6,7 @@ export interface Configuration { custom_js?: boolean; motion?: boolean; addons?: Addons; + token?: string; } export interface Addons { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6f0333d0..a3e1eedf 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -16,7 +16,7 @@ clickOriginatedFromMenu, connection } from '$lib/Stores'; - import { authentication, options } from '$lib/Socket'; + import { authentication } from '$lib/Socket'; import { onDestroy, onMount } from 'svelte'; import { browser } from '$app/environment'; import { modals } from 'svelte-modals'; @@ -68,12 +68,8 @@ console.debug('authenticating...'); - if ($configuration?.hassUrl) { - options.hassUrl = $configuration?.hassUrl; - } - try { - await authentication(options); + await authentication($configuration); console.debug('authenticated.'); clearInterval(retryInterval); } catch (err) { @@ -83,6 +79,19 @@ } } + /** + * Reconnect if long-lived access token changes + */ + $: if ($configuration?.token) updateConnection(); + + function updateConnection() { + if (isConnecting || !browser) return; + clearInterval(retryInterval); + + connect(); + retryInterval = setInterval(connect, 3000); + } + onDestroy(() => clearInterval(retryInterval)); onMount(async () => {