Throttle: IP-независимый лимит на аккаунт + заметка об устойчивости
Вход игрока и админа получают лимит login-user/admin-login-user, не зависящий от IP: ротация X-Forwarded-For (#58) больше не снимает защиту полностью. Успешный вход сбрасывает счётчики аккаунта. В docstring ratelimit — про сброс при рестарте и необходимость внешнего стора при нескольких воркерах. #60 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Вход по логину (нику) и паролю: регистрация, вход, защита от перебора."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlmodel import Session, select
|
||||
|
||||
@@ -181,6 +182,26 @@ def test_throttle_per_ip_across_logins(client: TestClient):
|
||||
assert _login(client, nickname="Ещё один", password="wrong-password").status_code == 429
|
||||
|
||||
|
||||
def test_account_scoped_throttle_survives_ip_rotation(client: TestClient, engine, monkeypatch):
|
||||
"""Перебор одного логина с РАЗНЫХ адресов (ротация X-Forwarded-For, #58) упирается в
|
||||
IP-независимый лимит на аккаунт (#60): пара IP+логин и лимит по IP так не копятся."""
|
||||
import app.core.ratelimit as ratelimit
|
||||
from app.auth.password import _ACCOUNT_LIMIT, login_player
|
||||
from app.core.errors import InvalidCredentialsError, TooManyAttemptsError
|
||||
|
||||
now = [3000.0]
|
||||
monkeypatch.setattr(ratelimit.time, "monotonic", lambda: now[0])
|
||||
_register(client, nickname="Жертва", password=PASSWORD)
|
||||
|
||||
with Session(engine) as s:
|
||||
for i in range(_ACCOUNT_LIMIT): # каждый раз новый адрес
|
||||
with pytest.raises(InvalidCredentialsError):
|
||||
login_player(s, "Жертва", "wrong-password", ip=f"10.0.{i // 256}.{i % 256}")
|
||||
# ещё одна попытка с совершенно нового адреса — уже блок по лимиту на аккаунт
|
||||
with pytest.raises(TooManyAttemptsError):
|
||||
login_player(s, "Жертва", "wrong-password", ip="203.0.113.7")
|
||||
|
||||
|
||||
# ─── Установка и смена пароля ────────────────────────────────────────────────
|
||||
|
||||
def _set_password(client: TestClient, new: str, current: str | None = None):
|
||||
|
||||
Reference in New Issue
Block a user