Skip to content

Commit fd10fa1

Browse files
authored
Add reauthentication flow to Uptime Kuma (home-assistant#148772)
1 parent 087a938 commit fd10fa1

File tree

6 files changed

+160
-5
lines changed

6 files changed

+160
-5
lines changed

homeassistant/components/uptime_kuma/config_flow.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from collections.abc import Mapping
56
import logging
67
from typing import Any
78

@@ -38,6 +39,7 @@
3839
vol.Optional(CONF_API_KEY, default=""): str,
3940
}
4041
)
42+
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_API_KEY, default=""): str})
4143

4244

4345
class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN):
@@ -77,3 +79,48 @@ async def async_step_user(
7779
),
7880
errors=errors,
7981
)
82+
83+
async def async_step_reauth(
84+
self, entry_data: Mapping[str, Any]
85+
) -> ConfigFlowResult:
86+
"""Perform reauth upon an API authentication error."""
87+
return await self.async_step_reauth_confirm()
88+
89+
async def async_step_reauth_confirm(
90+
self, user_input: dict[str, Any] | None = None
91+
) -> ConfigFlowResult:
92+
"""Confirm reauthentication dialog."""
93+
errors: dict[str, str] = {}
94+
95+
entry = self._get_reauth_entry()
96+
97+
if user_input is not None:
98+
session = async_get_clientsession(self.hass, entry.data[CONF_VERIFY_SSL])
99+
uptime_kuma = UptimeKuma(
100+
session,
101+
entry.data[CONF_URL],
102+
user_input[CONF_API_KEY],
103+
)
104+
105+
try:
106+
await uptime_kuma.metrics()
107+
except UptimeKumaAuthenticationException:
108+
errors["base"] = "invalid_auth"
109+
except UptimeKumaException:
110+
errors["base"] = "cannot_connect"
111+
except Exception:
112+
_LOGGER.exception("Unexpected exception")
113+
errors["base"] = "unknown"
114+
else:
115+
return self.async_update_reload_and_abort(
116+
entry,
117+
data_updates=user_input,
118+
)
119+
120+
return self.async_show_form(
121+
step_id="reauth_confirm",
122+
data_schema=self.add_suggested_values_to_schema(
123+
data_schema=STEP_REAUTH_DATA_SCHEMA, suggested_values=user_input
124+
),
125+
errors=errors,
126+
)

