Skip to content

fix: fix Web Console hanging when SSO token refresh fails - #603

Open
glasstiger wants to merge 46 commits into
mainfrom
ia_token_refresh_fix
Open

glasstiger wants to merge 46 commits into
mainfrom
ia_token_refresh_fix

Conversation

@glasstiger

@glasstiger glasstiger commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Review follow-up pushed in 9da50a589c36e7bf1d8719f8db919f0e6636b3fd.

Summary

Fix the Web Console hanging indefinitely when an SSO token refresh fails, and
make query execution and notebook persistence respect the authentication session
that started the work.

Refreshing is shared across SQL consumers, cancellable and bounded. Queries
normally wait up to 10 seconds; a rotating refresh request can continue until
its separate hard deadline (at most 60 seconds). A server-side 401 recovery
waits for that request rather than cancelling a potentially usable rotated
refresh token.

Transient transport, timeout, HTTP 429 and 5xx failures back off without forcing
logout. Once the bearer expires, SQL is not sent until renewal succeeds. The
console reports that explicitly, rather than reporting a database outage or
telling an agent that a never-sent write may have committed. An authoritative
OAuth rejection still returns the user to sign-in.

Complete scope

  • Authentication and token installation: strict token-response parsing;
    optional expiry handling; ID-token bearer expiry; refresh-token rotation;
    missing required ID-token errors; callback URL restoration; tab-local session
    generations; stable provider callbacks and subscription cleanup.
  • Query client: one shared console/telemetry client; send-time authorization
    for SQL, validation, import and preferences requests; bounded refresh waits;
    one safe retry after a rejected bearer; session-aware result/error fencing;
    explicit session-ended and not-sent outcomes.
  • Session teardown: clear client credentials and refresh callbacks, abort
    live queries, cancel delayed telemetry polls, and reset active/pending query
    confirmations (including confirmations already waiting for cancellation).
  • Editor and notifications: transient, query-id-indexed LOADING state is
    excluded from notification history; settled/superseded loading notifications
    are cleaned up; the notification Stop button only cancels its own execution;
    scripts preserve confirmed outcomes and report partial completion on logout.
    Script teardown is shared, without an extra settlement dispatch per statement.
  • Notebook execution and storage: fence live, headless and refresh work;
    separate layout-phase detachment from passive destruction; flush frames
    accepted before logout while discarding late responses; atomically save
    accepted headless metadata/snapshots; recover with metadata-only persistence
    when a large snapshot aborts the root transaction. Byte capping runs before
    acquiring the buffer write lock. Search updates publish only after root
    commit.
  • Catalog sources: separate SQL lifecycle hooks from wire options and give
    refresh waits and SQL requests their own deadline budgets. Catalog code
    receives the refresh budget from the client rather than importing coordinator
    internals.
  • Legacy result grid: generation-fence delayed rendering and pagination so
    an older page cannot replace the current result; expose recycled row indices
    to the stale-page regression test and document result-specific layout restore.
  • Request limiter: use a linked FIFO queue with constant-time cancellation
    of queued requests, preserving concurrency limits and ordering.
  • Recovery, toast and login UX: centred, danger-styled recovery UI; focus
    its heading and transfer focus into the recovered console; preserve Retry
    focus while busy; focus/describe sign-in errors; keep toast announcements
    available outside the SQL gate and during dialogs; use live theme tokens; warn
    both before and after token expiry; prevent incidental click dismissal;
    restore a dismissed warning on subsequent failures; provide expiry
    information, a sign-in action and Alt+N keyboard access. Renewal failures use
    consistent copy.
  • Test infrastructure and dependencies: add React 17 test-renderer and its
    types; extend unit and stubbed browser regressions; allowlist fake test tokens
    in gitleaks; update the QuestDB E2E submodule (26 commits, including
    permission-aware table visibility).
  • Enterprise CI: add .github/workflows/enterprise_e2e.yml, an opt-in Azure
    dispatch using vars.ENTERPRISE_E2E_ENABLED and secrets.ENT_DISPATCH_PAT.
    Fork PRs do not receive that secret. Private Enterprise source, binaries and
    live fixtures remain in the private repository. Dispatch success is not a
    passing integration result: check enterprise-e2e-web-console for the tested
    SHA. Setup and local execution are documented in e2e/README.md.

Review follow-up coverage

