feat(config-flow): mask secret fields with password selectors (Q7)

API_KEY, WINDY_STATION_PWD and POCASI_CZ_API_KEY now use a password TextSelector so
the values are masked in the UI. Field keys are unchanged, so stored options and the
flow contract are unaffected.

308 passing, 100% coverage; ruff + basedpyright clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ecowitt_support
SchiZzA 2026-06-21 19:10:31 +02:00
parent 242f2ee1b7
commit b2fbc33821
No known key found for this signature in database
1 changed files with 8 additions and 4 deletions

View File

@ -11,6 +11,7 @@ from yarl import URL
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult, OptionsFlow from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector
from homeassistant.helpers.network import get_url from homeassistant.helpers.network import get_url
from .const import ( from .const import (
@ -37,6 +38,9 @@ from .const import (
WSLINK_ADDON_PORT, WSLINK_ADDON_PORT,
) )
# Masked text input for secret fields (API keys / station passwords).
_PASSWORD_SELECTOR = selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD))
class CannotConnect(HomeAssistantError): class CannotConnect(HomeAssistantError):
"""We can not connect. - not used in push mechanism.""" """We can not connect. - not used in push mechanism."""
@ -80,7 +84,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
self.user_data_schema = { self.user_data_schema = {
vol.Optional(API_ID, default=self.user_data.get(API_ID, "")): str, vol.Optional(API_ID, default=self.user_data.get(API_ID, "")): str,
vol.Optional(API_KEY, default=self.user_data.get(API_KEY, "")): str, vol.Optional(API_KEY, default=self.user_data.get(API_KEY, "")): _PASSWORD_SELECTOR,
vol.Optional(WSLINK, default=self.user_data.get(WSLINK, False)): bool, vol.Optional(WSLINK, default=self.user_data.get(WSLINK, False)): bool,
vol.Optional(DEV_DBG, default=self.user_data.get(DEV_DBG, False)): bool, vol.Optional(DEV_DBG, default=self.user_data.get(DEV_DBG, False)): bool,
vol.Optional(LEGACY_ENABLED, default=self.user_data.get(LEGACY_ENABLED, True)): bool, vol.Optional(LEGACY_ENABLED, default=self.user_data.get(LEGACY_ENABLED, True)): bool,
@ -104,7 +108,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
vol.Optional( vol.Optional(
WINDY_STATION_PW, WINDY_STATION_PW,
default=self.windy_data.get(WINDY_STATION_PW, ""), default=self.windy_data.get(WINDY_STATION_PW, ""),
): str, ): _PASSWORD_SELECTOR,
vol.Optional(WINDY_ENABLED, default=self.windy_data[WINDY_ENABLED]): bool, vol.Optional(WINDY_ENABLED, default=self.windy_data[WINDY_ENABLED]): bool,
vol.Optional( vol.Optional(
WINDY_LOGGER_ENABLED, WINDY_LOGGER_ENABLED,
@ -122,7 +126,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
self.pocasi_cz_schema = { self.pocasi_cz_schema = {
vol.Required(POCASI_CZ_API_ID, default=self.pocasi_cz.get(POCASI_CZ_API_ID)): str, vol.Required(POCASI_CZ_API_ID, default=self.pocasi_cz.get(POCASI_CZ_API_ID)): str,
vol.Required(POCASI_CZ_API_KEY, default=self.pocasi_cz.get(POCASI_CZ_API_KEY)): str, vol.Required(POCASI_CZ_API_KEY, default=self.pocasi_cz.get(POCASI_CZ_API_KEY)): _PASSWORD_SELECTOR,
vol.Required( vol.Required(
POCASI_CZ_SEND_INTERVAL, POCASI_CZ_SEND_INTERVAL,
default=self.pocasi_cz.get(POCASI_CZ_SEND_INTERVAL), default=self.pocasi_cz.get(POCASI_CZ_SEND_INTERVAL),
@ -329,7 +333,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
pws_schema = { pws_schema = {
vol.Required(API_ID): str, vol.Required(API_ID): str,
vol.Required(API_KEY): str, vol.Required(API_KEY): _PASSWORD_SELECTOR,
vol.Optional(WSLINK): bool, vol.Optional(WSLINK): bool,
vol.Optional(DEV_DBG): bool, vol.Optional(DEV_DBG): bool,
} }