Files
ayurishchevandClaude Opus 5.5 03d727e496 Задачи 025-030: ёмкость префиксов, политика входа, дерево префиксов
Повторный анализ кодовой базы (docs/reviews/2026-09-26-codebase-review-2.md) и доработки:
025 Ёмкость префикса — размер его подсети (а не сумма листьев); «Обзор» считает ёмкость
    по корневым активным IPv4-префиксам и адреса внутри них.
026 Политика блокировки входа: 5 неудач на логин+IP, 20 на IP, 50 на логин со всех IP,
    кроме известных IP (known_logins, миграция 0009) — владельца нельзя заблокировать анонимно.
027 Сериализация попыток входа по IP (advisory-lock после блокировки логина).
028 UI «Префиксы»: загрузка всех страниц (до 20 000), счётчики по total, предупреждение об усечении.
029 Advisory-lock по VRF для операций, меняющих дерево префиксов и раскладку адресов.
030 Исправление замечаний ревью 025-029: _lock_prefix (VRF блокируется до чтения префикса,
    409 при одновременном переносе), константы политики входа перенесены в app/services.py.

Тесты: 14 passed (проверка ёмкости родителя приведена к семантике 025); сквозные сценарии
и гонки — docs/reviews/2026-09-26-changes-025-029-review.md, 2026-09-27-changes-030-review.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 08:28:50 +03:00

4.2 KiB
Raw Permalink Blame History

Политика блокировки входа без блокировки администратора анонимом (изменение 026)

Находка № 2 из docs/reviews/2026-09-26-codebase-review-2.md, серьёзность — средняя.

Context

Лимит по логину (5 неудач за 10 минут, изменение 012) действует для всех IP, и во время блокировки отклоняется и верный пароль. Любой, кто знает логин (например, admin), может отправлять 5 неверных попыток раз в 10 минут и держать учётную запись заблокированной.

Решение (app/api/v1/auth.py, app/models.py, миграция)

  1. Три области лимита (окно 10 минут, блокировка — окно после последней неудачи, как сейчас):
    Область Порог Кого блокирует
    логин + IP 5 этот логин с этого IP
    IP 20 любые логины с этого IP (как сейчас)
    логин (все IP) 50 этот логин со всех IP, кроме «известных» IP
    Константы MAX_PER_LOGIN_IP = 5, MAX_PER_IP = 20, MAX_PER_LOGIN = 50 в auth.py.
  2. Известные IP: таблица known_logins (username, client_ip, last_seen) с первичным ключом (username, client_ip) — новая миграция 0009 и модель KnownLogin. При успешном входе — upsert (INSERT … ON CONFLICT (username, client_ip) DO UPDATE SET last_seen = now()). IP считается известным, если last_seen не старше 30 дней (KNOWN_IP_DAYS = 30). Блокировка по области «логин» не применяется к запросам с известного IP. Области «логин + IP» и «IP» действуют всегда. Записи старше 30 дней удалять в часовом цикле ротации (app/rotation.py, рядом с очисткой login_attempts).
  3. _retry_after возвращает (секунды, область, счётчики), где область — login_ip | ip | login, счётчики — по каждой области. Запись session.failed — по-прежнему только первая неудача этого логина в окне (счётчик по логину во всех IP). session.locked — один раз при первом пересечении порога любой областью, в diff — scope (login_ip/ip/login), attempts, retry_after_seconds, для ip — distinct_logins (как сейчас).
  4. Сериализацию попыток по логину (pg_advisory_xact_lock(hashtext(name)), изменение 024) и выравнивание времени ответа сохранить.
  5. Успешный вход удаляет неудачи этого логина (по всем IP), как сейчас.
  6. README.md, раздел «Журнал» (абзац «Вход»): новые пороги, известные IP, почему анонимный клиент не может заблокировать вход с рабочего места пользователя.

Файлы

app/api/v1/auth.py, app/models.py, alembic/versions/0009_known_logins.py, app/rotation.py, README.md.

Проверка

  • 5 неверных попыток логина с IP A → 429 для A; верный пароль с IP B, с которого этот пользователь входил ранее, — 200.
  • 50 неверных попыток логина с разных IP (эмуляция через TRUSTED_PROXIES недоступна на стенде → проверка по коду или прямыми вставками в login_attempts) → вход с нового IP 429, с известного — 200.
  • alembic check без расхождений.