Review items Resolution
#1, #8, #23 Preserve confirmed script notifications; one shared finish path; settle LOADING once per script.
#2, #5, #6 Explicit not-sent renewal errors; no transient-failure logout threshold; retain pending 401 refresh recovery through its hard deadline.
#3, #9 Prepare byte-capped snapshots before the write transaction; retry metadata-only storage and warn after root abort.
#4, #7 Fence every live run generation; preserve error metadata; flush accepted throttled frames before session invalidation.
#10, #11 Reset confirmation state and invalidate waiting confirmations; clear shared credentials and cancel old telemetry timers.
#12–#15 Test current rotated tokens, loading-notification cancellation, root-commit search publication and deterministic loading cleanup.
#16–#20 Focused/centred recovery UI, actionable persistent toast with keyboard access, consistent renewal wording and simpler toast specificity.
#21, #22, #24–#28 Shared session handles/error guards; central authorization; pure token normalization; separate lifecycle options; remove unused helpers/reset hooks/payload fields; accurate local-error and commit-outcome types; clearer names/constants/comments.
#29 Document every shipped concern, the secret-using CI workflow and current validation results here, rather than describing this as only a refresh-loop change.

Validation

Validation of earlier PR head 908eba9dd43af4f7080599b4cb1f7760e2d65a67
(before the local six-issue follow-up):

  • yarn typecheck — passed.
  • yarn lint — passed, including color-token checks.
  • yarn build — passed (existing bundle-size advisory remains).
  • yarn test:unit — 2,925 tests passed in 153 files.
  • yarn cypress run --spec 'e2e/tests/auth/*.spec.js' --browser electron —
    57 passed against the production preview with stubbed HTTP responses:
    47 auth, 6 notification and 4 unauthorized-recovery tests.

Six-issue follow-up validated locally on 2026-10-16 and now published in
842d2545e84cd134c01fb719ed0b0ad71e3a3f81 (included in current PR head
75eae8bb9ccb3526cc3270eb38eb32a2f521ded1):

  • yarn typecheck, yarn lint and yarn build — passed.
  • yarn test:unit — 2,941 tests passed in 154 files.
  • yarn cypress run --spec 'e2e/tests/auth/*.spec.js' --browser electron —
    60 passed against the production preview with stubbed HTTP responses:
    50 auth, 6 notification and 4 unauthorized-recovery tests.

CI selector follow-up in 75eae8bb9ccb3526cc3270eb38eb32a2f521ded1:

  • Update the console editor regression to use the toast's current accessible
    name, Close notification, instead of the obsolete close selector.
  • Production build, targeted ESLint and Prettier checks — passed.
  • yarn cypress run --spec e2e/tests/console/editor.spec.js --browser electron
    completed with 122 tests passing and 1 pre-existing pending test. The local
    macOS Electron process then segfaulted during shutdown, so this was not a
    clean command exit. A focused rerun of the previously failing test passed
    with a clean exit against the real local QuestDB server.
  • New runs for the current head: CI
    and context-path tests.
    Their final results are not claimed by the local validation above.

The earlier 2,838-unit/38-auth figures described an older validation checkpoint,
not the current published head or the local follow-up. The full console E2E
suite and live Enterprise OIDC lane were not run locally. The Enterprise
rotation assertion was extended locally but remains unverified in the private
lane; the stubbed auth suite is not a substitute for live IdP/Enterprise tests.

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
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Web Console deploy preview

Preview Commit Logs
https://pr-603--web-console.netlify.app d5e350d build log

@glasstiger glasstiger changed the title fix: prevent auth token refresh failure from deadlocking the client fix: keep Web Console responsive when SSO token refresh fails Sep 28, 2026
Comment thread e2e/tests/enterprise/oidc.spec.js Outdated
Comment thread e2e/tests/enterprise/oidc.spec.js Outdated
Comment thread e2e/tests/enterprise/oidc.spec.js Outdated
The OIDC refresh-failure E2E test reuses the fake token fixtures used throughout oidc.spec.js. Ignore the findings in 0f4d4ac and mark the lines with gitleaks:allow so the squash commit on main is not flagged either.
@glasstiger glasstiger changed the title fix: keep Web Console responsive when SSO token refresh fails fix: fix Web Console hanging when SSO token refresh fails Sep 29, 2026
glasstiger and others added 7 commits September 29, 2026 10:56
The legacy grid mounts only after the first result arrives, so asserting
.qg-viewport right after reloading with useNewGrid=0 always timed out.
Assert it after the first query instead.

