Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 21 additions & 4 deletions airos/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
AirOSDataMissingError,
AirOSDeviceConnectionError,
AirOSKeyDataMissingError,
AirOSUrlNotFoundError,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 and not self.connected:
_LOGGER.error("Not connected, login first")
raise AirOSDeviceConnectionError from None

Expand All @@ -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:
self._store_auth_data(response)
self.connected = True

Expand All @@ -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)
Expand All @@ -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

Expand Down
4 changes: 4 additions & 0 deletions airos/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "airos"
version = "0.5.3"
version = "0.5.4a1"
license = "MIT"
description = "Ubiquiti airOS module(s) for Python 3."
readme = "README.md"
Expand Down