Add reauthentication flow to Uptime Kuma (#148772)

This commit is contained in:
Manu 2025-07-15 16:49:08 +02:00 committed by GitHub
parent 087a938a7d
commit fd10fa1fba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 160 additions and 5 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
@ -38,6 +39,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
vol.Optional(CONF_API_KEY, default=""): str,
}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_API_KEY, default=""): str})
class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN):
@ -77,3 +79,48 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN):
),
errors=errors,
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reauthentication dialog."""
errors: dict[str, str] = {}
entry = self._get_reauth_entry()
if user_input is not None:
session = async_get_clientsession(self.hass, entry.data[CONF_VERIFY_SSL])
uptime_kuma = UptimeKuma(
session,
entry.data[CONF_URL],
user_input[CONF_API_KEY],
)
try:
await uptime_kuma.metrics()
except UptimeKumaAuthenticationException:
errors["base"] = "invalid_auth"
except UptimeKumaException:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_update_reload_and_abort(
entry,
data_updates=user_input,
)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=self.add_suggested_values_to_schema(
data_schema=STEP_REAUTH_DATA_SCHEMA, suggested_values=user_input
),
errors=errors,
)

View File

@ -16,7 +16,7 @@ from pythonkuma import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryError
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -59,7 +59,7 @@ class UptimeKumaDataUpdateCoordinator(
try:
metrics = await self.api.metrics()
except UptimeKumaAuthenticationException as e:
raise ConfigEntryError(
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="auth_failed_exception",
) from e

View File

@ -38,7 +38,7 @@ rules:
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
reauthentication-flow: done
test-coverage: done
# Gold

View File

@ -13,6 +13,16 @@
"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",
"api_key": "Enter an API key. To create a new API key navigate to **Settings → API Keys** and select **Add API Key**"
}
},
"reauth_confirm": {
"title": "Re-authenticate with Uptime Kuma: {name}",
"description": "The API key for **{name}** is invalid. To re-authenticate with Uptime Kuma provide a new API key below",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"api_key": "[%key:component::uptime_kuma::config::step::user::data_description::api_key%]"
}
}
},
"error": {
@ -21,7 +31,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"entity": {

View File

@ -120,3 +120,73 @@ async def test_form_already_configured(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("mock_pythonkuma")
async def test_flow_reauth(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> None:
"""Test reauth flow."""
config_entry.add_to_hass(hass)
result = await config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "newapikey"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert config_entry.data[CONF_API_KEY] == "newapikey"
assert len(hass.config_entries.async_entries()) == 1
@pytest.mark.parametrize(
("raise_error", "text_error"),
[
(UptimeKumaConnectionException, "cannot_connect"),
(UptimeKumaAuthenticationException, "invalid_auth"),
(ValueError, "unknown"),
],
)
@pytest.mark.usefixtures("mock_pythonkuma")
async def test_flow_reauth_errors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pythonkuma: AsyncMock,
raise_error: Exception,
text_error: str,
) -> None:
"""Test reauth flow errors and recover."""
config_entry.add_to_hass(hass)
result = await config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
mock_pythonkuma.metrics.side_effect = raise_error
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "newapikey"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": text_error}
mock_pythonkuma.metrics.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "newapikey"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert config_entry.data[CONF_API_KEY] == "newapikey"
assert len(hass.config_entries.async_entries()) == 1

View File

@ -5,7 +5,8 @@ from unittest.mock import AsyncMock
import pytest
from pythonkuma import UptimeKumaAuthenticationException, UptimeKumaException
from homeassistant.config_entries import ConfigEntryState
from homeassistant.components.uptime_kuma.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@ -50,3 +51,29 @@ async def test_config_entry_not_ready(
await hass.async_block_till_done()
assert config_entry.state is state
async def test_config_reauth_flow(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pythonkuma: AsyncMock,
) -> None:
"""Test config entry auth error starts reauth flow."""
mock_pythonkuma.metrics.side_effect = UptimeKumaAuthenticationException
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
flow = flows[0]
assert flow.get("step_id") == "reauth_confirm"
assert flow.get("handler") == DOMAIN
assert "context" in flow
assert flow["context"].get("source") == SOURCE_REAUTH
assert flow["context"].get("entry_id") == config_entry.entry_id