Skip to content

Track own sessions#15

Merged
ScriptSmith merged 5 commits intomainfrom
own-sessions
Mar 19, 2026
Merged

Track own sessions#15
ScriptSmith merged 5 commits intomainfrom
own-sessions

Conversation

@ScriptSmith
Copy link
Owner

No description provided.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR adds self-service session management to the Account page, allowing users to view all their active sessions, see which one is current, and revoke individual sessions — including with a warning when revoking the current session. On the backend, two new endpoints (GET /admin/v1/me/sessions and DELETE /admin/v1/me/sessions/{session_id}) are gated behind the sso feature flag and mirror the existing admin user-sessions API, but scoped to the authenticated user's own identity. All previously flagged issues (ownership check bypass, silent deletion failure, missing error toast, single-ID tracking, audit log noise, and missing current-session indicator) have been addressed in this revision.

  • Backend (me_sessions.rs): New list and delete_one handlers with correct ownership verification, error propagation, and a conditionally-fired audit log scoped to session_existed.
  • Backend (sessions.rs): current_session_id: Option<Uuid> added to SessionListResponse; the admin list endpoint hard-codes it to None (correctly, as admins viewing another user's sessions have no "current" context there).
  • Frontend (AccountPage.tsx): Set<string> used for concurrent revocation tracking; confirm dialog warns before revoking the current session; error toast on failure — but onSuccess does not call logout() when the revoked session matches the user's own, leaving the client in a broken authenticated-looking state after self-revocation.
  • Component (SessionCard.tsx): isCurrent prop highlights the active session with a "This device" badge and a primary-colored border.
  • OpenAPI: Both new endpoints and the current_session_id field are correctly reflected in openapi.json.

Confidence Score: 3/5

  • Safe to merge with one fix: revoking the current session must trigger client-side logout to avoid leaving the user in a broken auth state.
  • The backend is solid — auth, ownership checks, error handling, and audit logging are all correct. The frontend handles concurrency and errors well. The one outstanding logic bug is that revoking the current session leaves the client showing the user as logged in even though their server session is gone, which is a confusing and broken UX state. That single issue keeps the score from being higher.
  • ui/src/pages/AccountPage.tsx — the onSuccess handler for deleteSessionMutation needs to call logout() when the revoked session matches the current session.

Important Files Changed

Filename Overview
src/routes/admin/me_sessions.rs New self-service session management endpoints. Previous issues (ownership check bypass, silent deletion failure, audit log firing on non-existent sessions) are all addressed in this revision. Error propagation is correct, audit log is properly guarded, and deletion failure returns an error response.
ui/src/pages/AccountPage.tsx Sessions UI is well-structured — concurrent revocation tracking with a Set, confirmation dialog, error toast, and current-session warning are all present. However, revoking the current session doesn't call logout(), leaving the client in a broken authenticated-looking state with an invalidated server session.
src/routes/admin/sessions.rs Adds current_session_id field to SessionListResponse (None for admin endpoint). Struct change is backward-compatible due to skip_serializing_if. Existing tests updated correctly.
ui/src/components/Admin/SessionsPanel/SessionCard.tsx Adds isCurrent prop to highlight the active session with a "This device" badge and a primary border. Clean, additive change with no regressions.
ui/src/pages/AccountPage.stories.tsx Mock session data and handlers added to all relevant stories. The Error story includes sessionsHandler (sessions shown) but no DELETE handler for sessions; clicking Revoke in that story will surface the error toast, which may be intentional or accidental.

Sequence Diagram

sequenceDiagram
    participant U as User (Browser)
    participant UI as AccountPage
    participant BE as /admin/v1/me/sessions
    participant SS as SessionStore

    U->>UI: Open Account page
    UI->>BE: GET /me/sessions
    BE->>SS: list_user_sessions(external_id)
    SS-->>BE: [session_a, session_b, ...]
    BE-->>UI: { data, enhanced_enabled, current_session_id }
    UI-->>U: Render session cards (current highlighted)

    U->>UI: Click Revoke on session_b
    UI-->>U: Confirm dialog ("Revoke session?")
    U->>UI: Confirm

    UI->>BE: DELETE /me/sessions/{session_b_id}
    BE->>SS: get_session(session_b_id)
    SS-->>BE: session (external_id matches ✓)
    BE->>SS: delete_session(session_b_id)
    SS-->>BE: ok
    BE-->>UI: { sessions_revoked: 1 }
    UI->>UI: Remove session_b_id from revokingSessionIds
    UI->>BE: GET /me/sessions (cache invalidated)
    BE-->>UI: Updated session list

    Note over U,UI: If session_b was the CURRENT session:<br/>BE returns 401 on refetch,<br/>UI shows error — but auth state<br/>is NOT cleared (bug)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/pages/AccountPage.tsx
Line: 86-93

Comment:
**Revoking the current session doesn't trigger client-side logout**

When a user confirms revoking their own active session, the `onSuccess` handler removes it from `revokingSessionIds` and invalidates the sessions query — but never calls `logout()`. The subsequent refetch of `/admin/v1/me/sessions` returns a 401 (the session cookie is now invalid), setting `sessionsError` to `true` and showing "Failed to load sessions. Please try again." The user's auth context remains intact, so they appear logged-in while their server session is gone, leaving them in a broken state.

`handleRevokeSession` already computes `isCurrent` before mutating, but that knowledge isn't carried into `onSuccess`. The fix is to compare `variables.path.session_id` against `sessions?.current_session_id` inside `onSuccess`, and call `logout()` (already imported via `useAuth`) when they match — skipping the query invalidation in that branch since the user is about to be logged out anyway.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Review fixes"

@ScriptSmith
Copy link
Owner Author

@greptile-apps

@ScriptSmith
Copy link
Owner Author

@greptile-apps

@ScriptSmith
Copy link
Owner Author

@greptile-apps

@ScriptSmith ScriptSmith merged commit d1dd9a4 into main Mar 19, 2026
19 checks passed
@ScriptSmith ScriptSmith deleted the own-sessions branch March 19, 2026 13:30
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