Keepalive via refresh-session; GET pings measured insufficient

The endurance test refuted the sliding-window model I committed earlier.
A keepalive doing only GET /api/v3/me succeeded at t+0/30/60/90 and was
still rejected by t+120 — consistent with the session ending ~2h after
LOGIN (t+107), and inconsistent with 2h after the last request, which
would have been t+210.

This is a live-vs-source divergence, not a misreading: both the current
JwtWhitelistAdapter and the legacy Feathers ensureTokenIsWhitelisted
re-set the Valkey TTL on every authenticated request, so the source
reads as a sliding window. The instance does not behave that way.

So the keepalive now calls POST /authentication/refresh-session, the
endpoint behind the UI's "Sitzung verlängern" button, which a separate
100s test showed does hold the reported budget at 7200s. It is the only
non-GET request in the server: no body, touches only our own session,
cannot read or modify user data, and is not exposed as a tool, so no
model-driven call can ever be a POST. It logs the returned budget, which
makes a failing extension visible before the session is lost.

Whether this is sufficient is NOT established. Two mechanisms still fit:
an idle TTL that reads fail to refresh (keepalive works), or an absolute
cap/revocation anchored at login — e.g. the IDP's back-channel logout,
which clears every token for the account rather than one. Added
scripts/session-diagnose.mjs to settle it: it logs the budget every 10
min, so a decaying series indicates the former and an abrupt 401 at
7200s the latter. Docs state the open question rather than asserting a
mechanism.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 15:46:54 +02:00
parent d657ece436
commit 60ca4d3eba
11 changed files with 333 additions and 124 deletions

View File

@@ -6,9 +6,9 @@
TSC_URL=https://schulcloud-thueringen.de
# The value of the `jwt` cookie from a logged-in browser session.
# Two clocks apply: a 30-day hard expiry, and a 2-hour idle timeout that every
# API call resets. The built-in keepalive handles the second one, so in practice
# this needs replacing monthly. See docs/AUTH.md.
# The 30-day `exp` in the token is NOT its lifetime: sessions have been measured
# ending ~2h after login, and ordinary API reads do not extend them. The
# keepalive calls refresh-session to try to hold it. See docs/AUTH.md.
TSC_JWT_COOKIE=
# ---------------------------------------------------------------------------
@@ -39,8 +39,7 @@ BIND_HOST=0.0.0.0
# Per-request timeout against the Schulcloud API, in ms. Default 30000.
# REQUEST_TIMEOUT_MS=30000
# How often to ping Schulcloud to hold the session open, in ms. Default 1800000
# (30 min). Must stay well under the instance's JWT_TIMEOUT_SECONDS — 7200s
# here, readable from GET /api/v3/config/public. Set to 0 to disable, which
# will let the token die after two hours of inactivity.
# How often to call refresh-session to hold the session open, in ms. Default
# 1800000 (30 min). Must stay well under the instance's JWT_TIMEOUT_SECONDS —
# 7200s here, readable from GET /api/v3/config/public. Set to 0 to disable.
# KEEPALIVE_INTERVAL_MS=1800000