test_jti_revoked_session_reuse_2572.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """``is_jti_revoked`` must reuse the caller's session when given one (#2572).
  2. The permission dependencies already hold a DB session when they check whether
  3. a JWT's ``jti`` is revoked. The old ``is_jti_revoked`` always opened a *second*
  4. ``async_session``, so every authenticated request checked out two pooled
  5. connections instead of one. On a large farm a burst of concurrent logins then
  6. exhausted the pool (reporter @Jostxxl). Passing the existing session collapses
  7. each request back to a single checkout.
  8. These tests verify both that the revocation query is still correct and that a
  9. provided session is genuinely reused (no second checkout), while the no-session
  10. call still opens its own — the behaviour older callers rely on.
  11. """
  12. from datetime import datetime, timedelta, timezone
  13. import pytest
  14. from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
  15. from backend.app.core import auth as auth_mod
  16. from backend.app.core.auth import is_jti_revoked
  17. from backend.app.models.auth_ephemeral import AuthEphemeralToken
  18. def _revoked_row(token: str) -> AuthEphemeralToken:
  19. return AuthEphemeralToken(
  20. token=token,
  21. token_type="revoked_jti",
  22. expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
  23. )
  24. @pytest.mark.asyncio
  25. async def test_revoked_jti_detected_via_provided_session(db_session):
  26. """A revoked jti is reported revoked when the caller passes its session."""
  27. db_session.add(_revoked_row("jti-revoked-1"))
  28. await db_session.commit()
  29. assert await is_jti_revoked("jti-revoked-1", db_session) is True
  30. @pytest.mark.asyncio
  31. async def test_unknown_jti_not_revoked_via_provided_session(db_session):
  32. assert await is_jti_revoked("jti-never-seen", db_session) is False
  33. @pytest.mark.asyncio
  34. async def test_provided_session_is_reused_not_a_second_checkout(db_session, monkeypatch):
  35. """PERF regression (#2572): passing ``db`` must NOT open a new session —
  36. that second checkout per request is the pool pressure we removed."""
  37. opened = {"n": 0}
  38. original = auth_mod.async_session
  39. def _counting(*args, **kwargs):
  40. opened["n"] += 1
  41. return original(*args, **kwargs)
  42. monkeypatch.setattr(auth_mod, "async_session", _counting)
  43. assert await is_jti_revoked("jti-with-session", db_session) is False
  44. assert opened["n"] == 0, "is_jti_revoked opened its own session despite being given one"
  45. @pytest.mark.asyncio
  46. async def test_no_session_still_opens_its_own(test_engine, monkeypatch):
  47. """Callers that check the jti before they have a session (e.g. the token
  48. dependencies) must keep working — omitting ``db`` opens a short one."""
  49. test_async_session = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
  50. async with test_async_session() as seed:
  51. seed.add(_revoked_row("jti-revoked-2"))
  52. await seed.commit()
  53. opened = {"n": 0}
  54. def _counting(*args, **kwargs):
  55. opened["n"] += 1
  56. return test_async_session(*args, **kwargs)
  57. monkeypatch.setattr(auth_mod, "async_session", _counting)
  58. assert await is_jti_revoked("jti-revoked-2") is True
  59. assert opened["n"] == 1, "is_jti_revoked should open exactly one session when none is provided"