Skip to content

Commit

Permalink
Clear auth on connection errors to handle deco reboot (#142)
Browse files Browse the repository at this point in the history
- Have addition logic calls wait on in-flight call
  • Loading branch information
amosyuen committed Feb 4, 2023
1 parent 17edd9c commit 7347a91
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 3 deletions.
27 changes: 27 additions & 0 deletions custom_components/tplink_deco/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def __init__(
self._sign_rsa_n = None
self._sign_rsa_e = None

self._login_future = None
self._seq = None
self._stok = None
self._cookie = None
Expand Down Expand Up @@ -291,6 +292,19 @@ async def async_login_if_needed(self):
return await self.async_login()

async def async_login(self):
if self._login_future is not None:
return await self._login_future

self._login_future = asyncio.get_running_loop().create_future()
try:
await self.async_login_internal()
self._login_future.set_result(True)
except Exception as err:
self._login_future.set_exception(err)
finally:
self._login_future = None

async def async_login_internal(self):
if self._aes_key is None:
self.generate_aes_key_and_iv()
if self._password_rsa_n is None:
Expand Down Expand Up @@ -318,6 +332,7 @@ async def async_login(self):
error_code = data.get("error_code")
result = data.get("result")
if error_code == -5002:
self.clear_auth()
attempts = result.get("attemptsAllowed", "unknown")
raise ConfigEntryAuthFailed(
f"Invalid login credentials. {attempts} attempts remaining."
Expand Down Expand Up @@ -400,6 +415,15 @@ async def _async_post(
)
raise ConfigEntryAuthFailed(message) from err
raise err
except (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError) as err:
# Clear auth in case deco rebooted and auth is invalid
self.clear_auth()
_LOGGER.error(
"%s connection error: %s",
context,
err,
)
raise err
except aiohttp.ClientError as err:
_LOGGER.error(
"%s client error: %s",
Expand All @@ -417,6 +441,7 @@ def _encode_payload(self, payload: Any):

def _encode_sign(self, data_len: int):
if self._seq is None:
self.clear_auth()
message = "_seq is None. Likely caused by logging in with admin account on another device. See https://github.com/amosyuen/ha-tplink-deco#login-credentials."
raise ConfigEntryAuthFailed(message)
seq_with_data_len = self._seq + data_len
Expand All @@ -439,12 +464,14 @@ def _encode_data(self, payload: Any):
return data

def clear_auth(self):
_LOGGER.debug("clear_auth")
self._seq = None
self._stok = None
self._cookie = None

def _decrypt_data(self, context: str, data: str):
if data == "":
self.clear_auth()
message = "Data empty. Likely caused by logging in with admin account on another device. See https://github.com/amosyuen/ha-tplink-deco#login-credentials."
raise ConfigEntryAuthFailed(message)

Expand Down
7 changes: 4 additions & 3 deletions custom_components/tplink_deco/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ def snake_case_to_title_space(str):
async def async_call_with_retry(api, func, *args):
try:
return await func(*args)
except ConfigEntryAuthFailed:
api.clear_auth()
except ConfigEntryAuthFailed as err:
_LOGGER.debug("Retrying auth error %s", err)
# Retry for auth exception in case is a token expired case
return await func(*args)
except asyncio.TimeoutError:
except asyncio.TimeoutError as err:
_LOGGER.debug("Retrying timeout error %s", err)
# Retry for timeouts
return await func(*args)

Expand Down

0 comments on commit 7347a91

Please sign in to comment.