fix(auth): a session in use slides instead of expiring on day 14

Starlette re-signs the session cookie only when the session changes, so a
browser in daily use was signed out 14 days after sign-in, mid-episode.
The first authenticated request a day after the last renewal now writes a
fresh stamp, which re-signs the cookie; only 14 idle days end a session.
The epoch stays the revoke.
This commit is contained in:
2026-11-14 14:43:04 +01:00
parent 3bb71a4bc3
commit 94f9ffdb1c
2 changed files with 70 additions and 0 deletions
+50
View File
@@ -122,6 +122,56 @@ def test_deleting_the_account_signs_the_browser_out(client, make_user):
assert "user_id" not in _session(client)
_DAY = 86400
_PROTECTED = "/api/me/notifications" # any route behind `current_user` that answers 200 offline
def _signed_at(monkeypatch, at: int, payload: dict) -> str:
"""A session cookie carrying `payload`, signed as if at the unix time `at`."""
with monkeypatch.context() as m: # not `undo()`: that would also undo the fixtures' patches
m.setattr(TimestampSigner, "get_timestamp", lambda self: at)
return _cookie(payload)
def _hold(client, cookie: str) -> None:
"""Make `cookie` the browser's only session cookie (a plain `set` would sit beside the one the
server set, under another domain)."""
client.cookies.clear()
client.cookies.set("session", cookie)
def test_a_session_in_use_does_not_expire_after_the_cookie_lifetime(client, make_user, monkeypatch):
# The cookie's signature is checked against a 14-day life, and Starlette re-signs it only when
# the session changes. A browser in daily use must still stay signed in: a session that has not
# been renewed for a day is renewed by the next authenticated request.
make_user(email="slide@example.com", password=_PW)
assert _login(client, "slide@example.com", _PW).status_code == 200
now = int(TimestampSigner(str(settings.secret_key)).get_timestamp())
payload = {**_session(client), "renewed": now - 2 * _DAY}
old = _signed_at(monkeypatch, now - 13 * _DAY, payload)
_hold(client, old)
r = client.get(_PROTECTED)
assert r.status_code == 200
renewed = r.cookies.get("session")
assert renewed and renewed != old # the request re-signed the cookie
# Two days on, the cookie signed 13 days ago is past its 14 days; the renewed one is not.
monkeypatch.setattr(TimestampSigner, "get_timestamp", lambda self: now + 2 * _DAY)
_hold(client, old)
assert client.get(_PROTECTED).status_code == 401
_hold(client, renewed)
assert client.get(_PROTECTED).status_code == 200
def test_a_session_renewed_today_is_not_re_signed_on_every_request(client, make_user):
make_user(email="slide2@example.com", password=_PW)
assert _login(client, "slide2@example.com", _PW).status_code == 200
r = client.get(_PROTECTED)
assert r.status_code == 200
assert "set-cookie" not in r.headers
def _session(client) -> dict:
"""The decoded payload of the client's session cookie."""
raw = client.cookies.get("session")