Skip to content

fix: prevent auth token refresh failure from deadlocking the client - #603

Open
glasstiger wants to merge 2 commits into
mainfrom
ia_token_refresh_fix
Open

fix: prevent auth token refresh failure from deadlocking the client#603
glasstiger wants to merge 2 commits into
mainfrom
ia_token_refresh_fix

Conversation

@glasstiger

Copy link
Copy Markdown
Contributor

Problem

When OIDC is used, Client.refreshAuthToken() proactively refreshes the token ~30s before expires_in elapses. The token fetch was performed inside an async setInterval callback:

const interval = setInterval(async () => {
  if (Client.numOfPendingQueries === 0) {
    clearInterval(interval)
    const newToken = await this.refreshTokenMethod()  // <-- rejects here
    ...
    Client.refreshTokenPending = false   // <-- never reached
    return resolve(true)                 // <-- never reached
  }
}, 50)

If refreshTokenMethod() rejected — a transport-level failure such as the token endpoint being unreachable, a CORS/DNS error, or a non-JSON response body (e.g. a reverse-proxy 502 HTML page) — the await threw before the flag was reset:

  • The outer promise never settled, so await this.refreshAuthToken() hung forever (it never even rejected).
  • The static refreshTokenPending flag was left stuck true.
  • Every subsequent query then deadlocked on the while (Client.refreshTokenPending) wait loop.

Net effect: the Web Console silently froze — no query ran, no 401 was triggered, no logout, no error surfaced. Only a manual page reload recovered it.

This only affected the transport-failure case. A well-formed OAuth error response (e.g. invalid_grant for an expired refresh token) already degraded gracefully to the login screen, as does the no-refresh-token case (401 → logout).

Fix

Restructure refreshAuthToken() so the failure can't escape without cleanup:

  • The setInterval now only waits for in-flight queries to drain, then resolve()s — a plain synchronous callback, no swallowed errors.
  • The refreshTokenMethod() call moves into the main async body, wrapped in try/catch/finally.
  • finally always resets refreshTokenPending — it can never get stuck again.
  • catch logs and swallows the error, keeping the stale token in place. The next request then receives a 401, which drives the existing MSG_CONNECTION_UNAUTHORIZED → logout flow — consistent with the no-refresh-token path.

The concurrency invariant is preserved: while refreshTokenPending is true, no new query passes the gate to increment numOfPendingQueries, so "0 pending queries when we refresh" still holds.

Testing

  • Added a regression test in client.test.ts: with an in-window SSO token and a refreshTokenMethod that rejects, the query still resolves, console.error fires, and a second query also goes through (proving the flag was reset). Against the old code this test hangs to timeout; against the fix it passes in ~100ms.
  • yarn test:unit src/utils/questdb/client.test.ts — 3/3 pass
  • tsc --noEmit — no new errors
  • eslint on both changed files — clean

🤖 Generated with Claude Code

When an OIDC token refresh failed at the transport level (token endpoint
unreachable, or a non-JSON response body), refreshTokenMethod() rejected
inside the setInterval callback of refreshAuthToken(), before the static
refreshTokenPending flag was reset. The flag was left stuck true, the outer
promise never settled, and every subsequent query deadlocked waiting on it —
the console silently froze with no 401 and no logout.

Wrap the refresh in try/catch/finally so refreshTokenPending is always reset.
On failure the stale token is kept in place and the next request receives a
401, which drives the existing re-auth flow — matching the behaviour when
there is no refresh token at all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@glasstiger glasstiger added bug Something isn't working web-console Issues relevant to "web-console" package labels Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working web-console Issues relevant to "web-console" package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant