Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 66 additions & 13 deletions custom_components/fitdays/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,26 @@

try:
from fitdays import FitdaysClient
from fitdays.exceptions import FitdaysAuthError, FitdaysError, FitdaysNetworkError
from fitdays.exceptions import (
FitdaysAuthError,
FitdaysError,
FitdaysNetworkError,
FitdaysValidationError,
)
except ImportError as err: # pragma: no cover - handled by manifest requirements
_LOGGER.error("Failed to import the fitdays package: %s", err)
raise

STEP_USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_EMAIL): str,
vol.Required(CONF_PASSWORD): _SECRET,
vol.Optional(CONF_COUNTRY, default=DEFAULT_COUNTRY): str,
}
)

def _credentials_schema(email: str = "", country: str = DEFAULT_COUNTRY) -> vol.Schema:
"""Build the email/password/country form, prefilled with what we know."""
return vol.Schema(
{
vol.Required(CONF_EMAIL, default=email): str,
vol.Required(CONF_PASSWORD): _SECRET,
vol.Optional(CONF_COUNTRY, default=country or DEFAULT_COUNTRY): str,
}
)


class FitdaysConfigFlow(ConfigFlow, domain=DOMAIN):
Expand Down Expand Up @@ -68,6 +76,10 @@ async def _async_try_login(
return None, "invalid_auth"
except FitdaysNetworkError:
return None, "cannot_connect"
except FitdaysValidationError:
# The client rejects blank input before it calls the cloud. That is
# a form problem, not an outage, so say so instead of "unknown".
return None, "invalid_auth"
except FitdaysError as err:
_LOGGER.error("Unexpected Fitdays error during login: %s", err)
return None, "unknown"
Expand Down Expand Up @@ -95,7 +107,7 @@ async def async_step_user(
return self.async_create_entry(title=email, data=entry_data)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_SCHEMA, errors=errors
step_id="user", data_schema=_credentials_schema(), errors=errors
)

async def async_step_reauth(
Expand All @@ -108,16 +120,17 @@ async def async_step_reauth(
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for the password again and refresh the stored session."""
"""Ask for the credentials again and refresh the stored session."""
errors: dict[str, str] = {}
existing = self._reauth_entry_data or {}
email = existing.get(CONF_EMAIL) or ""

if user_input is not None:
email = user_input[CONF_EMAIL].strip()
new_data, error = await self._async_try_login(
email,
user_input[CONF_PASSWORD],
existing.get(CONF_COUNTRY) or DEFAULT_COUNTRY,
user_input.get(CONF_COUNTRY) or DEFAULT_COUNTRY,
)
if error:
errors["base"] = error
Expand All @@ -127,9 +140,49 @@ async def async_step_reauth_confirm(
data={**existing, **new_data},
)

# The email is asked for rather than taken from the entry, because an
# entry written before the email was stored has none, and a login with
# a blank email fails in the client before it reaches the cloud.
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_PASSWORD): _SECRET}),
description_placeholders={"email": email},
data_schema=_credentials_schema(
email, existing.get(CONF_COUNTRY) or DEFAULT_COUNTRY
),
description_placeholders={"email": email or "this account"},
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Let the user point the entry at fresh credentials."""
errors: dict[str, str] = {}
entry = self._get_reconfigure_entry()
email = entry.data.get(CONF_EMAIL) or ""
country = entry.data.get(CONF_COUNTRY) or DEFAULT_COUNTRY

if user_input is not None:
email = user_input[CONF_EMAIL].strip()
country = user_input.get(CONF_COUNTRY) or DEFAULT_COUNTRY
new_data, error = await self._async_try_login(
email, user_input[CONF_PASSWORD], country
)
if error:
errors["base"] = error
elif str(new_data.get("uid")) != str(entry.unique_id):
# Reconfigure repairs one account's entry. A different account
# belongs in its own entry, or its sensors would silently
# change meaning.
return self.async_abort(reason="account_mismatch")
else:
return self.async_update_reload_and_abort(
entry,
data={**entry.data, **new_data},
title=email,
)

return self.async_show_form(
step_id="reconfigure",
data_schema=_credentials_schema(email, country),
errors=errors,
)
25 changes: 22 additions & 3 deletions custom_components/fitdays/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,26 @@
},
"reauth_confirm": {
"title": "Re-authenticate Fitdays",
"description": "The stored session for {email} is no longer valid. Enter the password again to reconnect.",
"description": "The stored session for {email} is no longer valid. Enter the credentials again to reconnect.",
"data": {
"password": "Password"
"email": "Email",
"password": "Password",
"country": "Country code"
},
"data_description": {
"country": "Two-letter code of the country your Fitdays account is registered in, for example NL."
}
},
"reconfigure": {
"title": "Reconfigure Fitdays",
"description": "Sign in again to replace the stored session. The credentials must belong to the same Fitdays account as this entry.",
"data": {
"email": "Email",
"password": "Password",
"country": "Country code"
},
"data_description": {
"country": "Two-letter code of the country your Fitdays account is registered in, for example NL."
}
}
},
Expand All @@ -28,7 +45,9 @@
},
"abort": {
"already_configured": "This Fitdays account is already set up.",
"reauth_successful": "Re-authentication was successful."
"reauth_successful": "Re-authentication was successful.",
"reconfigure_successful": "Fitdays was reconfigured.",
"account_mismatch": "Those credentials are for a different Fitdays account. Add that account as a new entry instead."
}
},
"entity": {
Expand Down
25 changes: 22 additions & 3 deletions custom_components/fitdays/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,26 @@
},
"reauth_confirm": {
"title": "Re-authenticate Fitdays",
"description": "The stored session for {email} is no longer valid. Enter the password again to reconnect.",
"description": "The stored session for {email} is no longer valid. Enter the credentials again to reconnect.",
"data": {
"password": "Password"
"email": "Email",
"password": "Password",
"country": "Country code"
},
"data_description": {
"country": "Two-letter code of the country your Fitdays account is registered in, for example NL."
}
},
"reconfigure": {
"title": "Reconfigure Fitdays",
"description": "Sign in again to replace the stored session. The credentials must belong to the same Fitdays account as this entry.",
"data": {
"email": "Email",
"password": "Password",
"country": "Country code"
},
"data_description": {
"country": "Two-letter code of the country your Fitdays account is registered in, for example NL."
}
}
},
Expand All @@ -28,7 +45,9 @@
},
"abort": {
"already_configured": "This Fitdays account is already set up.",
"reauth_successful": "Re-authentication was successful."
"reauth_successful": "Re-authentication was successful.",
"reconfigure_successful": "Fitdays was reconfigured.",
"account_mismatch": "Those credentials are for a different Fitdays account. Add that account as a new entry instead."
}
},
"entity": {
Expand Down
25 changes: 22 additions & 3 deletions custom_components/fitdays/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,26 @@
},
"reauth_confirm": {
"title": "Fitdays opnieuw verbinden",
"description": "De opgeslagen sessie voor {email} is niet meer geldig. Voer het wachtwoord opnieuw in om te herverbinden.",
"description": "De opgeslagen sessie voor {email} is niet meer geldig. Voer de inloggegevens opnieuw in om te herverbinden.",
"data": {
"password": "Wachtwoord"
"email": "E-mailadres",
"password": "Wachtwoord",
"country": "Landcode"
},
"data_description": {
"country": "Tweeletterige code van het land waar je Fitdays-account geregistreerd is, bijvoorbeeld NL."
}
},
"reconfigure": {
"title": "Fitdays opnieuw instellen",
"description": "Log opnieuw in om de opgeslagen sessie te vervangen. De inloggegevens moeten bij hetzelfde Fitdays-account horen als deze invoer.",
"data": {
"email": "E-mailadres",
"password": "Wachtwoord",
"country": "Landcode"
},
"data_description": {
"country": "Tweeletterige code van het land waar je Fitdays-account geregistreerd is, bijvoorbeeld NL."
}
}
},
Expand All @@ -28,7 +45,9 @@
},
"abort": {
"already_configured": "Dit Fitdays-account is al ingesteld.",
"reauth_successful": "Opnieuw verbinden is gelukt."
"reauth_successful": "Opnieuw verbinden is gelukt.",
"reconfigure_successful": "Fitdays is opnieuw ingesteld.",
"account_mismatch": "Deze inloggegevens horen bij een ander Fitdays-account. Voeg dat account toe als een nieuwe invoer."
}
},
"entity": {
Expand Down