Skip to content

portal: default sessions to 7 days rolling, 30 days absolute - #1025

Merged
ReganBell merged 2 commits into
mainfrom
portal-session-defaults
Sep 9, 2026
Merged

portal: default sessions to 7 days rolling, 30 days absolute#1025
ReganBell merged 2 commits into
mainfrom
portal-session-defaults

Conversation

@ReganBell

@ReganBell ReganBell commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

What changed

The portal's default browser-session lifetimes go up.

before after
PORTAL_SESSION_TTL_S (rolling; renewed on activity after half elapses) 28800 (8 h) 604800 (7 d)
PORTAL_SESSION_MAX_TTL_S default (absolute cap from authentication) max(86400, TTL) (24 h) max(2592000, TTL) (30 d)
renewal point floor(TTL / 2) unchanged

Both env vars still override the defaults exactly as before, and boot still refuses a max below the rolling TTL.

Why

With the built-in email auth broker there is no upstream identity-provider session behind the portal cookie, so every portal expiry costs the user a fresh emailed sign-in link. At 8 h / 24 h that is a link nearly every day, which is too aggressive for a default. Seven days rolling with a 30-day hard cap keeps the absolute bound while making the common case (daily use) stay signed in.

Every instance, not just the constant

  • plugins/portal/src/index.ts — the two defaults.
  • deploy/portal/fly.toml and cli/templates/fly/portal.toml — both templates pinned PORTAL_SESSION_TTL_S = "28800" into the [env] block, and the CLI copies that block verbatim into every derived Fly machine env. Left in place, the old lifetime would have survived on every Fly deployment regardless of the code default. The pin is removed so the code default governs; operators who want a different value set env.portal.PORTAL_SESSION_TTL_S in their deployment config, which already flows into the derived toml.
  • plugins/portal/README.md — the two places that documented 8 h / 24 h / "one day".
  • plugins/portal/test/router.test.ts, plugins/portal/test/playground.test.ts — the sliding-renewal tests aged a cookie past 4 h assuming the implicit 8 h default; they now pin PORTAL_SESSION_TTL_S themselves and derive their fixtures from it, so they test the renewal mechanism rather than whichever default is current.
  • plugins/portal/test/local-auth-bypass.test.ts — runs with the TTL unset, so it now asserts the minted cookie's Max-Age=604800; a silent revert of the default fails a test.
  • plugins/portal/test/entry.test.ts — the boot check that probes "TTL above the default max cap still boots" used 604800, which is no longer above the cap; it now uses 5184000.

Unrelated 86400/28800 occurrences (favicon cache max-age, playground mint window, turn wall-clock bounds, OAuth token fixtures, microvm durations) were reviewed and left alone.

Trade-offs surfaced in review (unchanged here, flagged for the maintainer)

  • The allow-list (OIDC_ALLOWED_EMAILS / domain) is checked only at sign-in, so removing someone now leaves an existing session valid for up to 7 days rolling / 30 days absolute instead of 8 h / 24 h. Re-checking the rule at renewal would close that, but the session subject is not always an email (OIDC_PRINCIPAL_CLAIM=sub, Slack team pins, playground guests), so it is left as a follow-up rather than folded in.
  • Playground anonymous guests inherit the same rolling TTL, so a try-it deployment keeps a guest scope alive for a week instead of 8 h.

Verification

  • plugins/portal: npm run typecheck clean, npm test 124/124.
  • cli: npm run typecheck clean, npm test 539/539 (includes the derive-byte-for-byte check against the checked-in Fly tomls).
  • root prettier --check, eslint, oxlint --deny-warnings clean on the touched paths.
  • Independent fresh-context review: no bugs; findings were the two trade-offs above plus the missing default assertion, which is now added.

The portal's own cookie was the only session a browser held once the
built-in email auth broker replaced an upstream identity provider, so the
8-hour rolling / 24-hour absolute defaults meant a fresh emailed sign-in
link nearly every day. The rolling default is now 604800 s (7 days) and
the absolute cap floors at 2592000 s (30 days); the renewal point stays at
half the rolling TTL. PORTAL_SESSION_TTL_S and PORTAL_SESSION_MAX_TTL_S
still override both.

The Fly templates pinned PORTAL_SESSION_TTL_S=28800 into every derived
machine env, which would have silently kept the old lifetime on those
deployments; the pin is removed so the code default governs and operators
set the knob through the deployment config's env map when they want a
different value. Tests that exercised sliding renewal against the implicit
default now pin their own TTL, and the boot-check test that probes a TTL
above the default cap uses a value above the new cap.
Review noted that after the renewal tests pinned their own TTL, nothing
would fail if the 7-day default silently reverted. The local-auth-bypass
login test runs with PORTAL_SESSION_TTL_S unset, so it now checks the
minted cookie's Max-Age against the default.
@ReganBell
ReganBell merged commit 4610c14 into main Sep 9, 2026
20 checks passed
@ReganBell
ReganBell deleted the portal-session-defaults branch September 9, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant