Skip to content

fix(session): cap session expiry at absolute_timeout - #205

Merged
allen0099 merged 1 commit into
masterfrom
fix/session-absolute-timeout-cap
Sep 26, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/session-absolute-timeout-cap

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Closes #164

Problem

With absolute_timeout set, a sliding renewal set expires_at to now + session_ttl even when that was past created_at + absolute_timeout. The backend TTL and the JWT exp both come from expires_at, so they overshot the cap too. Repro: with session_ttl=100, sliding_threshold=0.5 and absolute_timeout=120, a read at 60 s renews expires_at to 40 s past the cap. The same thing happened at creation when session_ttl > absolute_timeout.

Fix

  • New SessionManager._expiry_for(session) returns now + session_ttl, capped at created_at + absolute_timeout. It is used at creation and on sliding renewal.
  • A renewal happens only when it actually moves expires_at later. Otherwise, once the expiry sits at the cap and less than the threshold remains, every request would get a re-issued token with the same exp.
  • The existing absolute_timeout check in get_session stays. It still catches records stored before the cap existed, or before absolute_timeout was lowered.

Behaviour change

The backend now drops the record at the cap. A token presented after it therefore raises SessionNotFoundError instead of SessionExpiredError, which is what already happens after an ordinary session_ttl expiry. The library does not tell these two apart, but applications catching SessionExpiredError specifically will see the difference. The CHANGELOG entry notes this.

Tests

  • test_sliding_renewal_stops_at_absolute_timeout (JWT): renewed expires_at, JWT exp and backend expiry all sit at the cap.
  • test_no_renewal_once_expiry_reaches_absolute_timeout: no second token at the cap.
  • test_new_session_expiry_is_capped_by_absolute_timeout.
  • test_absolute_timeout_raises_session_expired_error could no longer pass as written, because its record now expires in the backend. It now covers the "record stored before the cap" case without sleep.
  • Mutation checks: without the cap, only the first and third new tests fail. With only the at-cap guard removed, only the second one fails.
  • ruff, mypy --strict, full suite against live Redis and Memcached (CACHEX_REQUIRE_LIVE_SERVERS=1): 861 passed. session/manager.py coverage stays at 100%.

Docs

docs/SESSION.md and docs/JWT_CLAIMS.md, plus their zh-TW versions, now state the cap.

A sliding renewal, or a session_ttl longer than absolute_timeout, set
expires_at past created_at + absolute_timeout. The backend TTL and the
JWT exp follow expires_at, so the stored record and the token claimed a
longer life than the configuration allows until the next read rejected
the session.

Cap expires_at at creation and on renewal, and skip the renewal once
the expiry already sits at the cap, so such a session does not get a
new token on every request.

Closes #164
@allen0099 allen0099 added this to the 0.3.8 milestone Sep 26, 2026
@allen0099 allen0099 added bug Something isn't working session Session management subsystem labels Sep 26, 2026
@allen0099
allen0099 merged commit 9c59dcf into master Sep 26, 2026
11 checks passed
@allen0099
allen0099 deleted the fix/session-absolute-timeout-cap branch September 26, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working session Session management subsystem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sliding renewal can extend a session past absolute_timeout

1 participant