gendesign/tradein-mvp/backend/app/core
bot-backend 1610c01c6d
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m18s
fix(tradein/auth): отказ по насыщению — до выборки из БД и с агрегированным следом (#2715)
Два пункта хвоста #2712/#2717.

1. След инцидента. Отказ при насыщении сознательно не пишет login_failed и не
   тратит бюджет неудач по имени (иначе насыщением блокируют чужую учётку) —
   значит инцидент был виден только строкой logger.warning НА КАЖДЫЙ отказ.
   Лог у бэкенда общий и ограниченный (docker json-file, 20m × 3): при флуде в
   сотни запросов в секунду 60 МБ прокручиваются за минуты и выселяют все
   прочие логи ровно во время атаки. Теперь на окно (1с) — одна строка и одно
   событие login_verify_saturated в user_events, оба с числом отказов,
   накопленных с прошлой записи. Событие важнее строки: аудит переживает и
   ротацию логов, и редеплой. Имя в событии пустое намеренно — отказ случился
   до того, как мы на имя посмотрели, а запись присланного дала бы атакующему
   строки аудита с любым именем на выбор.

2. Гейт насыщения переехал ПЕРЕД выборкой пользователя. Раньше заведомо
   отклоняемый запрос всё равно брал соединение из пула и делал SELECT по имени
   — и это была единственная работа на пути отказа, чьё время зависит от
   существования учётки (bcrypt, который эту разницу ровняет, до отказанного
   запроса не доходит). Добавлен verify_slots_saturated(key) — тот же предикат,
   что решает отказ, но без взятия слота; verify_password_bounded зовёт его же,
   так что двум условиям разъехаться нечем и инвариант «одна точка выноса =
   одна точка учёта» цел. Предчек учитывает и общий потолок, и долю на ключ
   (#2714): при флуде с одного адреса первой упирается именно доля.

Замер (20 заведомо отклоняемых запросов): до — 20 выборок из БД, 20 строк лога;
после — 0 выборок, 1 строка. Все три ветки проверены мутацией кода: тесты
краснеют.

Refs #2715
2026-08-06 17:36:22 +05:00
..
__init__.py feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
auth.py feat(tradein/v2): реальный display-профиль в TopNav через /me (#2046) 2026-07-03 22:55:12 +03:00
auth_db.py feat(tradein): DSN к БД auth собирается из AUTH_DB_PASSWORD, а не дублирует секрет 2026-08-02 12:05:41 +03:00
config.py fix(tradein/auth): bcrypt вне событийного цикла + настоящий потолок темпа логинов (#2665) (#2712) 2026-08-06 09:02:10 +00:00
db.py feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
fdw.py chore(tradein/geocoder): удалить Яндекс-геокодер (#2593) 2026-07-31 21:22:09 +03:00
password.py fix(tradein/auth): отказ по насыщению — до выборки из БД и с агрегированным следом (#2715) 2026-08-06 17:36:22 +05:00
ratelimit.py feat(tradein/auth): глобальный потолок попыток входа на имя пользователя (#2571) 2026-08-05 22:38:23 +05:00
rbac.py fix(tradein/auth): убрать устаревшее «Caddy basic_auth required» из 401 (#2573) (#2649) 2026-08-05 08:45:57 +00:00
request_audit.py fix(tradein/security): утечка ключа прокси, аудит действий админа, отличимость неудачного входа, IDOR в заявке (#2536) 2026-07-26 22:42:15 +00:00
shutdown.py feat(tradein/scraper): cooperative SIGTERM-drain for graceful deploy (#1182 Phase 2) 2026-06-28 19:52:09 +03:00