Skip to content

fix(server): non-fatal UA session binding + sliding session renewal - #727

Merged
xarmian merged 1 commit into
mainfrom
fix/session-logout-ua-renewal
Jun 15, 2026
Merged

fix(server): non-fatal UA session binding + sliding session renewal#727
xarmian merged 1 commit into
mainfrom
fix/session-logout-ua-renewal

Conversation

@xarmian

@xarmian xarmian commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Why

Users were being logged out frequently on web and the mobile app. Two server-side root causes:

  1. UA session binding was unconditional and fatal. Any User-Agent change (browser/WebView update, DevTools device emulation, mobile rebuild) silently de-authenticated the session. Evidence on the dev box: 206 "User-Agent changed" rejections vs 0 strict IP destroys.
  2. No sliding renewal. Sessions had a fixed absolute TTL (7d web / 30d CLI) with no refresh on activity, so even active users hit the cliff.

What

  • UA binding is now log-only at all three enforcement sites: TokenAuth, SessionAuth, and the validateSessionCookie helper (CLI-auth approval, account/2FA setup, session check) — so auth semantics don't split between middleware- and helper-protected routes.
  • Sliding renewal: RenewSessionIfStale extends expires_at when a session is past its half-window threshold, capped at created_at + 90d (SessionMaxLifetime). CAS-guarded; only reported as renewed when RowsAffected() confirms the write. SessionAuth/TokenAuth re-issue the session (+CSRF) cookie on renewal.
  • New renew_ttl_seconds column (sqlite migration 067 + pg 046). Legacy rows (0) keep their fixed expiry and age out.

Tracking

BUG-1815, TASK-1816.

Verification

  • go build ./..., go test ./internal/store/ ./internal/server/ pass (4 new renewal tests).
  • Reviewed by Codex review loop — CLEAN (2 findings fixed: split UA semantics, renewal CAS RowsAffected).

Two root causes behind users being logged out:

- UA session binding was unconditional and fatal — any User-Agent change
  (browser/WebView update, DevTools device emulation, mobile rebuild)
  silently de-authenticated the session. Now log-only across all three
  enforcement sites (TokenAuth, SessionAuth, and the validateSessionCookie
  helper used by CLI-auth/account/session-check routes), mirroring the
  default IP-change handling. (BUG-1815)

- Sessions had a fixed absolute TTL with no refresh on activity, so even an
  active user hit the cliff at 7d (web) / 30d (CLI). Adds sliding renewal:
  RenewSessionIfStale extends expires_at when past the half-window threshold,
  capped at created_at + 90d (SessionMaxLifetime), CAS-guarded and only
  reported when RowsAffected confirms the write. The middleware re-issues the
  session + CSRF cookies on renewal. New renew_ttl_seconds column (sqlite +
  pg migrations); legacy rows (0) keep their fixed expiry. (TASK-1816)

Reviewed by Codex (clean). Tests: store + server suites pass.
@xarmian
xarmian merged commit 33e4943 into main Jun 15, 2026
4 checks passed
@xarmian
xarmian deleted the fix/session-logout-ua-renewal branch June 15, 2026 01:15
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