Show a hint when a Verisure password is longer than 30 characters (#184250)

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Morten Jensen
2026-10-05 12:44:35 +02:00
committed by GitHub
co-authored by Claude Sonnet 5.5
parent e2d077e560
commit c1e5561efe
3 changed files with 112 additions and 2 deletions
@@ -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"
@@ -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."
}
}
}
@@ -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,