The JWT's exp claim says 30 days, and I took that as the session
lifetime. It is only an outer ceiling. The server also keeps a per-token
whitelist entry in Valkey (jwt:{accountId}:{jti}) whose TTL is
JWT_TIMEOUT_SECONDS — 7200s on this instance — and JwtStrategy.validate
re-sets it on every authenticated request. Two hours idle and the token
is rejected with 29 days still on exp.
Proven, not inferred: the token from yesterday returned 401 at 13.8h old.
The live instance publishes the values unauthenticated at
GET /api/v3/config/public — JWT_TIMEOUT_SECONDS 7200,
JWT_SHOW_TIMEOUT_WARNING_SECONDS 3600, the latter being exactly the
one-hour UI prompt that prompted this investigation.
refresh-session turns out not to be special: it extends through the same
guard as any other route, and uniquely only in returning the remaining
TTL. So the keepalive uses GET /api/v3/me instead, and the server stays
GET-only; the one POST in the repo is in scripts/probe.mjs, where it
reports the idle budget.
JWT_EXTENDED_TIMEOUT_SECONDS (~1 month) exists in the config schema but
is vestigial: privateDevice has no references in the current NestJS
source, and generateJwtAndAddToWhitelist never overrides the TTL.
Also fixes a real breakage this surfaced: TypeScript parameter
properties are rejected by Node's type stripping, so `npm run dev` and
`npm test` both failed on any file reaching them. Rewritten as explicit
fields, and noted in CLAUDE.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
47 lines
2.0 KiB
Plaintext
47 lines
2.0 KiB
Plaintext
# ---------------------------------------------------------------------------
|
|
# Schulcloud instance
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Base URL of the instance, no trailing slash.
|
|
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.
|
|
TSC_JWT_COOKIE=
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# This MCP server
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Shared secret callers must present as `Authorization: Bearer <token>`.
|
|
# REQUIRED for the public deployment — without it the endpoint is open to
|
|
# anyone who finds the hostname. Generate one with:
|
|
# openssl rand -hex 32
|
|
MCP_AUTH_TOKEN=
|
|
|
|
# Listen address inside the container. Leave as-is when running behind Caddy.
|
|
PORT=8080
|
|
BIND_HOST=0.0.0.0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Limits (optional — sensible defaults are built in)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Largest file download_file will pull, in bytes. Default 25 MiB.
|
|
# Videos in Schulcloud routinely exceed this; they are not extractable anyway.
|
|
# MAX_DOWNLOAD_BYTES=26214400
|
|
|
|
# Characters of extracted text returned before truncation. Default 120000.
|
|
# MAX_EXTRACTED_CHARS=120000
|
|
|
|
# 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.
|
|
# KEEPALIVE_INTERVAL_MS=1800000
|