diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 364f5242377..e0cc0eeb1ec 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -235,7 +235,7 @@ class LoginFlowBaseView(HomeAssistantView): f"Login blocked: {user_access_error}", HTTPStatus.FORBIDDEN ) - await process_success_login(request) + process_success_login(request) result["result"] = self._store_result(client_id, result_obj) return self.json(result) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 85feb19a24b..89d927ee8af 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -162,27 +162,28 @@ async def process_wrong_login(request: Request) -> None: ) -async def process_success_login(request: Request) -> None: +@callback +def process_success_login(request: Request) -> None: """Process a success login attempt. Reset failed login attempts counter for remote IP address. No release IP address from banned list function, it can only be done by manual modify ip bans config file. """ - remote_addr = ip_address(request.remote) # type: ignore[arg-type] - + app = request.app # Check if ban middleware is loaded - if KEY_BAN_MANAGER not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1: + if KEY_BAN_MANAGER not in app or app[KEY_LOGIN_THRESHOLD] < 1: return - if ( - remote_addr in request.app[KEY_FAILED_LOGIN_ATTEMPTS] - and request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > 0 - ): + remote_addr = ip_address(request.remote) # type: ignore[arg-type] + login_attempt_history: defaultdict[IPv4Address | IPv6Address, int] = app[ + KEY_FAILED_LOGIN_ATTEMPTS + ] + if remote_addr in login_attempt_history and login_attempt_history[remote_addr] > 0: _LOGGER.debug( "Login success, reset failed login attempts counter from %s", remote_addr ) - request.app[KEY_FAILED_LOGIN_ATTEMPTS].pop(remote_addr) + login_attempt_history.pop(remote_addr) class IpBan: diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py index 9f8e8bfb6f8..2c86a26efc9 100644 --- a/homeassistant/components/websocket_api/auth.py +++ b/homeassistant/components/websocket_api/auth.py @@ -103,7 +103,7 @@ class AuthPhase: ) -> ActiveConnection: """Create an active connection.""" self._logger.debug("Auth OK") - await process_success_login(self._request) + process_success_login(self._request) self._send_message(auth_ok_message()) return ActiveConnection( self._logger, self._hass, self._send_message, user, refresh_token diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index e6e237a7b67..8082a268a80 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -16,6 +16,7 @@ from homeassistant.components.http.ban import ( KEY_BAN_MANAGER, KEY_FAILED_LOGIN_ATTEMPTS, IpBanManager, + process_success_login, setup_bans, ) from homeassistant.components.http.view import request_handler_factory @@ -332,9 +333,14 @@ async def test_failed_login_attempts_counter( """Return 200 status code.""" return None, 200 + async def auth_true_handler(request): + """Return 200 status code.""" + process_success_login(request) + return None, 200 + app.router.add_get( "/auth_true", - request_handler_factory(hass, Mock(requires_auth=True), auth_handler), + request_handler_factory(hass, Mock(requires_auth=True), auth_true_handler), ) app.router.add_get( "/auth_false", @@ -377,4 +383,12 @@ async def test_failed_login_attempts_counter( # We no longer support trusted networks. resp = await client.get("/auth_true") assert resp.status == HTTPStatus.OK + assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 0 + + resp = await client.get("/auth_false") + assert resp.status == HTTPStatus.UNAUTHORIZED + assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 1 + + resp = await client.get("/auth_false") + assert resp.status == HTTPStatus.UNAUTHORIZED assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 2