From 8958a657cb50f79a2d15c3ffdf3ba61ba2b5c704 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 6 Aug 2026 17:46:43 +0500 Subject: [PATCH] =?UTF-8?q?fix(tradein/auth):=20=D0=B0=D0=B3=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=20=D0=BF=D0=B8=D1=88=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BD=D0=B0=20ERROR=20=E2=80=94=20=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D1=87=D0=B5=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B5=D1=82=20(#2715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Бэкенд поднят с LoggingIntegration(level=INFO, event_level=ERROR) (app/main.py): событием GlitchTip запись становится ровно с ERROR, а WARNING остаётся строкой в docker-логе, которая умирает с ротацией и редеплоем — то есть ровно тем следом, на бесполезность которого жалуется пункт 1 issue. Прецедент цены известен (#2674): монитор писал WARNING про протухшие куки, событий было ноль. Спама не будет: запись не чаще раза в окно (1с), все группируются в один issue. Получателей у проекта по-прежнему нет (#2673) — событие будет видно в интерфейсе и никому не уйдёт; это сказано в docstring, а не подразумевается. Уровень закреплён тестом: понижение до WARNING выключило бы канал молча. Refs #2715 --- tradein-mvp/backend/app/api/v1/auth.py | 16 ++++++++++++---- tradein-mvp/backend/tests/test_auth_api.py | 4 ++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tradein-mvp/backend/app/api/v1/auth.py b/tradein-mvp/backend/app/api/v1/auth.py index b4180e8f..cedc699d 100644 --- a/tradein-mvp/backend/app/api/v1/auth.py +++ b/tradein-mvp/backend/app/api/v1/auth.py @@ -219,10 +219,18 @@ def _saturated_429(ip: str) -> HTTPException: дублируется. Первый отказ отчитывается сразу, а не в конце окна: одиночная аномалия обязана быть видна, даже если продолжения не будет. + Уровень ERROR, а не WARNING, — не косметика: бэкенд поднят с + `LoggingIntegration(level=INFO, event_level=ERROR)` (app/main.py), то есть + ровно с ERROR запись становится событием GlitchTip, а WARNING остаётся + строкой в docker-логе, которая умирает с ротацией и редеплоем. Цена + прецедента известна (#2674): монитор писал WARNING про протухшие куки — и + событий было ноль. Спама не будет: запись не чаще раза в окно, и все они + группируются в один issue (шаблон сообщения один). + Чего это НЕ делает: у GlitchTip-проекта нет ни правил, ни получателей - (#2673), так что уведомление никому не уйдёт — след появляется в аудите и - в логе, а не в чьём-то телефоне. Проверить доставку поведенчески сейчас - нельзя, и утверждать её здесь было бы враньём. + (#2673), так что уведомление никому не уйдёт — событие будет видно в + интерфейсе, но не в чьём-то телефоне. Проверить доставку поведенчески + сейчас не на чем, и утверждать её здесь было бы враньём. `username=""` — не заглушка: имя не пишем ПОТОМУ, что отказ случился до того, как мы на него посмотрели. Записывай мы присланное, атакующий @@ -237,7 +245,7 @@ def _saturated_429(ip: str) -> HTTPException: if now - _saturation_reported_at >= _SATURATION_REPORT_WINDOW_S: rejected, _saturation_rejected = _saturation_rejected, 0 _saturation_reported_at = now - logger.warning( + logger.error( "login rejected: password verify saturated — %d отказов с прошлой записи " "(не чаще раза в %.0fс), последний ip=%s", rejected, diff --git a/tradein-mvp/backend/tests/test_auth_api.py b/tradein-mvp/backend/tests/test_auth_api.py index 8c802b87..3ff4e00a 100644 --- a/tradein-mvp/backend/tests/test_auth_api.py +++ b/tradein-mvp/backend/tests/test_auth_api.py @@ -1057,6 +1057,10 @@ def test_saturation_is_reported_once_per_window_and_lands_in_audit( lines = [r for r in caplog.records if "saturated" in r.getMessage()] assert len(lines) == 1, f"20 отказов дали {len(lines)} строк в логе — агрегации нет" + # ERROR, а не WARNING: бэкенд поднят с LoggingIntegration(event_level=ERROR) + # (app/main.py), и только с ERROR запись становится событием GlitchTip. + # Понижение уровня выключило бы канал молча — прецедент #2674. + assert lines[0].levelno == logging.ERROR saturated = [e for e in events if e["event_type"] == "login_verify_saturated"] assert len(saturated) == 1, saturated