Also release the old query's page before the new result loads that page.
The previous scroll away and back could refetch the page, hiding the stale
write, so the test only sometimes failed without the generation guard.
When the server reads the user's groups from the ID token
(acl.oidc.groups.encoded.in.token), the console authenticates with the
ID token, yet it timed the refresh by the token response's expires_in,
which describes the access token. Time it by the ID token's exp claim
instead, and fall back to expires_in only when the claim is unreadable.

If the identity provider did not return the ID token the server
expects, at sign-in or on refresh, the console sent "Bearer undefined",
and the resulting 401s ended the session with no explanation. Show a
"Missing ID token" error instead, and never install a missing bearer.

The OAuth callback params stayed in the URL after a failed sign-in, so
a reload resubmitted the used code, and its invalid_grant error
replaced the original one. Restore the URL whatever the outcome, which
also keeps a shared query link across a failed sign-in.

A token endpoint that could not be reached, returned a response that
is not JSON, or did not respond left the sign-in on a blank screen.
Show an error instead, and give up after 10 seconds, the same timeout
as the refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
glasstiger and others added 3 commits October 14, 2026 01:28
A token response may omit expires_in, which OAuth only recommends. An
ID token bearer expires at its exp claim regardless, so it no longer
needs one. Without any known expiry, sign in anyway, but never refresh
the token, proactively or after a 401: such a provider typically issues
no refresh tokens either, so the token is used until the server rejects
it, and the user then signs in again.

A catalog request's 10s deadline kept running while the client waited
for the token refresh that follows a 401, so Query Activity and table
details could become unavailable even though the refresh succeeded.
The client now reports every refresh wait, and the catalog deadline
gives the refresh its own bounded time before restarting the SQL
timeout.

Modal dialogs hide the rest of the page from assistive technology,
except live regions, which hid the SSO refresh warning from screen
readers. Mount the notifications in a live region, as later
react-toastify versions do.

The refresh warning promised a retry, which does not happen while a
timed-out refresh request is still pending. State only the consequence.

The enterprise reload test returned expires_in: 0, which is not a valid
lifetime. Use 300 seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settle execution-bound loading notifications and make cancellation idempotent. Stop scripts safely across authentication teardown, preserving cleanup and suppressing stale results.

Add regressions for detached session isolation, script auth loss, catalog switching during shared refresh, and telemetry auth-gate ordering and refreshed SSO authorization.
glasstiger added a commit to questdb/documentation that referenced this pull request Oct 14, 2026
… renewal and Java token providers

Align the OIDC, RBAC and client pages with questdb/questdb-enterprise#1267,
questdb/questdb#7799, questdb/ui#603 and questdb/java-questdb-client#102.

- Issuer validation (QuestDB Enterprise 4.0.2): acl.oidc.issuer and
  acl.oidc.issuer.validation.enabled, a new "The expected issuer" section
  with where the issuer comes from, the issuers of Entra ID v1.0 and v2.0
  tokens, the common/organizations templates, issuers that depend on the
  host name of the request, the opt-out and its advisory, the
  InvalidIssuer and MissingRequiredClaim("iss") log lines, and the
  upgrade notes. acl.oidc.host needs acl.oidc.issuer unless validation
  is disabled. PGWire tokens need the expected issuer too.
- HTTP sessions: a request with a credential that QuestDB rejects, or
  cannot check because the provider is unreachable, gets 401 and ends
  its session. Sessions end with the password or REST token that opened
  them, or when the user is disabled or dropped, and on a replica when
  they are next used. acl.oidc.cache.ttl reuses only the check of a
  password that QuestDB verifies itself, even with OIDC disabled.
- Provider outages: with the User Info endpoint, Web Console users are
  logged out once acl.oidc.cache.ttl has passed; with tokens validated
  locally, once the ID token expires; ROPC logins need the token
  endpoint in both modes.
- Web Console: it renews the ID token by its exp claim, keeps its token
  while it cannot reach the provider, and without a refresh token logs
  the user out when the token of the login expires. Remove the stale
  "logged out about an hour after the login" text from the OIDC page
  and the Entra ID guide; Entra ID limits the refresh tokens of
  single-page applications to 24 hours. Its password login sends a REST
  token that survives a password change, so drop the user's REST tokens
  to log the user out.
