tradein/auth: усилить защиту логина от перебора после снятия basic_auth #2571

Open
opened 2026-07-30 21:17:56 +00:00 by lekss361 · 0 comments
Owner

Найдено deep-review PR #2569 (cutover, эпик #2549). 🟡 Не блокер, но актуально сразу после снятия basic_auth.

Проблема

До cutover форма входа была прикрыта Caddy basic_auth — снаружи до неё было не достучаться. После снятия POST /trade-in/api/v1/auth/login становится единственным публичным эндпоинтом, доступным из интернета без всяких кредов.

Текущий лимит (_LOGIN_LIMITER): 5 попыток / 300с с ключом f"{len(username)}:{username}:{ip}" — то есть на пару (пользователь, IP). Глобального потолка на username нет, поэтому распределённый перебор (credential stuffing с множества адресов) получает по 5 попыток с каждого источника.

Хорошая новость: _client_ip (ratelimit.py:140-153) берёт правый hop из XFF, а за Caddy ровно один прокси-хоп — значит ключ не подделывается заголовком.

Что сделать (варианты)

  • Глобальный лимит попыток на username поверх per-IP (например, 20/час независимо от источника) с логированием в user_events.
  • Экспоненциальная задержка/блокировка учётки после N неудач подряд, снимаемая менеджером через «Команду» или по таймауту.
  • Опционально: CAPTCHA/PoW после превышения порога.

Учесть: блокировка по username — вектор DoS против конкретного пользователя, поэтому лучше замедление, а не жёсткая блокировка.

DoD

Тесты: распределённый перебор (много IP, один username) упирается в глобальный потолок; легитимный пользователь с опечаткой не блокируется надолго; события неудачных входов видны в аудите.

Найдено deep-review PR #2569 (cutover, эпик #2549). 🟡 Не блокер, но актуально сразу после снятия basic_auth. ## Проблема До cutover форма входа была прикрыта Caddy basic_auth — снаружи до неё было не достучаться. После снятия `POST /trade-in/api/v1/auth/login` становится единственным публичным эндпоинтом, доступным из интернета без всяких кредов. Текущий лимит (`_LOGIN_LIMITER`): 5 попыток / 300с с ключом `f"{len(username)}:{username}:{ip}"` — то есть **на пару (пользователь, IP)**. Глобального потолка на username нет, поэтому распределённый перебор (credential stuffing с множества адресов) получает по 5 попыток с каждого источника. Хорошая новость: `_client_ip` (`ratelimit.py:140-153`) берёт **правый** hop из XFF, а за Caddy ровно один прокси-хоп — значит ключ не подделывается заголовком. ## Что сделать (варианты) - Глобальный лимит попыток на username поверх per-IP (например, 20/час независимо от источника) с логированием в `user_events`. - Экспоненциальная задержка/блокировка учётки после N неудач подряд, снимаемая менеджером через «Команду» или по таймауту. - Опционально: CAPTCHA/PoW после превышения порога. Учесть: блокировка по username — вектор DoS против конкретного пользователя, поэтому лучше замедление, а не жёсткая блокировка. ## DoD Тесты: распределённый перебор (много IP, один username) упирается в глобальный потолок; легитимный пользователь с опечаткой не блокируется надолго; события неудачных входов видны в аудите.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2571
No description provided.