Skip to content

fix: guard WebSocket and skip duplicate whoami when unauthenticated#98

Merged
DaxServer merged 2 commits into
mainfrom
fix/ws-auth-guard
Jun 20, 2026
Merged

fix: guard WebSocket and skip duplicate whoami when unauthenticated#98
DaxServer merged 2 commits into
mainfrom
fix/ws-auth-guard

Conversation

@DaxServer

Copy link
Copy Markdown
Owner
  • Only open the WebSocket when the user is authenticated; close it on logout. Previously open() was called unconditionally in onMounted, flooding the network tab with reconnect attempts for logged-out users
  • Remove redundant checkAuth() call from Header.vue; the router's beforeEach guard already owns this (with an isChecked dedup guard), so the header was always firing a second /auth/whoami request on mount
  • Extract auth-gated socket logic into useAuthSocket composable with 4 unit tests

— Claude Sonnet 4.6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The change is safe to merge; the composable logic is correct, the socket is properly gated, and the redundant whoami removal is sound.

The core implementation is straightforward and well-tested. The only notes are style-level: test scope management deviates from the project guide, and the unauthenticated-init branch calls close() on an already-idle socket (harmless but undocumented). Neither affects runtime correctness.

frontend/src/composables/tests/useAuthSocket.test.ts — scope lifecycle should follow the beforeEach/afterEach pattern from CLAUDE.md to prevent potential test leaks.

Important Files Changed

Filename Overview
frontend/src/composables/useAuthSocket.ts New composable that gates WebSocket open/close on authentication state using an immediate, sync watcher and onScopeDispose cleanup.
frontend/src/composables/tests/useAuthSocket.test.ts 5 unit tests covering the composable's auth-gated socket logic; scope lifecycle management diverges from the project's own CLAUDE.md testing pattern, risking leaked scopes on assertion failure.
frontend/src/App.vue Replaces unconditional onMounted open() with useAuthSocket(); clean change with no issues.
frontend/src/components/Header.vue Removes redundant checkAuth() call; router's beforeEach guard already owns this with an isChecked dedup guard.
.gitignore Adds .mcp.json to gitignore; trivial housekeeping.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as App.vue
    participant UAS as useAuthSocket
    participant Auth as auth.store
    participant Router as Router (beforeEach)
    participant Socket as useSocket

    App->>UAS: useAuthSocket()
    UAS->>Auth: watch(isAuthenticated, immediate)
    Auth-->>UAS: "isAuthenticated = false (initial)"
    UAS->>Socket: close() [no-op on idle socket]

    Router->>Auth: checkAuth() → whoami
    Auth-->>Router: user set
    Auth-->>UAS: "isAuthenticated = true (transition)"
    UAS->>Socket: open()

    Note over App,Socket: User logs out
    App->>Auth: logout()
    Auth-->>UAS: "isAuthenticated = false"
    UAS->>Socket: close()

    Note over App,Socket: Component unmounts / scope disposed
    UAS->>Socket: onScopeDispose → close()
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as App.vue
    participant UAS as useAuthSocket
    participant Auth as auth.store
    participant Router as Router (beforeEach)
    participant Socket as useSocket

    App->>UAS: useAuthSocket()
    UAS->>Auth: watch(isAuthenticated, immediate)
    Auth-->>UAS: "isAuthenticated = false (initial)"
    UAS->>Socket: close() [no-op on idle socket]

    Router->>Auth: checkAuth() → whoami
    Auth-->>Router: user set
    Auth-->>UAS: "isAuthenticated = true (transition)"
    UAS->>Socket: open()

    Note over App,Socket: User logs out
    App->>Auth: logout()
    Auth-->>UAS: "isAuthenticated = false"
    UAS->>Socket: close()

    Note over App,Socket: Component unmounts / scope disposed
    UAS->>Socket: onScopeDispose → close()
Loading

Reviews (2): Last reviewed commit: "fix: add immediate:true to useAuthSocket..." | Re-trigger Greptile

Comment thread frontend/src/composables/useAuthSocket.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@DaxServer

Copy link
Copy Markdown
Owner Author

Fixed the immediate: true gap — added it alongside flush: 'sync' so the watcher fires on the initial value, covering the page-reload-while-logged-in case. Also added a dedicated test for already-authenticated-on-init and updated the logout/dispose tests to assert open() was called before checking close().

— Claude Sonnet 4.6

@DaxServer DaxServer merged commit edd6222 into main Jun 20, 2026
5 checks passed
@DaxServer DaxServer deleted the fix/ws-auth-guard branch June 20, 2026 12:19
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