From de167192216a0597f7d72eac7c8edfd80bb7df74 Mon Sep 17 00:00:00 2001 From: Thijs Wiefferink Date: Wed, 17 Apr 2024 21:10:19 +0000 Subject: [PATCH] Add config login code step - Enter login code from the login email - User is logged in on the client_id - Crisp user id is saved in the config entry --- custom_components/crisp/api.py | 10 +- custom_components/crisp/config_flow.py | 106 ++++++++++++++----- custom_components/crisp/translations/en.json | 10 +- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/custom_components/crisp/api.py b/custom_components/crisp/api.py index 3271ec2..fb4be81 100644 --- a/custom_components/crisp/api.py +++ b/custom_components/crisp/api.py @@ -27,7 +27,6 @@ class CrispApiClientAuthenticationError(CrispApiClientError): # TODO: country enum? -# TODO: offer option to override base url? different per country class CrispApiClient: """Crisp API client.""" @@ -75,6 +74,15 @@ async def request_login_code(self, email: str, country: str) -> any: method="post", path="/user/login", data={"email": email, "country": country} ) + async def login(self, email: str, country: str, login_code: str) -> any: + """Login a user using the login code retreived from the email sent by request_login_code.""" + + return await self._api_wrapper( + method="post", + path="/user/login", + data={"email": email, "country": country, "code": login_code}, + ) + async def _api_wrapper( self, method: str, diff --git a/custom_components/crisp/config_flow.py b/custom_components/crisp/config_flow.py index 02e55f4..c57f7f4 100644 --- a/custom_components/crisp/config_flow.py +++ b/custom_components/crisp/config_flow.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_EMAIL, CONF_TOKEN +from homeassistant.const import CONF_EMAIL, CONF_TOKEN, CONF_CODE, CONF_COUNTRY_CODE from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_create_clientsession @@ -31,18 +31,29 @@ async def async_step_user( self, info: dict | None = None, ) -> config_entries.FlowResult: - """Handle a flow initialized by the user: initial setup.""" - errors = {} - # TODO: validate email? is there some function for that? + """Handle a flow initialized by the user: enter email of Crisp user.""" + errors: dict[str, str] = {} if info is not None: try: - info[CONF_TOKEN] = CrispApiClient.generate_client_id() + # Generate client_id + client_id = CrispApiClient.generate_client_id() + info[CONF_TOKEN] = client_id + + # TODO: ask country from the user + country = "nl" + info[CONF_COUNTRY_CODE] = country + + self.crisp_client = CrispApiClient( + session=async_create_clientsession(self.hass), + client_id=client_id, + ) # Request a login code for the user belonging to the email - response = await self.request_login_email( - client_id=info[CONF_TOKEN], email=info[CONF_EMAIL] + response = await self.crisp_client.request_login_code( + email=info[CONF_EMAIL], country=country ) + _LOGGER.debug("request_login_code response: ", response) except CrispApiClientAuthenticationError as exception: _LOGGER.warning(exception) errors["base"] = "auth" @@ -60,17 +71,11 @@ async def async_step_user( # TODO: finish the flow without requesting login, use user id directly errors["base"] = "Already logged in" else: - # TODO: show login code form - # Set unique id of this config flow to the Crisp user id - # await self.async_set_unique_id(user_id) - # Ensure config flow can only be done once for this email - # self._abort_if_unique_id_configured() + # Save the email/client_id + self.user_info = info - # All good, create config entry - return self.async_create_entry( - title=info[CONF_EMAIL], - data=info, - ) + # Show the login code step + return await self.async_step_login() # Show errors in the form return self.async_show_form( @@ -88,16 +93,65 @@ async def async_step_user( } ), errors=errors, + last_step=False, ) - async def request_login_email(self, client_id: str, email: str) -> any: - """Request a login code for the Crisp user that belongs to the given email address.""" + async def async_step_login(self, info: dict | None = None): + """Second step of the setup; enter login code for the Crisp user.""" + + errors: dict[str, str] = {} + if info is not None: + try: + # Try to login using the provided code + response = await self.crisp_client.login( + email=self.user_info[CONF_EMAIL], + country=self.user_info[CONF_COUNTRY_CODE], + login_code=info[CONF_CODE], + ) + _LOGGER.debug("login response: ", response) + except CrispApiClientAuthenticationError as exception: + _LOGGER.warning(exception) + errors["base"] = "auth" + except CrispApiClientCommunicationError as exception: + _LOGGER.error(exception) + errors["base"] = "connection" + except CrispApiClientError as exception: + _LOGGER.exception(exception) + errors["base"] = "unknown" + else: + if "error" in response: + # These errors are not super human-readable, could attempt to map some of them to better text + errors[CONF_CODE] = response.error + elif "id" not in response: + # Did not get a user id back as confirmation, something went wrong + errors["base"] = "Unknown error, could not login" + else: + self.user_info["user_id"] = user_id = response["id"] + + # Set unique id of this config flow to the Crisp user id (more stable than email, which can be changed) + await self.async_set_unique_id(user_id) + # Ensure config flow can only be done once for this Crisp user id + self._abort_if_unique_id_configured() + + # All good, create config entry + return self.async_create_entry( + title=self.user_info[CONF_EMAIL], + data=self.user_info, + ) - # TODO: ask for country code from the user - client = CrispApiClient( - session=async_create_clientsession(self.hass), - client_id=client_id, + return self.async_show_form( + step_id="login", + data_schema=vol.Schema( + { + vol.Required( + CONF_CODE, + default=(info or {}).get(CONF_CODE), + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + } + ), + errors=errors, ) - response = await client.request_login_code(email=email, country="nl") - _LOGGER.debug(response) - return response diff --git a/custom_components/crisp/translations/en.json b/custom_components/crisp/translations/en.json index 999050b..766a29a 100644 --- a/custom_components/crisp/translations/en.json +++ b/custom_components/crisp/translations/en.json @@ -6,13 +6,19 @@ "data": { "email": "Email" } + }, + "login": { + "description": "Enter the login code you received from Crisp on your email address.", + "data": { + "code": "Login code" + } } }, "abort": { - "already_configured": "Crisp account with that email is already configured." + "already_configured": "That Crisp user is already configured." }, "error": { - "auth": "Username/Password is wrong.", + "auth": "Email is wrong.", "connection": "Unable to connect to the server.", "unknown": "Unknown error occurred." }