diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b724b..f2df1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## [0.5.4] - 2025-10-01 + +### Added + +- Alternate/pre-decessing login url for v6 + +## [0.5.3] - 2025-09-26 + +### Changed + +- Improved unauthorized and forbidden handling + ## [0.5.1] - 2025-08-31 ### Changed diff --git a/airos/base.py b/airos/base.py index dabcdbe..c1ae6c4 100644 --- a/airos/base.py +++ b/airos/base.py @@ -26,6 +26,7 @@ AirOSDataMissingError, AirOSDeviceConnectionError, AirOSKeyDataMissingError, + AirOSUrlNotFoundError, ) _LOGGER = logging.getLogger(__name__) @@ -71,7 +72,10 @@ def __init__( self.current_csrf_token: str | None = None # Mostly 8.x API endpoints, login/status are the same in 6.x - self._login_url = f"{self.base_url}/api/auth" + self._login_urls = { + "default": f"{self.base_url}/api/auth", + "v6_alternative": f"{self.base_url}/login.cgi", + } self._status_cgi_url = f"{self.base_url}/status.cgi" # Presumed 8.x only endpoints self._stakick_cgi_url = f"{self.base_url}/stakick.cgi" @@ -216,7 +220,7 @@ async def _request_json( request_headers.update(headers) try: - if url != self._login_url and not self.connected: + if url not in self._login_urls.values() and not self.connected: _LOGGER.error("Not connected, login first") raise AirOSDeviceConnectionError from None @@ -232,7 +236,7 @@ async def _request_json( _LOGGER.debug("Successfully fetched JSON from %s", url) # If this is the login request, we need to store the new auth data - if url == self._login_url: + if url in self._login_urls.values(): self._store_auth_data(response) self.connected = True @@ -243,6 +247,8 @@ async def _request_json( ) if err.status in [401, 403]: raise AirOSConnectionAuthenticationError from err + if err.status in [404]: + raise AirOSUrlNotFoundError from err raise AirOSConnectionSetupError from err except (TimeoutError, aiohttp.ClientError) as err: _LOGGER.exception("Error during API call to %s", url) @@ -258,7 +264,18 @@ async def login(self) -> None: """Login to AirOS device.""" payload = {"username": self.username, "password": self.password} try: - await self._request_json("POST", self._login_url, json_data=payload) + await self._request_json( + "POST", self._login_urls["default"], json_data=payload + ) + except AirOSUrlNotFoundError: + try: + await self._request_json( + "POST", self._login_urls["v6_alternative"], json_data=payload + ) + except AirOSConnectionSetupError as err: + raise AirOSConnectionSetupError( + "Failed to login to default and alternate AirOS device urls" + ) from err except AirOSConnectionSetupError as err: raise AirOSConnectionSetupError("Failed to login to AirOS device") from err diff --git a/airos/exceptions.py b/airos/exceptions.py index f19e59d..339d6fd 100644 --- a/airos/exceptions.py +++ b/airos/exceptions.py @@ -39,3 +39,7 @@ class AirOSEndpointError(AirOSDiscoveryError): class AirOSNotSupportedError(AirOSException): """Raised when method not available for device.""" + + +class AirOSUrlNotFoundError(AirOSException): + """Raised when url not available for device.""" diff --git a/pyproject.toml b/pyproject.toml index 0985bce..281e7fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "airos" -version = "0.5.3" +version = "0.5.4" license = "MIT" description = "Ubiquiti airOS module(s) for Python 3." readme = "README.md"