homeassistant/components/uptime_kuma/coordinator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from homeassistant.config_entries import ConfigEntry
1717
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL
1818
from homeassistant.core import HomeAssistant, callback
19-
from homeassistant.exceptions import ConfigEntryError
19+
from homeassistant.exceptions import ConfigEntryAuthFailed
2020
from homeassistant.helpers import entity_registry as er
2121
from homeassistant.helpers.aiohttp_client import async_get_clientsession
2222
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -59,7 +59,7 @@ async def _async_update_data(self) -> dict[str | int, UptimeKumaMonitor]:
5959
try:
6060
metrics = await self.api.metrics()
6161
except UptimeKumaAuthenticationException as e:
62-
raise ConfigEntryError(
62+
raise ConfigEntryAuthFailed(
6363
translation_domain=DOMAIN,
6464
translation_key="auth_failed_exception",
6565
) from e

homeassistant/components/uptime_kuma/quality_scale.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ rules:
3838
integration-owner: done
3939
log-when-unavailable: done
4040
parallel-updates: done
41-
reauthentication-flow: todo
41+
reauthentication-flow: done
4242
test-coverage: done
4343

4444
# Gold

homeassistant/components/uptime_kuma/strings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313
"verify_ssl": "Enable SSL certificate verification for secure connections. Disable only if connecting to an Uptime Kuma instance using a self-signed certificate or via IP address",
1414
"api_key": "Enter an API key. To create a new API key navigate to **Settings → API Keys** and select **Add API Key**"
1515
}
16+
},
17+
"reauth_confirm": {
18+
"title": "Re-authenticate with Uptime Kuma: {name}",
19+
"description": "The API key for **{name}** is invalid. To re-authenticate with Uptime Kuma provide a new API key below",
20+
"data": {
21+
"api_key": "[%key:common::config_flow::data::api_key%]"
22+
},
23+
"data_description": {
24+
"api_key": "[%key:component::uptime_kuma::config::step::user::data_description::api_key%]"
25+
}
1626
}
1727
},
1828
"error": {
@@ -21,7 +31,8 @@
2131
"unknown": "[%key:common::config_flow::error::unknown%]"
2232
},
2333
"abort": {
24-
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
34+
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
35+
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
2536
}
2637
},
2738
"entity": {

tests/components/uptime_kuma/test_config_flow.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,73 @@ async def test_form_already_configured(
120120

121121
assert result["type"] is FlowResultType.ABORT
122122
assert result["reason"] == "already_configured"
123+
124+
125+
@pytest.mark.usefixtures("mock_pythonkuma")
126+
async def test_flow_reauth(
127+
hass: HomeAssistant,
128+
config_entry: MockConfigEntry,
129+
) -> None:
130+
"""Test reauth flow."""
131+
config_entry.add_to_hass(hass)
132+
result = await config_entry.start_reauth_flow(hass)
133+
assert result["type"] is FlowResultType.FORM
134+
assert result["step_id"] == "reauth_confirm"
135+
136+
result = await hass.config_entries.flow.async_configure(
137+
result["flow_id"],
138+
{CONF_API_KEY: "newapikey"},
139+
)
140+
141+
assert result["type"] is FlowResultType.ABORT
142+
assert result["reason"] == "reauth_successful"
143+
assert config_entry.data[CONF_API_KEY] == "newapikey"
144+
145+
assert len(hass.config_entries.async_entries()) == 1
146+
147+
148+
@pytest.mark.parametrize(
149+
("raise_error", "text_error"),
150+
[
151+
(UptimeKumaConnectionException, "cannot_connect"),
152+
(UptimeKumaAuthenticationException, "invalid_auth"),
153+
(ValueError, "unknown"),
154+
],
155+
)
156+
@pytest.mark.usefixtures("mock_pythonkuma")
157+
async def test_flow_reauth_errors(
158+
hass: HomeAssistant,
159+
config_entry: MockConfigEntry,
160+
mock_pythonkuma: AsyncMock,
161+
raise_error: Exception,
162+
text_error: str,
163+
) -> None:
164+
"""Test reauth flow errors and recover."""
165+
config_entry.add_to_hass(hass)
166+
result = await config_entry.start_reauth_flow(hass)
167+
assert result["type"] is FlowResultType.FORM
168+
assert result["step_id"] == "reauth_confirm"
169+
170+
mock_pythonkuma.metrics.side_effect = raise_error
171+
172+
result = await hass.config_entries.flow.async_configure(
173+
result["flow_id"],
174+
{CONF_API_KEY: "newapikey"},
175+
)
176+
177+
assert result["type"] is FlowResultType.FORM
178+
assert result["errors"] == {"base": text_error}
179+
180+
mock_pythonkuma.metrics.side_effect = None
181+
182+
result = await hass.config_entries.flow.async_configure(
183+
result["flow_id"],
184+
{CONF_API_KEY: "newapikey"},
185+
)
186+
await hass.async_block_till_done()
187+
188+
assert result["type"] is FlowResultType.ABORT
189+
assert result["reason"] == "reauth_successful"
190+
assert config_entry.data[CONF_API_KEY] == "newapikey"
191+
192+
assert len(hass.config_entries.async_entries()) == 1

tests/components/uptime_kuma/test_init.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import pytest
66
from pythonkuma import UptimeKumaAuthenticationException, UptimeKumaException
77

8-
from homeassistant.config_entries import ConfigEntryState
8+
from homeassistant.components.uptime_kuma.const import DOMAIN
9+
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
910
from homeassistant.core import HomeAssistant
1011

1112
from tests.common import MockConfigEntry
@@ -50,3 +51,29 @@ async def test_config_entry_not_ready(
5051
await hass.async_block_till_done()
5152

5253
assert config_entry.state is state
54+
55+
56+
async def test_config_reauth_flow(
57+
hass: HomeAssistant,
58+
config_entry: MockConfigEntry,
59+
mock_pythonkuma: AsyncMock,
60+
) -> None:
61+
"""Test config entry auth error starts reauth flow."""
62+
63+
mock_pythonkuma.metrics.side_effect = UptimeKumaAuthenticationException
64+
config_entry.add_to_hass(hass)
65+
await hass.config_entries.async_setup(config_entry.entry_id)
66+
await hass.async_block_till_done()
67+
68+
assert config_entry.state is ConfigEntryState.SETUP_ERROR
69+
70+
flows = hass.config_entries.flow.async_progress()
71+
assert len(flows) == 1
72+
73+
flow = flows[0]
74+
assert flow.get("step_id") == "reauth_confirm"
75+
assert flow.get("handler") == DOMAIN
76+
77+
assert "context" in flow
78+
assert flow["context"].get("source") == SOURCE_REAUTH
79+
assert flow["context"].get("entry_id") == config_entry.entry_id

0 commit comments

Comments
 (0)