# Сериализация попыток входа по IP (изменение 027) Находка № 7 из `docs/reviews/2026-09-26-codebase-review-2.md`, серьёзность — низкая. Выполняется вместе с 026 (тот же файл). ## Context Изменение 024 сериализует попытки одного логина (`pg_advisory_xact_lock(hashtext(логин))`). Параллельные попытки **разных** логинов с одного IP проходят проверку лимита по IP одновременно, и порог 20 можно превысить на степень параллелизма. ## Решение (`app/api/v1/auth.py`, `login`) 1. После блокировки по логину и до `_retry_after` — вторая блокировка `pg_advisory_xact_lock(hashtext('ip:' || ip))`, если `ip` известен. Порядок всегда «логин, затем IP»: IP-блокировка берётся последней, поэтому цикл ожидания невозможен. 2. Использовать двухаргументную форму advisory-lock с отдельными пространствами ключей, чтобы исключить коллизии между логинами и IP: `pg_advisory_xact_lock(, hashtext(name))` и `pg_advisory_xact_lock(, hashtext(ip))`; константы пространств — в `auth.py` (например, 7031 и 7032). 3. Комментарий в коде: цена — попытки с одного IP (в том числе за NAT) обрабатываются по одной, время ответа при массовом входе растёт на время argon2. ## Файлы `app/api/v1/auth.py`, `README.md` (одна фраза в абзаце «Вход»). ## Проверка 16 параллельных неверных входов на 16 разных несуществующих логинов с одного IP при заранее вставленных 15 неудачах этого IP → проверено не больше 5 паролей (в `login_attempts` добавилось ≤ 5 записей), остальные — 429.