From c1e5561efe7f8fed3bfdae5bb96356ce5be4b6e5 Mon Sep 17 00:00:00 2001 From: Morten Jensen <3702620+Jensen95@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:44:35 +0200 Subject: [PATCH] Show a hint when a Verisure password is longer than 30 characters (#184250) Co-authored-by: Claude Sonnet 5.5 --- .../components/verisure/config_flow.py | 15 ++- .../components/verisure/strings.json | 7 ++ tests/components/verisure/test_config_flow.py | 92 +++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index d0ff4dec9622..9381af0700f6 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -26,6 +26,9 @@ from .const import ( ) from .coordinator import VerisureConfigEntry +# Observed on the Danish My Pages site, other countries may differ. +MAX_PASSWORD_LENGTH = 30 + class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Verisure.""" @@ -90,7 +93,11 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_mfa() else: LOGGER.debug("Could not log in to Verisure, %s", ex) - errors["base"] = "invalid_auth" + errors["base"] = ( + "invalid_auth_password_too_long" + if len(self.password) > MAX_PASSWORD_LENGTH + else "invalid_auth" + ) except VerisureRateLimitError as ex: LOGGER.debug("Verisure rate limited during login, %s", ex) errors["base"] = "mfa_rate_limited" @@ -233,7 +240,11 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_reauth_mfa() else: LOGGER.debug("Could not log in to Verisure, %s", ex) - errors["base"] = "invalid_auth" + errors["base"] = ( + "invalid_auth_password_too_long" + if len(self.password) > MAX_PASSWORD_LENGTH + else "invalid_auth" + ) except VerisureRateLimitError as ex: LOGGER.debug("Verisure rate limited during reauth login, %s", ex) errors["base"] = "mfa_rate_limited" diff --git a/homeassistant/components/verisure/strings.json b/homeassistant/components/verisure/strings.json index d49ac4cd3257..b01ac16b9b8f 100644 --- a/homeassistant/components/verisure/strings.json +++ b/homeassistant/components/verisure/strings.json @@ -5,6 +5,7 @@ }, "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_auth_password_too_long": "Invalid authentication. Verisure may not accept passwords longer than 30 characters. Try changing yours to 30 characters or fewer.", "mfa_rate_limited": "Too many login attempts. Wait before trying again.", "unknown": "[%key:common::config_flow::error::unknown%]", "unknown_mfa": "Unknown error occurred during MFA setup" @@ -27,6 +28,9 @@ "description": "Re-authenticate with your Verisure My Pages account.", "email": "[%key:common::config_flow::data::email%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "password": "[%key:component::verisure::config::step::user::data_description::password%]" } }, "reauth_mfa": { @@ -40,6 +44,9 @@ "description": "Sign in with your Verisure My Pages account.", "email": "[%key:common::config_flow::data::email%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "password": "Verisure may not accept passwords longer than 30 characters." } } } diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 413a955587ed..4435c1ed5fd1 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -539,6 +539,98 @@ async def test_reauth_flow_errors( assert len(mock_setup_entry.mock_calls) == 1 +@pytest.mark.parametrize( + ("password", "error"), + [ + pytest.param("a" * 30, "invalid_auth", id="at_limit"), + pytest.param("a" * 31, "invalid_auth_password_too_long", id="over_limit"), + ], +) +async def test_user_flow_invalid_auth_password_length( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_verisure_config_flow: MagicMock, + password: str, + error: str, +) -> None: + """Test a rejected login hints at the password length when it is too long.""" + mock_verisure_config_flow.get_installations.return_value = { + k1: {k2: {k3: [v3[0]] for k3, v3 in v2.items()} for k2, v2 in v1.items()} + for k1, v1 in mock_verisure_config_flow.get_installations.return_value.items() + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_verisure_config_flow.login.side_effect = VerisureLoginError("Login failed") + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"email": "verisure_my_pages@example.com", "password": password}, + ) + await hass.async_block_till_done() + + assert result2.get("type") is FlowResultType.FORM + assert result2.get("step_id") == "user" + assert result2.get("errors") == {"base": error} + + mock_verisure_config_flow.login.side_effect = None + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {"email": "verisure_my_pages@example.com", "password": "SuperS3cr3t!"}, + ) + await hass.async_block_till_done() + + assert result3.get("type") is FlowResultType.CREATE_ENTRY + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("password", "error"), + [ + pytest.param("a" * 30, "invalid_auth", id="at_limit"), + pytest.param("a" * 31, "invalid_auth_password_too_long", id="over_limit"), + ], +) +@pytest.mark.usefixtures("mock_setup_entry") +async def test_reauth_flow_invalid_auth_password_length( + hass: HomeAssistant, + mock_verisure_config_flow: MagicMock, + mock_config_entry: MockConfigEntry, + password: str, + error: str, +) -> None: + """Test a rejected reauth login hints at the password length when too long.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reauth_flow(hass) + + mock_verisure_config_flow.login.side_effect = VerisureLoginError("Login failed") + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"email": "verisure_my_pages@example.com", "password": password}, + ) + await hass.async_block_till_done() + + assert result2.get("type") is FlowResultType.FORM + assert result2.get("step_id") == "reauth_confirm" + assert result2.get("errors") == {"base": error} + + mock_verisure_config_flow.login.side_effect = None + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {"email": "verisure_my_pages@example.com", "password": "SuperS3cr3t!"}, + ) + await hass.async_block_till_done() + + assert result3.get("type") is FlowResultType.ABORT + assert result3.get("reason") == "reauth_successful" + assert mock_config_entry.data == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + async def test_user_flow_mfa_rate_limited( hass: HomeAssistant, mock_verisure_config_flow: MagicMock,