Redirect native-picker click on OIDC provider to /auth/oidc/welcome (fix dead-end) (#266)

Fixes #252
This commit is contained in:
Ramon
2026-05-01 14:57:40 +02:00
committed by GitHub
parent d251ebfb92
commit 9d9025164a
3 changed files with 70 additions and 28 deletions

View File

@@ -65,8 +65,11 @@ class OpenIDAuthProvider(AuthProvider):
{ {
# Currently register as default, might be used when we have multiple OIDC providers # Currently register as default, might be used when we have multiple OIDC providers
CONF_ID: "default", CONF_ID: "default",
# Name displayed in the UI # Stable label for HA's native auth-picker row. Kept fixed so the
CONF_NAME: config.get("display_name", DEFAULT_TITLE), # 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 # Type
CONF_TYPE: PROVIDER_TYPE, CONF_TYPE: PROVIDER_TYPE,
}, },

View File

@@ -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 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() { function update() {
// Find ha-auth-flow authFlowElement = document.querySelector('ha-auth-flow')
authFlowElement = document.querySelector('ha-auth-flow'); if (!authFlowElement) return
if (!authFlowElement) { const authProviderElement = document.querySelector('ha-pick-auth-provider')
return;
}
// Check if the text "Login aborted" is present on the page // Intercept picker clicks so the OIDC cookie is set before submit.
if (!authFlowElement.innerText.includes('Login aborted')) { interceptPickerRow(authProviderElement)
return;
}
// Find the ha-pick-auth-provider element // Auto-select on "Login aborted".
const authProviderElement = document.querySelector('ha-pick-auth-provider'); if (!authFlowElement.innerText.includes('Login aborted')) return
if (!authProviderElement) return
if (!authProviderElement) { const firstListItem = authProviderElement.shadowRoot?.querySelector('ha-list-item')
return; if (!firstListItem) {
} console.warn("[OIDC] No ha-list-item found inside ha-pick-auth-provider. Not automatically selecting OIDC provider.")
return
// Click the first ha-list-item element inside the ha-pick-auth-provider }
const firstListItem = authProviderElement.shadowRoot?.querySelector('ha-list-item'); firstListItem.click()
if (!firstListItem) {
console.warn("[OIDC] No ha-list-item found inside ha-pick-auth-provider. Not automatically selecting HA provider.");
return;
}
firstListItem.click();
} }
// Hide the content until ready // Hide the content until ready
@@ -58,4 +76,5 @@ setTimeout(() => {
// Force display the content // Force display the content
document.querySelector(".content").style.display = ""; document.querySelector(".content").style.display = "";
}, 300) update();
}, 300)

View File

@@ -19,6 +19,8 @@ from custom_components.auth_oidc import DOMAIN
from custom_components.auth_oidc.config.const import ( from custom_components.auth_oidc.config.const import (
DISCOVERY_URL, DISCOVERY_URL,
CLIENT_ID, CLIENT_ID,
DEFAULT_TITLE,
DISPLAY_NAME,
FEATURES, FEATURES,
FEATURES_AUTOMATIC_PERSON_CREATION, FEATURES_AUTOMATIC_PERSON_CREATION,
FEATURES_AUTOMATIC_USER_LINKING, 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 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 @pytest.mark.asyncio
async def test_provider_ip_fallback_fails_closed_without_request_context( async def test_provider_ip_fallback_fails_closed_without_request_context(
hass: HomeAssistant, hass: HomeAssistant,