- Java client: "Rotating tokens" with token_provider=azure, the
  questdb-client-azure artifact, RefreshingTokenProvider and how token
  failures are handled, "Connection health", the token provider keys in
  the connect string reference, and a Java example in the Entra ID
  guide.
- With the User Info endpoint, QuestDB treats the access token as opaque.
- Changelog entries for the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fence headless commits and refresh-engine result persistence against ended authentication sessions, and tear down refresh work during layout cleanup.

Skip loading notification settlement when an execution never published loading, and add regression coverage for session races and fast queries.
Keep schema compatibility attempts within their originating auth session. Commit headless notebook metadata and snapshots together, rolling back session-invalidated writes and publishing search updates only after commit.

Keep loading notifications out of retained history and settle them through a query-ID index that follows rekeys and namespace moves. Add regression coverage for all three review findings.

Validation: typecheck, lint, 2813 unit tests across 145 files, and production build passed.
@glasstiger

glasstiger commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Code Review — APPROVED

Review outcome: APPROVED. This level-3 PR review found no confirmed introduced defects or blocking findings. The validation limits below remain applicable.

Issues

No confirmed introduced defects found in PR #603 at 7199d6181bcc8ec6d6fe1b8df94a9438db8b5da9.

The level-3 review covered all 13 review domains, including the independent adversarial pass, plus a wider follow-up on unchanged callers. No broken out-of-diff consumer was established.

False-positives

No draft findings were submitted, so none required dismissal.

The supplemental caller check classified unfenced raw-client requests and store-owned telemetry continuations as preexisting behavior, not regressions introduced by this PR.

Summary

Review verdict: APPROVED, based on source review and passing local quality gates.

Local check Result
yarn typecheck Passed
yarn lint Passed
yarn build Passed
yarn test:unit 2,817 tests passed across 145 files

Validation limits:

  • Public CI and context-path tests were still in progress when this comment was posted.
  • Enterprise E2E dispatch was skipped; live OIDC behavior and IdP token rotation remain unverified.
  • Cypress suites and screen-reader behavior were not exercised locally.
  • The QuestDB test-fixture gitlink update was considered as a UI testing dependency, not an independent audit of its 22 server commits.

The PR combines authentication, notebook persistence, notification cleanup, grid pagination, and test-fixture changes. No concrete regression was established, but the passing local gates do not replace the outstanding integration checks.

Findings: 0 confirmed, 0 draft findings dropped; 0 in-diff / 0 out-of-diff. No source files were changed during the review.

@glasstiger

glasstiger commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Level-3 review — Approved

Reviewed PR #603 at 7199d6181bcc8ec6d6fe1b8df94a9438db8b5da9.

Review notes

No introduced functional defects were confirmed.

ID Issue name Category Severity Location Description Steps to reproduce Suggested fix
#1 Stale server fixture scope Test review & coverage Minor in-diff — e2e/questdb:1, compared with PR description The description states that the fixture advances 22 commits to ea37707be2. The actual gitlink advances 23 commits to 53296e5952, additionally including server PR questdb/questdb#7912’s nosync/msync changes. Reviewers following the documented range omit that change. This is inaccurate review/bisection metadata, not a shipped-dependency defect. • Compare the PR body’s “E2E QuestDB checkout” paragraph with the gitlink diff.
• Count commits between the old and actual new pins: 23.
Update the description’s endpoint, count, and additional-change summary. No source or gitlink correction is necessary.

False-positives

Both candidates describe real existing behavior, but neither was introduced by this PR.

Category Draft finding Why dismissed
React correctness & hooks Pending AI SQL can survive logout and execute after re-login. The baseline manager also retains pending query B after active query A finishes. Its refresh drain merely postpones logout; it does not invalidate B. Confirmation after re-login can therefore execute B on the baseline too.
Async, timers & cancellation Removing refresh-time draining lets an old Metrics response overwrite the newly selected table’s data/error state. The baseline already permits overlapping metric requests whenever no refresh is needed. Selecting B while A remains pending produces the same stale-data/error behavior.

These remain separate follow-up issues. Their dismissal here concerns regression attribution, not safety. Seven targeted probes—including baseline-Client comparisons—corroborated the findings.

Summary

Outcome: Approved, with the non-blocking PR-description correction above.

The review completed all 13 domain passes, per-finding verification, and a wider follow-up on unchanged callers. No new functional regression was established across authentication, query execution, notebook persistence, notifications, catalog deadlines, or grid pagination.

