From 9d9025164a710232fea02efe11ce4374157cde32 Mon Sep 17 00:00:00 2001 From: Ramon <3713936+mexican75@users.noreply.github.com> Date: Fri, 1 May 2026 14:57:40 +0200 Subject: [PATCH] Redirect native-picker click on OIDC provider to /auth/oidc/welcome (fix dead-end) (#266) Fixes #252 --- custom_components/auth_oidc/provider.py | 7 +- .../auth_oidc/static/injection.js | 71 ++++++++++++------- tests/test_hass_auth_provider.py | 20 ++++++ 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/custom_components/auth_oidc/provider.py b/custom_components/auth_oidc/provider.py index 1943bb7..9026b71 100644 --- a/custom_components/auth_oidc/provider.py +++ b/custom_components/auth_oidc/provider.py @@ -65,8 +65,11 @@ class OpenIDAuthProvider(AuthProvider): { # Currently register as default, might be used when we have multiple OIDC providers CONF_ID: "default", - # Name displayed in the UI - CONF_NAME: config.get("display_name", DEFAULT_TITLE), + # Stable label for HA's native auth-picker row. Kept fixed so the + # frontend-injection script can match it without threading the + # user-configurable display_name through. The user's display_name + # is still rendered on the welcome page. + CONF_NAME: DEFAULT_TITLE, # Type CONF_TYPE: PROVIDER_TYPE, }, diff --git a/custom_components/auth_oidc/static/injection.js b/custom_components/auth_oidc/static/injection.js index 95c556a..1cba5c5 100644 --- a/custom_components/auth_oidc/static/injection.js +++ b/custom_components/auth_oidc/static/injection.js @@ -1,37 +1,55 @@ /** - * hass-oidc-auth - UX script to automatically select the Home Assistant auth provider when the "Login aborted" message is shown. + * Frontend helpers for /auth/authorize: auto-select on aborted login, + * and route picker clicks on our provider through /auth/oidc/welcome. */ +const OIDC_PROVIDER_NAME = "OpenID Connect (SSO)" // matches provider.py CONF_NAME +const OIDC_WELCOME_PATH = "/auth/oidc/welcome" + let authFlowElement = null +let pickerIntercepted = false + +function interceptPickerRow(authProviderElement) { + if (pickerIntercepted) return + if (!authProviderElement) return + if (!authProviderElement.shadowRoot) { + console.warn("[OIDC] ha-pick-auth-provider has no shadowRoot; HA frontend may have changed.") + return + } + const items = authProviderElement.shadowRoot.querySelectorAll('ha-list-item') + if (items.length === 0) return // not yet populated; retry on next mutation + for (const item of items) { + if ((item.innerText || '').trim() !== OIDC_PROVIDER_NAME) continue + item.addEventListener('click', (e) => { + e.stopImmediatePropagation() + e.preventDefault() + window.location.href = + OIDC_WELCOME_PATH + + '?redirect_uri=' + encodeURIComponent(btoa(window.location.href)) + }, true) + pickerIntercepted = true + return + } +} function update() { - // Find ha-auth-flow - authFlowElement = document.querySelector('ha-auth-flow'); + authFlowElement = document.querySelector('ha-auth-flow') + if (!authFlowElement) return - if (!authFlowElement) { - return; - } + const authProviderElement = document.querySelector('ha-pick-auth-provider') - // Check if the text "Login aborted" is present on the page - if (!authFlowElement.innerText.includes('Login aborted')) { - return; - } + // Intercept picker clicks so the OIDC cookie is set before submit. + interceptPickerRow(authProviderElement) - // Find the ha-pick-auth-provider element - const authProviderElement = document.querySelector('ha-pick-auth-provider'); - - if (!authProviderElement) { - return; - } - - // Click the first ha-list-item element inside the ha-pick-auth-provider - const firstListItem = authProviderElement.shadowRoot?.querySelector('ha-list-item'); - if (!firstListItem) { - console.warn("[OIDC] No ha-list-item found inside ha-pick-auth-provider. Not automatically selecting HA provider."); - return; - } - - firstListItem.click(); + // Auto-select on "Login aborted". + if (!authFlowElement.innerText.includes('Login aborted')) return + if (!authProviderElement) return + const firstListItem = authProviderElement.shadowRoot?.querySelector('ha-list-item') + if (!firstListItem) { + console.warn("[OIDC] No ha-list-item found inside ha-pick-auth-provider. Not automatically selecting OIDC provider.") + return + } + firstListItem.click() } // Hide the content until ready @@ -58,4 +76,5 @@ setTimeout(() => { // Force display the content document.querySelector(".content").style.display = ""; -}, 300) \ No newline at end of file + update(); +}, 300) diff --git a/tests/test_hass_auth_provider.py b/tests/test_hass_auth_provider.py index 49cdcec..1217e35 100644 --- a/tests/test_hass_auth_provider.py +++ b/tests/test_hass_auth_provider.py @@ -19,6 +19,8 @@ from custom_components.auth_oidc import DOMAIN from custom_components.auth_oidc.config.const import ( DISCOVERY_URL, CLIENT_ID, + DEFAULT_TITLE, + DISPLAY_NAME, FEATURES, FEATURES_AUTOMATIC_PERSON_CREATION, FEATURES_AUTOMATIC_USER_LINKING, @@ -58,6 +60,24 @@ async def test_setup_success_auth_provider_registration(hass: HomeAssistant): assert auth_providers[0].support_mfa is False +@pytest.mark.asyncio +async def test_provider_name_is_stable_regardless_of_display_name(hass: HomeAssistant): + """CONF_NAME stays at DEFAULT_TITLE so injection.js can match the picker + row regardless of the configured display_name.""" + await setup( + hass, + { + CLIENT_ID: "dummy", + DISCOVERY_URL: "https://example.com/.well-known/openid-configuration", + DISPLAY_NAME: "Custom / Branded IdP", + }, + True, + ) + + provider = hass.auth.get_auth_providers(DOMAIN)[0] + assert provider.name == DEFAULT_TITLE + + @pytest.mark.asyncio async def test_provider_ip_fallback_fails_closed_without_request_context( hass: HomeAssistant,