feat(auth): per-user device list + per-device session revoke #169

Open
opened 2026-05-14 00:12:52 +00:00 by chihlasm · 0 comments
Owner

Follow-up from PR #168 (session-expiration-policy).

Motivation

The v1 surface from #168 supports account-wide bulk revocation (POST /accounts/me/security/revoke-sessions with scope: all|others). The follow-up is to let any user see their own active devices and revoke individual sessions — the classic Slack/Google "Where youre signed in" surface. Also unlocks finer-grained breach response.

Scope

  • Schema: add three columns to refresh_tokensuser_agent VARCHAR(500), ip VARCHAR(45), last_used_at TIMESTAMPTZ. Stamp user_agent + ip at _mint_session_tokens / _mint_with_claims from the FastAPI Request. Update last_used_at on every /auth/refresh (in the same atomic-revoke statement is cleanest — RETURNING the row and INSERTing the new one with last_used_at = now()).
  • Endpoint: GET /me/sessions returns the current users active refresh-token rows with {jti_prefix, user_agent, ip, created_at, last_used_at, is_current}. is_current set by comparing JTI hashes.
  • Endpoint: DELETE /me/sessions/{jti_prefix} revokes one specific token (audited).
  • UI: new section on the profile page (/account/profile) or new /account/devices page — list of "Chrome on macOS · Last active 2h ago · IP 1.2.3.4 · Sign out" rows.
  • Stretch: parse user_agent to friendly device label (react-device-detect or similar). Geo lookup on ip for "Signed in from San Francisco" copy.

Out of scope

  • Per-device naming ("My work laptop").
  • Trusted-device list / device fingerprinting.

Why deferred from #168

The original plan called it out (docs/plans/2026-05-13-session-expiration-policy.md §4.11 and §9 follow-up #2). Account-wide bulk revoke covers the breach-response use case for v1 — "I lost my laptop" still means "sign me out everywhere." Per-device is the next polish layer. Adds a column migration + tracking work that the v1 ship didnt need.

Dependencies

None. Lands cleanly on top of PR #168.

Effort

~1-2 days. Migration + 2 endpoints + 1 page + audit-event type.

Follow-up from PR #168 (session-expiration-policy). ## Motivation The v1 surface from #168 supports **account-wide** bulk revocation (`POST /accounts/me/security/revoke-sessions` with `scope: all|others`). The follow-up is to let any user see **their own active devices** and revoke individual sessions — the classic Slack/Google "Where youre signed in" surface. Also unlocks finer-grained breach response. ## Scope - **Schema:** add three columns to `refresh_tokens` — `user_agent VARCHAR(500)`, `ip VARCHAR(45)`, `last_used_at TIMESTAMPTZ`. Stamp `user_agent` + `ip` at `_mint_session_tokens` / `_mint_with_claims` from the FastAPI `Request`. Update `last_used_at` on every `/auth/refresh` (in the same atomic-revoke statement is cleanest — `RETURNING` the row and `INSERT`ing the new one with `last_used_at = now()`). - **Endpoint:** `GET /me/sessions` returns the current users active refresh-token rows with `{jti_prefix, user_agent, ip, created_at, last_used_at, is_current}`. `is_current` set by comparing JTI hashes. - **Endpoint:** `DELETE /me/sessions/{jti_prefix}` revokes one specific token (audited). - **UI:** new section on the profile page (`/account/profile`) or new `/account/devices` page — list of "Chrome on macOS · Last active 2h ago · IP 1.2.3.4 · Sign out" rows. - **Stretch:** parse `user_agent` to friendly device label (`react-device-detect` or similar). Geo lookup on `ip` for "Signed in from San Francisco" copy. ## Out of scope - Per-device naming ("My work laptop"). - Trusted-device list / device fingerprinting. ## Why deferred from #168 The original plan called it out (`docs/plans/2026-05-13-session-expiration-policy.md` §4.11 and §9 follow-up #2). Account-wide bulk revoke covers the breach-response use case for v1 — "I lost my laptop" still means "sign me out everywhere." Per-device is the next polish layer. Adds a column migration + tracking work that the v1 ship didnt need. ## Dependencies None. Lands cleanly on top of PR #168. ## Effort ~1-2 days. Migration + 2 endpoints + 1 page + audit-event type.
Sign in to join this conversation.