Local check Result
yarn typecheck Passed
yarn lint Passed
yarn build Passed
yarn test:unit 2,817 tests passed across 145 files

Public CI and context-path tests passed for the reviewed SHA. Enterprise E2E dispatch was skipped; live IdP/EE behavior and screen-reader announcements remain unverified. Local Cypress suites were not run, and the server-fixture range was not independently audited as server code.

Verification totals: 3 candidates checked; 1 minor metadata issue confirmed, 2 dismissed as preexisting. Location split: 1 in-diff/gitlink metadata finding, 0 introduced out-of-diff defects.

No source files were changed during the review.

Address the PR review findings across token renewal, script outcomes, notebook persistence, session teardown, accessibility, and auth types. Add regression coverage and document the complete PR scope.\n\nValidated with typecheck, lint, production build, 2,838 unit tests, and 38 stubbed auth browser tests.
Preserve never-started notebook run outcomes, reject stale headless results before serialization, and fix dialog and login focus handling.

Update persistent refresh warnings at token expiry, add regression coverage, and correct the PR description shortcut and fixture count.
Restore SSO refresh capability across provider remounts, recover schema after local renewal failures, and preserve no-send outcomes for queued headless reads.

Settle script loading independently of abort targets, defer accepted snapshot preparation until after session invalidation, and align headless cancellation metadata.

Add 14 regression tests. Validated with typecheck, lint, build, and 2,887 unit tests across 151 files.
Keep transient refresh failures recoverable after SQL 401 responses, preserve password form focus, and restore shortcut focus when closing notifications.

Simplify loading notification settlement to use only the query ID and extract the shared button base without tooltip dependencies. Add unit and browser regression coverage.
Prevent obsolete initial code exchanges from replacing recovered logins, coordinate recovery across mounted schema panels, and report validation-only notebook runs as never executed. Add regression coverage for all three review findings.
@glasstiger

Copy link
Copy Markdown
Contributor Author

Level-3 code review

Issues

No confirmed introduced defects found in PR #603, reviewed at d1badd7823bcdeacfa6c491cdc38e1539828ced9.

The level-3 review completed all 13 independent passes and a widened follow-up on unchanged callers. No broken out-of-diff contract was established.

False-positives

No draft findings were submitted for per-finding verification.

Two exploratory concerns were resolved during the caller follow-up:

Category Concern Disposition
Cross-context caller impact Legacy-grid layout hashing could finish asynchronously after result replacement. Dismissed: layoutStoreComputeKeyAndHash computes the hash and invokes its callback synchronously. Deferred rendering and pagination are generation-guarded.
Cross-context caller impact Late Schema/AI-action error toasts could survive logout and appear after re-login. Not established as an introduced defect. Toastify unregisters asynchronously, so synchronous logout followed by a promise catch does not prove cross-login queuing. Similar delayed-error paths existed in the baseline. Browser-level verification remains outstanding.

Summary

Verdict: approve, based on source review and passing local quality gates—not a completed CI or live-integration sign-off.

Local check Result
yarn typecheck Passed
yarn lint Passed
yarn build Passed; bundle-size advisory remains
yarn test:unit 2,954 tests passed across 155 files

Validation limits at review completion:

  • Public CI and context-path tests were still pending at the final review check.
  • Enterprise E2E dispatch was skipped; live IdP and token-rotation behavior remain unverified.
  • Cypress suites and screen-reader behavior were not exercised locally.
  • The E2E server fixture’s 24-commit advance was checked as test infrastructure, not independently audited as server code.

No introduced regression was confirmed across authentication, query execution, notebook persistence, notifications, catalog recovery, or grid replacement.

Totals: 0 confirmed findings; 0 draft findings requiring verification. 0 in-diff / 0 out-of-diff defects. No source files were changed.

Resolve all 32 review findings covering refresh coordination, session isolation, keyboard access, schema recovery, notebook persistence, and regression coverage. Remove the stale PR draft and unrelated server bump.
Address the Moderate and Minor review findings for PR 603, including toast accessibility, CSV upload races, interruption history, session fencing, schema recovery, settings feedback, and non-renewing telemetry. Consolidate shared helpers and extend regression coverage.

Validated typecheck, lint, production build, 3,031 unit tests, and 64 stubbed Chrome browser-auth tests. Live Enterprise OIDC and manual screen-reader validation remain separate checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant