fix: отозванная cookie не оживает, когда логина нет в users
This commit is contained in:
parent
b4420cea16
commit
a2ce08f46a
2 changed files with 41 additions and 2 deletions
|
|
@ -51,6 +51,8 @@ _INSTANCE = hashlib.sha256(
|
|||
).hexdigest()
|
||||
_generations: dict[str, int] = {}
|
||||
_active_sockets: dict[str, weakref.WeakKeyDictionary] = {}
|
||||
# Logins already revoked because their users row disappeared.
|
||||
_vanished: set[str] = set()
|
||||
AUTH_GENERATION_SYNC_SECONDS = 1.0
|
||||
AUTH_GENERATION_MAX_AGE_SECONDS = 2.0
|
||||
_generations_synced_at: float | None = None
|
||||
|
|
@ -121,10 +123,13 @@ async def sync_generations() -> None:
|
|||
# The local-only dev-token principal is synthetic, never stored in users;
|
||||
# a DB watcher must not revoke its in-memory generation during test/demo
|
||||
# flows that deliberately exercise account invalidation.
|
||||
# The revoked generation stays cached: dropping it would make the next
|
||||
# lookup fall back to 0 and re-accept cookies issued before the revocation.
|
||||
synthetic = {"dev"} if get_settings().dev_auth_bypass else set()
|
||||
for login in _generations.keys() - current.keys() - synthetic:
|
||||
_vanished.intersection_update(_generations.keys() - current.keys())
|
||||
for login in _generations.keys() - current.keys() - synthetic - _vanished:
|
||||
invalidate_login(login)
|
||||
_generations.pop(login, None)
|
||||
_vanished.add(login)
|
||||
global _generations_synced_at
|
||||
_generations_synced_at = time.monotonic()
|
||||
|
||||
|
|
|
|||
|
|
@ -272,6 +272,40 @@ def test_peer_node_generation_sync_closes_revoked_websocket(monkeypatch):
|
|||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_generation_sync_keeps_revocation_of_login_missing_from_db(monkeypatch):
|
||||
# Демо-логины синтетические, в users их нет. Сброс поколения к 0 после
|
||||
# выхода вернул бы силу cookie, выданной до выхода.
|
||||
login = "demo-instructor"
|
||||
|
||||
class FakeResult:
|
||||
def all(self):
|
||||
return []
|
||||
|
||||
class FakeDb:
|
||||
async def execute(self, _query):
|
||||
return FakeResult()
|
||||
|
||||
class FakeSession:
|
||||
async def __aenter__(self):
|
||||
return FakeDb()
|
||||
|
||||
async def __aexit__(self, *_args):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(auth, "get_sessionmaker", lambda: lambda: FakeSession())
|
||||
auth.prime_generations({})
|
||||
who = auth._demo_principal(Role.INSTRUCTOR)
|
||||
request = SimpleNamespace(session={})
|
||||
auth._issue_session(request, who)
|
||||
stale = dict(request.session)
|
||||
|
||||
auth.invalidate_login(login)
|
||||
asyncio.run(auth.sync_generations())
|
||||
|
||||
assert auth._session_principal(stale) is None
|
||||
auth._generations.pop(login, None)
|
||||
|
||||
|
||||
def test_revocation_does_not_log_error_if_socket_already_disconnected():
|
||||
class DisconnectedSocket:
|
||||
async def close(self, **_kwargs):
|
||||
|
|
|
|||
Loading…
Reference in a new issue