"""SAC UI login rate limit and Telegram alert on brute-force.""" def _failed_login(client, username: str = "test-admin") -> int: return client.post( "/api/v1/auth/login", json={"username": username, "password": "wrong-password"}, ).status_code def test_login_blocked_after_max_failures(client, monkeypatch): monkeypatch.setenv("SAC_LOGIN_MAX_FAILURES", "3") from app.config import get_settings get_settings.cache_clear() assert _failed_login(client) == 401 assert _failed_login(client) == 401 assert _failed_login(client) == 401 assert _failed_login(client) == 429 ok = client.post( "/api/v1/auth/login", json={"username": "test-admin", "password": "test-admin-password"}, ) assert ok.status_code == 429 get_settings.cache_clear() def test_login_telegram_alert_on_threshold(client, monkeypatch): monkeypatch.setenv("SAC_LOGIN_MAX_FAILURES", "3") monkeypatch.setenv("SAC_LOGIN_ALERT_TELEGRAM", "true") from app.config import get_settings get_settings.cache_clear() sent: list[str] = [] monkeypatch.setattr( "app.services.login_rate_limit.send_telegram_text", lambda text, **kwargs: sent.append(text), ) for _ in range(3): assert _failed_login(client, username="attacker") == 401 assert len(sent) == 1 assert "SAC: подбор пароля UI" in sent[0] assert "attacker" in sent[0] assert _failed_login(client, username="attacker") == 429 assert len(sent) == 1 get_settings.cache_clear() def test_deactivated_user_token_rejected_on_me(client, jwt_monitor_headers, db_session): from app.models.user import User user = db_session.query(User).filter(User.username == "test-monitor").one() user.is_active = False db_session.commit() response = client.get("/api/v1/auth/me", headers=jwt_monitor_headers) assert response.status_code == 401