21 lines
2.2 KiB
Markdown
21 lines
2.2 KiB
Markdown
# Сериализация попыток входа по 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(<LOGIN_NS>, hashtext(name))` и `pg_advisory_xact_lock(<IP_NS>, 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.
|