feat/multi-device-sessions #79

Merged
nrupard merged 4 commits from feat/multi-device-sessions into main 2026-05-12 19:53:25 +02:00
Owner
No description provided.
- Migration 0012 adds user_agent / ip / last_seen_at columns to sessions, indexed on last_seen for the settings query.
- New db helpers: create_session_with_origin (UA + IP captured), list_sessions_for_user (live, ordered by last activity), delete_session_for_user (user-scoped so one account cannot revoke another's session), touch_session_last_seen.
- Login, register, and the 2FA finalization path now capture the User-Agent header and the client IP from X-Forwarded-For / X-Real-IP when present; auth middleware bumps last_seen_at at most once per minute per session via an in-memory ledger so chat-style request volume does not hammer SQLite.
- Settings page renders an Active sessions list with a "{browser} on {os}" label, IP, last-seen, and first-seen timestamps. The current session is badged "This device" and its Revoke button is disabled; logout is the only path to end the current session.
- POST /settings/sessions/{id}/revoke deletes the session row, drops the ledger entry, and redirects with session_revoked=1 for the flash.
- New db_sessions.rs covers scoping, expiry filtering, last-seen touch, and origin capture; existing test fixtures now run migrations 10/11/12 so legacy create_session calls keep working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Recognize ChromeOS (X11; CrOS ...), which previously fell through to "Unknown OS".
- Distinguish iPadOS from iOS so an iPad does not get re-labeled when its UA omits the Mac OS X token.
- Surface version numbers when the UA exposes them cleanly: macOS, iOS/iPadOS, and Android append the version; Windows NT maps to the marketing name (Windows 10/11 share NT 10.0, so they share a label).
- Add FreeBSD / OpenBSD / NetBSD / Fuchsia detection, and a generic "Unix" fallback for X11/Unix UAs that do not say Linux.
- Cover the families with unit tests so a future change to detect_os cannot silently regress an existing OS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default SQLite journal mode is "delete", which serializes every reader and writer behind a single file lock. Under normal chat load the auth pool sees activity-touch, session last-seen, message writes, and notification updates landing within the same instant, and one of them loses the race with a "database is locked" warning.

Switch the connection options to WAL journal mode plus 5 s busy_timeout. WAL lets readers proceed while one writer holds the file, and busy_timeout makes any contending writer wait for the lock instead of failing immediately. synchronous=NORMAL is the documented WAL default and is the right durability tradeoff for our workload: a crash can lose the most recent commit but never corrupts the database.

Also raise max_connections to 16 so the pool can saturate the new WAL reader capacity instead of bottlenecking on the sqlx default of 10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(auth): drop transaction and add debounce on touch_user_activity
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 10s
6d637756d0
WAL + busy_timeout alone did not stop the "database is locked" warning on touch_user_activity. Two compounding causes remained:

1. The function held a write transaction across SELECT and UPDATE for every call. Under chat load this runs on every WebSocket message and every room visit, so dozens of overlapping transactions queued on the auth DB write lock and the slowest one exceeded busy_timeout.
2. The same handler fired hundreds of times per minute per active user, even though the column it touches only matters at one-minute resolution (idle threshold is 30 minutes).

Restructure the DB call into two single-statement updates: the first only matches idle rows so it preserves the DND-sticky guarantee, and the common path is exactly one write with no transaction at all. Add a 30 s per-user debounce ledger in AppState (same shape as the session last-seen ledger) so a burst of WS chatter collapses into one DB write per user per debounce window. The first call after a quiet period bypasses the debounce so idle->active flips remain instant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nrupard deleted branch feat/multi-device-sessions 2026-05-12 19:53:25 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!79
No description provided.