diff --git a/src/ui_cli/commands/status.py b/src/ui_cli/commands/status.py index e7f6496..c36465b 100644 --- a/src/ui_cli/commands/status.py +++ b/src/ui_cli/commands/status.py @@ -153,7 +153,7 @@ async def check_local_controller(verbose: bool = False) -> dict: try: client = UniFiLocalClient(timeout=STATUS_CHECK_TIMEOUT) start = time.perf_counter() - await client.login() + await client.ensure_authenticated() elapsed_ms = (time.perf_counter() - start) * 1000 result["connection"] = "OK" diff --git a/src/ui_cli/config.py b/src/ui_cli/config.py index b22e122..0d2ac3b 100644 --- a/src/ui_cli/config.py +++ b/src/ui_cli/config.py @@ -51,6 +51,10 @@ class Settings(BaseSettings): default=False, description="Verify SSL certificates (disable for self-signed)", ) + controller_totp: str = Field( + default="", + description="TOTP code for MFA-enabled accounts", + ) @property def is_configured(self) -> bool: diff --git a/src/ui_cli/local_client.py b/src/ui_cli/local_client.py index 961e0d7..19eedc0 100644 --- a/src/ui_cli/local_client.py +++ b/src/ui_cli/local_client.py @@ -70,6 +70,7 @@ def __init__( self.password = password or settings.controller_password self.site = site or settings.controller_site self.verify_ssl = verify_ssl if verify_ssl is not None else settings.controller_verify_ssl + self.totp = settings.controller_totp # Timeout priority: explicit param > --quick flag > settings if timeout is not None: @@ -200,13 +201,16 @@ async def login(self) -> bool: try: # Try UDM-style auth first if self._is_udm: + login_data = { + "username": self.username, + "password": self.password, + "remember": True, + } + if self.totp: + login_data["token"] = self.totp response = await client.post( f"{self.controller_url}/api/auth/login", - json={ - "username": self.username, - "password": self.password, - "remember": True, - }, + json=login_data, ) if response.status_code == 200: @@ -214,6 +218,10 @@ async def login(self) -> bool: self._csrf_token = response.headers.get("X-CSRF-Token") self._save_session() return True + elif response.status_code == 499: + raise LocalAuthenticationError( + "MFA token required. Set UNIFI_CONTROLLER_TOTP to your current TOTP code." + ) elif response.status_code == 403: # 403 on UDM often means wrong credentials raise LocalAuthenticationError(