From 5961b5fd1ceade6ff2bc9fc23b639f7eede69817 Mon Sep 17 00:00:00 2001 From: Mattias Persson Date: Sat, 27 Jan 2024 22:19:51 +0100 Subject: [PATCH] Update proxy and auth flow --- data/configuration.yaml | 1 - server.js | 75 +++++++++++++--- src/lib/Modal/LoginModal.svelte | 150 ++++++++++++++++++++++---------- src/lib/Settings/Logout.svelte | 2 +- src/lib/Socket.ts | 37 ++------ src/routes/+page.server.ts | 21 +---- src/routes/+page.svelte | 1 + vite.config.ts | 16 +--- 8 files changed, 177 insertions(+), 126 deletions(-) diff --git a/data/configuration.yaml b/data/configuration.yaml index 03a0c036..0ce74eac 100644 --- a/data/configuration.yaml +++ b/data/configuration.yaml @@ -1,2 +1 @@ locale: en -hassUrl: http://192.168.1.241:8123 diff --git a/server.js b/server.js index 59a70f07..c93e7bb6 100644 --- a/server.js +++ b/server.js @@ -5,26 +5,77 @@ import dotenv from 'dotenv'; dotenv.config(); +let logTarget; + const app = express(); -const HASS_URL = process.env.HASS_URL; +// environment +const ADDON = process.env.ADDON === 'true'; const PORT = process.env.PORT; +const HASS_PORT = process.env.HASS_PORT; +const EXPOSED_PORT = process.env.EXPOSED_PORT; -// production proxy -if (HASS_URL) { - app.use( - ['/local/', '/api/image/', '/api/*_proxy*'], - createProxyMiddleware({ - target: HASS_URL, - changeOrigin: true - }) - ); +// dynamically set target for proxy middleware +function customRouter(req) { + let target = process.env.HASS_URL; + + if (ADDON) { + // headers + const source = req.headers['x-hass-source']; + const forwardedProto = req.headers['x-forwarded-proto']; + const forwardedHost = req.headers['x-forwarded-host']; + const host = req.headers['host']; + + // ingress + if (source && forwardedProto && forwardedHost) { + target = `${forwardedProto}://${forwardedHost}`; + process.env.HASS_URL = target; + } + + // exposed port + else if (host && EXPOSED_PORT && HASS_PORT) { + target = `http://${host.replace(EXPOSED_PORT, HASS_PORT)}`; + process.env.HASS_URL = target; + } + } + + // target should be defined now + if (!target) { + throw new Error('Proxy target could not be determined'); + } + + // log actual target instead of placeholder `...` because + // the router gets invoked before headers are processed + if (!logTarget) { + logTarget = `... -> ${target}`; + console.log(logTarget); + } + + return target; } +// production proxy +app.use( + ['/local/', '/api/', '/auth/'], + createProxyMiddleware({ + target: '...', + router: customRouter, + changeOrigin: true + }) +); + // let sveltekit handle everything else app.use(handler); app.listen(PORT, () => { - console.debug(`ENV PORT: ${PORT}`); - console.debug(`ENV ADDON ${process.env.ADDON || false}`); + if (ADDON) { + console.log('ADDON:', ADDON); + console.log('INGRESS_PORT:', PORT); + console.log('EXPOSED_PORT:', EXPOSED_PORT); + console.log('HASS_PORT:', HASS_PORT); + } else { + console.log('HASS_URL:', process.env.HASS_URL); + console.log('PORT:', PORT); + console.log('ADDON:', ADDON); + } }); diff --git a/src/lib/Modal/LoginModal.svelte b/src/lib/Modal/LoginModal.svelte index 8cb975d5..814da775 100644 --- a/src/lib/Modal/LoginModal.svelte +++ b/src/lib/Modal/LoginModal.svelte @@ -1,64 +1,118 @@ {#if isOpen} - +

{$lang('login')}

