Fix session lifetime: 2h sliding idle timeout, not 30 days
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>
This commit is contained in:
15
docs/API.md
15
docs/API.md
@@ -79,6 +79,8 @@ current material**; lessons are the older format.
|
||||
| One file's metadata | `GET /api/v3/file/{fileRecordId}` |
|
||||
| File bytes | `GET /api/v3/file/download/{fileRecordId}/{fileName}` |
|
||||
| News | `GET /api/v3/news` |
|
||||
| Instance settings (no auth) | `GET /api/v3/config/public` |
|
||||
| Remaining idle budget | `POST /api/v3/authentication/refresh-session` → `{expiresInSeconds}` |
|
||||
|
||||
### Gotchas that cost real time
|
||||
|
||||
@@ -115,6 +117,16 @@ ids as `{buffer:{type:'Buffer',data:[...]}}` rather than hex strings — a leak
|
||||
from the legacy Mongo serialisation. `normalizeObjectId` in `src/render.ts`
|
||||
converts them.
|
||||
|
||||
**The JWT's `exp` is not the session lifetime.** A server-side whitelist entry
|
||||
in Valkey (`jwt:{accountId}:{jti}`) expires after `JWT_TIMEOUT_SECONDS` — 7200 s
|
||||
on this instance — and every authenticated request re-sets it. Two hours idle
|
||||
and the token is rejected with 29 days still on `exp`. The live values are
|
||||
public at `GET /api/v3/config/public`. Full write-up in `docs/AUTH.md`.
|
||||
|
||||
**`GET /api/v3/config/public` is unauthenticated and useful.** 78 keys of
|
||||
instance configuration, including the session timeouts and feature flags. Handy
|
||||
for checking deployed settings without a token.
|
||||
|
||||
**`Content-Disposition` on downloads is malformed.** It comes back as
|
||||
`attachment;; filename="…"` — note the doubled semicolon — and the filename is
|
||||
percent-encoded inside the quotes. Parse defensively.
|
||||
@@ -132,4 +144,5 @@ the Etherpad-style editor, not the document text.
|
||||
## Re-verifying after an upstream release
|
||||
|
||||
`npm run probe` re-checks every assumption above against the live instance and
|
||||
prints what it finds, including days left on the token.
|
||||
prints what it finds — both token clocks included: days until hard expiry, and
|
||||
seconds of idle budget remaining.
|
||||
|
||||
Reference in New Issue
Block a user