{#if disabled} @@ -126,11 +174,11 @@
- {#if invalidAuth} -
{invalidAuth}
+ {#if errorMessage} +
{errorMessage}
{:else if step === 'init'}
{$lang('authorizing_client').replace('{clientId}', '"Fusion"')} @@ -146,7 +194,7 @@ {#if step === 'init'}

{$lang('username')}

{$lang('mfa_code')}

- +
{/if} diff --git a/src/lib/Settings/Logout.svelte b/src/lib/Settings/Logout.svelte index 12eaa1ac..d29cf9a1 100644 --- a/src/lib/Settings/Logout.svelte +++ b/src/lib/Settings/Logout.svelte @@ -8,7 +8,7 @@ title: $lang('log_out'), message: $lang('confirm_log_out'), confirm: async () => { - localStorage.removeItem('auth'); + localStorage.removeItem('hassTokens'); location.reload(); }, cancel: () => { diff --git a/src/lib/Socket.ts b/src/lib/Socket.ts index efddbebf..58fef03b 100644 --- a/src/lib/Socket.ts +++ b/src/lib/Socket.ts @@ -7,15 +7,13 @@ import { ERR_INVALID_AUTH, ERR_CONNECTION_LOST, ERR_HASS_HOST_REQUIRED, - ERR_INVALID_HTTPS_TO_HTTP, - genClientId + ERR_INVALID_HTTPS_TO_HTTP } from 'home-assistant-js-websocket'; import { states, connection, config, connected } from '$lib/Stores'; import { openModal } from 'svelte-modals'; -import { base } from '$app/paths'; -export async function authenticate(hassUrl: string) { - const storage = localStorage.getItem('auth'); +export async function authenticate() { + const storage = localStorage.getItem('hassTokens'); const data = storage ? JSON.parse(storage) : null; if (data?.access_token) { @@ -23,13 +21,8 @@ export async function authenticate(hassUrl: string) { const auth = new Auth(data); await webSocket(auth); } else { - // login - const clientId = genClientId(); - openModal(async () => import('$lib/Modal/LoginModal.svelte'), { - clientId, - hassUrl, - flow_id: await getFlowId(clientId, hassUrl) - }); + // login modal + openModal(async () => import('$lib/Modal/LoginModal.svelte')); } } @@ -70,7 +63,7 @@ export async function webSocket(auth: Auth) { console.error('ERR_INVALID_AUTH'); // data is invalid - localStorage.removeItem('auth'); + localStorage.removeItem('hassTokens'); location.reload(); break; @@ -96,21 +89,3 @@ export async function webSocket(auth: Auth) { throw error; } } - -export async function getFlowId(clientId: string, hassUrl: string) { - try { - const response = await fetch(`${base}/api/auth`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ clientId, hassUrl }) - }); - - const data = await response.json(); - - return data.flow_id; - } catch (error) { - console.error('error fetching flow_id:', error); - } -} diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 8003af47..4c1ce0d7 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -30,7 +30,7 @@ async function loadFile(file: string) { /** * Server load function */ -export async function load({ request }): Promise<{ +export async function load(): Promise<{ configuration: Configuration; dashboard: Dashboard; theme: any; @@ -42,24 +42,7 @@ export async function load({ request }): Promise<{ loadFile('./data/dashboard.yaml') ]); - // determine hassUrl - let hassUrl = process.env.ADDON ? configuration.hassUrl : process.env.HASS_URL; - - if (!hassUrl) { - const proto = request.headers.get('x-forwarded-proto'); - const host = request.headers.get('x-forwarded-host'); - if (proto && host) hassUrl = `${proto}://${host}`; - } - - // update hassUrl - if (hassUrl && hassUrl !== configuration.hassUrl) { - configuration.hassUrl = hassUrl; - try { - await writeFile('./data/configuration.yaml', yaml.dump(configuration), 'utf8'); - } catch (error) { - console.error('error updating configuration.yaml:', error); - } - } + configuration.hassUrl = process.env.HASS_URL; // initialize keys if missing dashboard.views = dashboard.views || []; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a6548832..259fa4cf 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -207,6 +207,7 @@ 'main main'; min-height: 100vh; overflow: hidden; + grid-template-rows: auto auto auto 1fr !important; } } diff --git a/vite.config.ts b/vite.config.ts index bf42b302..4f7d9947 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -59,23 +59,11 @@ export default defineConfig({ target: process.env.HASS_URL, changeOrigin: true }, - '/api/image/': { + '/api/': { target: process.env.HASS_URL, changeOrigin: true }, - '/api/image_proxy/': { - target: process.env.HASS_URL, - changeOrigin: true - }, - '/api/media_player_proxy/': { - target: process.env.HASS_URL, - changeOrigin: true - }, - '/api/camera_proxy/': { - target: process.env.HASS_URL, - changeOrigin: true - }, - '/api/camera_proxy_stream/': { + '/auth/': { target: process.env.HASS_URL, changeOrigin: true }