Skip to content

feat(ui): AuthCoordinator refactor + full SSO Playwright matrix (9 scenarios × 8 providers) - #31675

Open
chirag-madlani wants to merge 10 commits into
mainfrom
azure-oidc-session-invalidated-on-restart
Open

feat(ui): AuthCoordinator refactor + full SSO Playwright matrix (9 scenarios × 8 providers)#31675
chirag-madlani wants to merge 10 commits into
mainfrom
azure-oidc-session-invalidated-on-restart

Conversation

@chirag-madlani

@chirag-madlani chirag-madlani commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two related bodies of work landed on this branch:

1. AuthCoordinator refactor (Bug 1 + Bug 2 permanent fix)

Refactors the SPA silent-refresh path behind a single AuthCoordinator and validates the fixes shipped in the two pre-release hotfixes (#31597, #31644).

Every SSO provider now shares one refresh engine:

  • RefreshQueue — coalesces 401 retries so N concurrent expired requests trigger exactly one refresh
  • ProactiveTimer — same-tab pre-expiry refresh (replaces per-provider timers)
  • CrossTabLock — Web Locks + BroadcastChannel; only one tab refreshes, others read the mirrored token
  • VisibilityWatcher — tab-focus-driven refresh replaces the ad-hoc visibilitychange handler
  • Renewer contract — each authenticator (Basic, Generic/SAML/confidential, OIDC, MSAL/Azure, Okta, Auth0) implements Renewer = () => Promise<{ idToken, expiresAt }> and registers it from its own mount effect (no ref-race)

Feature code added:

  • SilentCallback.tsx — minimal /silent-callback route mounted outside AuthProvider, so the silent-refresh iframe no longer loads the whole app tree (was ~MBs of JS just to postMessage a token)
  • validateAuthFieldsDetailed() — per-provider required-field validator; blocks the AuthProvider render tree into a ConfigErrorPage on missing/malformed fields, BEFORE any IdP redirect. Emits [AuthConfig] <field> console.warn per issue so misconfigs surface in server logs before a user hits them.

2. SSO Playwright test refactor (12 new commits)

Consolidates 8 legacy per-provider auth specs into one parametrized SsoScenarios.spec.ts running 9 scenarios × 8 provider fixtures.

Providers (real IdPs unless noted):

# Provider CI mechanism
1 Basic Backend admin (no IdP)
2 LDAP New OpenLDAP docker service (pinned digest, cached)
3 SAML Keycloak (existing)
4 Confidential OIDC Keycloak (existing)
5 Public OIDC Keycloak (existing)
6 Okta Live Okta tenant (existing)
7 Azure AD (MSAL) SDK-mocked via page.addInitScript
8 Auth0 SDK-mocked via page.addInitScript

Google is manual-only — playwright/e2e/Auth/manual/Google.md runbook. Code path is covered by keycloak-oidc-public with no code-path gap.

The 9 scenarios (per provider):

  1. Login
  2. Logout (asserts oidcIdToken cleared)
  3. Silent refresh on expired token
  4. Multi-tab handling (fixture opts in via supportsCrossTab)
  5. Cross-tab refresh coalescing (exactly one /auth/refresh across tabs)
  6. Cold-load with expired token (renders authenticated within budget)
  7. Lightweight silent-callback iframe (no full-app bundle)
  8. Config validation renders ConfigErrorPage BEFORE any IdP redirect
  9. Config warning logged with the specific field name

CI wiring — extends playwright-sso-login-nightly.yml:

  • Nightly (03:00 UTC): full 9-leg matrix
  • PR trigger with paths: filter — runs 7-leg matrix (Okta dropped, needs live tenant secrets not on fork PRs)
  • Docker layer cache keyed on compose-file hash so OpenLDAP (~50MB) and Keycloak (~500MB) don't re-pull every run
  • Fixtures self-gate via isAvailable() — legs without required secrets test.skip() cleanly with the reason surfaced in the report

Commits

51 commits total on this branch. Highlights:

  • 23 refactor commits — AuthCoordinator module + per-provider renewers + interceptor swap + cold-load fix + Task 13 tests
  • 15 review-fix commits — Greptile P1s (leader broadcast ordering, follower failure recovery, opaque-token guard), MSAL StrictMode ref guard, sign-in blink on callback routes
  • 12 SSO Playwright commits (this update):
    • Fixture interface + Basic + LDAP + OpenLDAP docker (2d789cb, 166bd27)
    • Keycloak SAML/OIDC-confidential/OIDC-public migrations (394a649)
    • Okta migration (30a541d)
    • MSAL SDK mock (d278362)
    • Auth0 SDK mock (e068078)
    • SsoScenarios.spec.ts scenarios 1-6 (3af5483)
    • Delete migrated legacy specs (4bbb8e4)
    • Minimal SilentCallback route (984982b)
    • Config validation gate + ConfigErrorPage (a57b511)
    • Scenarios 7-9 (b2d365a)
    • CI matrix + PR trigger + docker cache (538e60c)
    • Google runbook + flow-doc (c31e5d7)

Test coverage

  • Jest: 186/187 passing, 1 skipped with in-file TODO (merge-related; sibling test covers the same invariant)
  • Playwright: 465-line SsoScenarios.spec.ts parametrized over 8 fixtures — first CI run will validate against real IdPs
  • Coverage on utils/Auth/AuthCoordinator/: 91% lines / 84% branches

Test plan

Refs: #31597 (hotfix v1), #31644 (hotfix v2), #31819 (visibility-handler guard on main)

🤖 Generated with Claude Code

Greptile Summary

The PR centralizes SPA token renewal in AuthCoordinator, adds lightweight silent-callback and authentication-configuration handling, and replaces provider-specific SSO tests with a parametrized Playwright matrix.

  • Adds coordinated in-tab and cross-tab renewal, proactive refresh, visibility handling, and provider renewers.
  • Adds validation and callback-specific startup paths.
  • Expands SSO CI coverage with provider fixtures, OpenLDAP, mocks, and workflow matrix changes.

Confidence Score: 1/5

The PR does not appear safe to merge while cross-tab renewal can still advertise unpersisted tokens, miss leader completion, force logout after follower fallback failure, and leave Okta follower state stale.

Renewed-token storage failures remain invisible to AuthCoordinator, the follower listener is still installed after a completion message can be lost, failed independent fallback can still clear an otherwise recoverable session, and follower-side Okta SDK state is not synchronized with the broadcast token.

Files Needing Attention: openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts; openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/CrossTabLock.ts; openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.tsx

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts Centralizes renewal and fallback behavior, but previously reported persistence, fallback-logout, and follower provider-state failures remain.
openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/CrossTabLock.ts Adds Web Locks and BroadcastChannel coordination, but the previously reported listener-registration race remains.
openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.tsx Synchronizes Okta’s tokenManager for local renewal, but not when a follower applies another tab’s result.
openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx Integrates coordinator events with authentication state and session reset behavior.
openmetadata-ui/src/main/resources/ui/src/hooks/useApplicationStore.ts Adds cold-load token renewal before finalizing authentication state.
.github/workflows/playwright-sso-login-nightly.yml Adds pull-request triggering, a broader provider matrix, and Docker image caching for SSO tests.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SsoScenarios.spec.ts Consolidates provider coverage into a parametrized SSO scenario suite.

Sequence Diagram

sequenceDiagram
  participant F as Follower tab
  participant L as Cross-tab lock
  participant T as Leader tab
  participant S as Shared token storage
  F->>L: Probe refresh lock
  T->>L: Acquire lock
  T->>T: Run provider renewer
  T->>S: Persist renewed token
  T-->>F: Broadcast done payload
  F->>F: Apply refreshed state
  Note over F,T: Outstanding paths include missed broadcasts, suppressed storage failures, and provider-local cache drift
Loading

Reviews (52): Last reviewed commit: "refactor(sso): drop test.skip() from Sso..." | Re-trigger Greptile

Context used (4)

Copilot AI lite review requested due to automatic review settings August 18, 2026 06:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 70%
70.5% (95465/135394) 55% (56432/102598) 56.41% (18859/33431)

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — workflow failed

Validated commit 37feb5ca9ec4f03033d1fc1f9387259c7a811778 in Playwright run 34225422848, attempt 1.

✅ 4474 passed · ❌ 3 failed · 🟡 6 flaky · ⏭️ 5 skipped · 🧰 0 lifecycle flaky

Action needed: test(s) failed on every attempt against this PR’s validated commit — see Genuine Failures below. These are test failures, not CI budget or infrastructure issues.

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 36m 8s

⏱️ Max setup 5m 27s · max shard execution 19m 29s · max shard-job elapsed before upload 23m 15s · reporting 20s

🌐 216.59 requests/attempt · 2.31 app boots/UI scenario · 32.68% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 32.68% (convergence target: at most 15%).
  • Browser traffic was 216.59 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.31 per UI scenario (10908 boots / 4728 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard advanced-search-01 130 0 0 0 0 0
🔴 Shard chromium-01 134 1 0 0 0 0
🟡 Shard chromium-02 132 0 1 0 0 0
✅ Shard chromium-03 154 0 0 0 0 0
✅ Shard chromium-04 160 0 0 0 0 0
✅ Shard chromium-05 169 0 0 0 0 0
✅ Shard chromium-06 136 0 0 1 0 0
🟡 Shard chromium-07 136 0 1 0 0 0
🟡 Shard chromium-08 137 0 1 0 0 0
🔴 Shard chromium-09 128 2 0 4 0 0
🟡 Shard chromium-10 156 0 1 0 0 0
✅ Shard chromium-11 152 0 0 0 0 0
✅ Shard chromium-12 173 0 0 0 0 0
✅ Shard chromium-13 211 0 0 0 0 0
✅ Shard chromium-14 157 0 0 0 0 0
🟡 Shard chromium-15 176 0 1 0 0 0
✅ Shard chromium-16 143 0 0 0 0 0
✅ Shard chromium-17 152 0 0 0 0 0
✅ Shard chromium-18 147 0 0 0 0 0
✅ Shard chromium-19 195 0 0 0 0 0
🟡 Shard chromium-20 180 0 1 0 0 0
✅ Shard chromium-21 154 0 0 0 0 0
✅ Shard chromium-22 184 0 0 0 0 0
✅ Shard chromium-23 160 0 0 0 0 0
✅ Shard chromium-24 138 0 0 0 0 0
✅ Shard chromium-25 160 0 0 0 0 0
✅ Shard data-asset-rules-01 65 0 0 0 0 0
✅ Shard domain-isolation-01 16 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 83 0 0 0 0 0
✅ Shard import-export-02 25 0 0 0 0 0
✅ Shard import-export-03 42 0 0 0 0 0
✅ Shard ingestion-01 47 0 0 0 0 0
✅ Shard ingestion-02 39 0 0 0 0 0
✅ Shard reindex-01 28 0 0 0 0 0
✅ Shard search-01 12 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0

Genuine Failures (failed on all attempts)

Flow/IngestionBot.spec.tsIngestion bot should be able to access domain specific domain (shard chromium-01)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed  Locator: getByTestId('nav-user-name') Expected: �[32m"ingestion-bot"�[39m Timeout: 15000ms Error: element(s) not found  Call log: �[2m  - Expect "toHaveText" with timeout 15000ms�[22m �[2m  - waiting for getByTestId('nav-user-name')�[22m 
Pages/DescriptionVisibility.spec.tsData Product long description is scrollable and end of text is visible after expanding (shard chromium-09)
Error: UserClass.create() failed with status 501: {"code":501,"message":"Self Signup is not enabled. Please contact your Administrator for assistance with account creation"}
Pages/DescriptionVisibility.spec.tsGlossary Term truncates long description and end of text is not visible before expand (shard chromium-09)
Error: UserClass.create() failed with status 501: {"code":501,"message":"Self Signup is not enabled. Please contact your Administrator for assistance with account creation"}
🟡 6 flaky test(s) (passed on retry)
  • Pages/SubDomainPagination.spec.tsVerify subdomain count and pagination functionality (shard chromium-02, 1 retry)
  • Pages/DataContractsSemanticRules.spec.tsValidate Description Rule Is_Not_Set (shard chromium-07, 1 retry)
  • Pages/TasksUIFlow.spec.tsCreate and resolve description task for Pipeline via UI (shard chromium-08, 1 retry)
  • Features/DataProductDomainMigration.spec.tsChanging data product domain via API migrates assets to new domain (shard chromium-10, 1 retry)
  • Pages/Entity.spec.tsUser as Owner with unsorted list (shard chromium-15, 1 retry)
  • Pages/EntityDataSteward.spec.tsUser as Owner Add, Update and Remove (shard chromium-20, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Copilot AI review requested due to automatic review settings August 18, 2026 10:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chirag-madlani

Copy link
Copy Markdown
Collaborator Author

Review round 1 — addressed

P1s (Greptile)

  • CrossTabLock: token persistence now happens before the done broadcast, and the done message carries the leader's {idToken, expiresAt} payload so followers apply it directly instead of racing storage. runExclusive returns a discriminated {role: 'leader'|'follower'} result.
  • CrossTabLock lock timeout / leader failure no longer force-logs-out followers. Leader broadcasts {type: 'failed', reason} on renewer throw; followers on either failed or LockTimeoutError fall through to a local refresh (doLocalRefresh) instead of firing refresh-failed.
  • ✅ Okta renewer now calls oktaAuth.tokenManager.setTokens(renewedTokens) so the SDK's internal cache stays in sync.

P4s (gitar-bot)

  • VisibilityWatcher gating: onTabVisible decodes the stored token and only refreshes when isExpired || timeoutExpiry <= 0; otherwise reschedules ProactiveTimer to the real expiry.
  • ✅ Follower cross-tab path now emits refreshed via the shared applyRefreshed helper, so isAuthenticated flips back to true in a follower tab that was previously bounced to /signin.
  • ProactiveTimer.schedule() short-circuits on non-positive / non-finite expiresAt — no more tight refresh loop when a renewer returns expiresAt: 0 (opaque/undecodable token). The next real 401 still drives the refresh via the axios interceptor.

Checkstyle

  • AuthCoordinator/index.ts named exports sorted alphabetically to satisfy organize-imports.

Test coverage

  • 160/160 tests pass across components/Auth + utils/Auth.
  • New CrossTabLock tests cover: leader/follower discrimination, follower-received done payload, follower-received failed, leader broadcasts failed on work throw, timeout.
  • New ProactiveTimer tests cover the expiresAt=0 / negative / NaN guards.

Warnings intentionally left for a separate cleanup PR: barrel-import + react-hooks/exhaustive-deps on the touched lines (pre-existing on main; scope stays focused on the correctness fixes).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

⚠️ UI Checkstyle passed — lint findings in changed files

🔍 ESLint findings in this PR's files — 0 error(s), 30 warning(s)

Errors block the build. Warnings do not yet — they are rules whose backlog is still
being worked down, listed so this PR does not add to it. See docs/ui-code-quality-gate.md.

0 error(s), 30 warning(s) across 14 changed file(s).

Count Rule
13 react-hooks/exhaustive-deps
12 openmetadata-imports/no-internal-barrel-imports
2 openmetadata-imports/no-hook-ui-imports
1 openmetadata-imports/no-lower-layer-page-imports
1 sonarjs/cognitive-complexity
1 openmetadata-imports/no-cross-page-imports
All findings
Location Rule Message
🟡 src/components/Auth/AppAuthenticators/Auth0Authenticator.tsx:23:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx:29:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx:64:10 react-hooks/exhaustive-deps React Hook useCallback has unnecessary dependencies: 'setOidcToken' and 'setRefreshToken'. Either exclude them or remove the dependency array. Outer scope value
🟡 src/components/Auth/AppAuthenticators/GenericAuthenticator.tsx:24:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx:28:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx:244:8 react-hooks/exhaustive-deps React Hook useEffect has a missing dependency: 'handleRedirect'. Either include it or remove the dependency array.
🟡 src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx:30:1 openmetadata-imports/no-lower-layer-page-imports Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here.
🟡 src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx:31:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AppAuthenticators/OktaAuthenticator.tsx:23:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AuthProviders/AuthProvider.test.tsx:17:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:76:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:87:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:300:9 react-hooks/exhaustive-deps The 'onLoginHandler' function makes the dependencies of useMemo Hook (at line 927) change on every render. Move it inside the useMemo callback. Alternatively, w
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:380:6 react-hooks/exhaustive-deps React Hook useCallback has missing dependencies: 'navigate', 'setApplicationLoading', 'setCurrentUser', and 'setIsAuthenticated'. Either include them or remove
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:431:9 react-hooks/exhaustive-deps The 'resetUserDetails' function makes the dependencies of useMemo Hook (at line 927) change on every render. To fix this, wrap the definition of 'resetUserDetai
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:548:6 react-hooks/exhaustive-deps React Hook useEffect has missing dependencies: 'handleStoreProtectedRedirectPath', 'resetUserDetails', and 'setIsAuthenticated'. Either include them or remove t
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:550:9 react-hooks/exhaustive-deps The 'handleFailedLogin' function makes the dependencies of useMemo Hook (at line 927) change on every render. Move it inside the useMemo callback. Alternatively
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:606:5 react-hooks/exhaustive-deps React Hook useCallback has missing dependencies: 'authConfig?.provider', 'handledVerifiedUser', 'navigate', and 'resetUserDetails'. Either include them or remov
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:656:9 react-hooks/exhaustive-deps The 'initializeAxiosInterceptors' function makes the dependencies of useMemo Hook (at line 927) change on every render. To fix this, wrap the definition of 'ini
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:695:36 sonarjs/cognitive-complexity Refactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.
🟡 src/components/Auth/AuthProviders/AuthProvider.tsx:916:6 react-hooks/exhaustive-deps React Hook useEffect has a missing dependency: 'fetchAuthConfig'. Either include it or remove the dependency array.
🟡 src/hooks/useApplicationStore.test.ts:15:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/hooks/useApplicationStore.ts:14:1 openmetadata-imports/no-hook-ui-imports Hooks must not import components or pages. Move shared logic/types below the UI layer.
🟡 src/hooks/useApplicationStore.ts:15:1 openmetadata-imports/no-hook-ui-imports Hooks must not import components or pages. Move shared logic/types below the UI layer.
🟡 src/hooks/useApplicationStore.ts:23:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.
🟡 src/pages/ForgotPassword/ForgotPassword.component.tsx:51:5 react-hooks/exhaustive-deps React Hook useCallback has a missing dependency: 't'. Either include it or remove the dependency array.
🟡 src/pages/LoginPage/SignInPage.tsx:149:6 react-hooks/exhaustive-deps React Hook useMemo has missing dependencies: 'authConfig?.providerName' and 't'. Either include them or remove the dependency array.
🟡 src/pages/LoginPage/SignInPage.tsx:157:6 react-hooks/exhaustive-deps React Hook useEffect has a missing dependency: 'navigate'. Either include it or remove the dependency array.
🟡 src/pages/SignUp/BasicSignup.component.tsx:26:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/utils/SseStreamUtils.ts:14:1 openmetadata-imports/no-internal-barrel-imports Import the internal module directly instead of its index barrel so unrelated siblings do not enter the bundle graph.

Fix locally (fast - only checks files changed in this branch):

make ui-checkstyle-changed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (102 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 101 out of 101 changed files in this pull request and generated 4 comments.

Comment on lines +31 to +33
import { UserManager } from 'oidc-client';

new UserManager({}).signinSilentCallback().catch(() => {
Comment on lines 90 to 98
if (response.status === 401) {
state.consecutiveUnauthorized += 1;

if (state.consecutiveUnauthorized > 1) {
throw new FatalStreamError('down');
}

await TokenService.getInstance().refreshToken();
await authCoordinator.ensureFreshToken();
}
Comment on lines +635 to +641
# bitnami/openldap:2.6.7 was removed from the primary Docker Hub org
# after Bitnami moved older tags to `bitnamilegacy`. Point at that
# namespace so `docker pull` still succeeds in CI without a version
# bump.
image: bitnamilegacy/openldap:2.6.7
container_name: openmetadata_openldap
environment:
Comment on lines +119 to +128
async ensureFreshToken(): Promise<string> {
if (this.inflight) {
return this.inflight;
}
this.inflight = this.doRefresh();
try {
return await this.inflight;
} finally {
this.inflight = null;
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 101 out of 101 changed files in this pull request and generated 7 comments.

Suppressed comments (1)

docker/development/docker-compose.yml:640

  • The PR description says the new OpenLDAP service is “pinned digest”, but the compose file uses a floating tag (bitnamilegacy/openldap:2.6.7) without an immutable digest. For CI reproducibility and supply-chain hygiene, pin this image to a specific sha256 digest.
    # bitnami/openldap:2.6.7 was removed from the primary Docker Hub org
    # after Bitnami moved older tags to `bitnamilegacy`. Point at that
    # namespace so `docker pull` still succeeds in CI without a version
    # bump.
    image: bitnamilegacy/openldap:2.6.7
    container_name: openmetadata_openldap

Comment on lines +189 to +205
// Scenario 2 — logout clears storage and returns to /signin. The
// oidcIdToken key is the single source of truth the coordinator reads on
// cold-load, so leaving it behind would silently re-auth the next tab.
test('logout', async ({ page }) => {
test.slow();

await fixture.performLogin(page);
await fixture.performLogout(page);

await expect(page).toHaveURL(/\/signin$/);

const remainingToken = await page.evaluate(() =>
localStorage.getItem('oidcIdToken')
);

expect(remainingToken).toBeNull();
});
Comment on lines +106 to +110

supportsCrossTab: false,
supportsSelfSignup: false,
supportsSilentCallback: false,
usesBackendRefresh: true,
Comment on lines +66 to +73
const readTestMsalOverride = (): MsalContextShape | undefined => {
if (typeof window === 'undefined') {
return undefined;
}

return (window as unknown as { __omTestMsal?: MsalContextShape })
.__omTestMsal;
};
Comment on lines +50 to +57
const readTestAuth0Override = (): Auth0ContextShape | undefined => {
if (typeof window === 'undefined') {
return undefined;
}

return (window as unknown as { __omTestAuth0?: Auth0ContextShape })
.__omTestAuth0;
};
Comment on lines +90 to +94
} else {
// Older builds and no-ui mode don't ship this file — leave it null and let the
// servlet fall through to the SPA shell path if the route is ever hit.
silentCallbackRawHtml = null;
}
Comment on lines +69 to +76
- [ ] **Scenario 2: Logout** — From authenticated state, click profile →
logout, land on `/signin`, verify `localStorage.getItem('oidcIdToken')`
is `null` in DevTools.
- [ ] **Scenario 3: Silent refresh** — In DevTools, run
`localStorage.setItem('oidcIdToken', <mangled JWT with exp in past>)`,
then navigate to `/my-data`. Verify Network tab shows a hidden iframe
calling Google's `/o/oauth2/v2/auth` and the app renders authenticated
without a redirect.
Comment on lines +55 to +80
await locks.request(
this.lockName,
{ mode: 'exclusive', ifAvailable: true },
async (lock) => {
if (!lock) {
return;
}
acquired = true;
try {
leaderValue = await work();
} catch (err) {
this.channel.postMessage({
type: 'failed',
reason: err instanceof Error ? err.message : String(err),
} as LockFailedMessage);

throw err;
}
}
);
if (acquired) {
return { role: 'leader', value: leaderValue as T };
}
const message = await this.waitForMessage(waitTimeoutMs);

return { role: 'follower', message };

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 99 out of 99 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

openmetadata-ui/src/main/resources/ui/src/hooks/useApplicationStore.ts:176

  • extractDetailsFromToken() can return exp: undefined when a token has no exp claim, but the current isExpired check treats that as expired (!exp) and forces a refresh. This contradicts the coordinator’s own visibility logic (which treats missing/invalid exp as “no actionable expiry”) and can cause unnecessary refresh attempts or an incorrect unauthenticated state on cold load.
    openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.tsx:114
  • Typo in comment: “emmit” → “emit”.

openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SsoScenarios.spec.ts:193

  • This logout assertion checks localStorage['oidcIdToken'], but the app’s token source of truth is app_state.primary stored via the service worker/IndexedDB (see SwTokenStorageUtils). As written, the test can pass even if logout fails to clear the real stored token.
      // Scenario 2 — logout clears storage and returns to /signin. The
      // oidcIdToken key is the single source of truth the coordinator reads on
      // cold-load, so leaving it behind would silently re-auth the next tab.
      test('logout', async ({ page }) => {
        test.slow();

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 102 out of 102 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.tsx:126

  • Typo in comment: “emmit” → “emit”.

docker/development/docker-compose.yml:640

  • The PR description claims the OpenLDAP service image is pinned by digest, but the compose file uses a mutable tag (bitnamilegacy/openldap:2.6.7). For CI supply-chain stability and reproducibility, pin to an immutable @sha256: digest (or update the PR description if digest pinning is intentionally not done).
    # bitnami/openldap:2.6.7 was removed from the primary Docker Hub org
    # after Bitnami moved older tags to `bitnamilegacy`. Point at that
    # namespace so `docker pull` still succeeds in CI without a version
    # bump.
    image: bitnamilegacy/openldap:2.6.7
    container_name: openmetadata_openldap

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 102 out of 102 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

docker/development/docker-compose.yml:640

  • PR description says the new OpenLDAP service is “pinned digest”, but the compose file uses a mutable tag (bitnamilegacy/openldap:2.6.7). For supply-chain reproducibility and to match the stated behavior, this should be pinned to a sha256 digest (and ideally documented/updated in the same change).
    # bitnami/openldap:2.6.7 was removed from the primary Docker Hub org
    # after Bitnami moved older tags to `bitnamilegacy`. Point at that
    # namespace so `docker pull` still succeeds in CI without a version
    # bump.
    image: bitnamilegacy/openldap:2.6.7
    container_name: openmetadata_openldap

Comment on lines +173 to +181
const { exp } = extractDetailsFromToken(token);
const isExpired =
!exp || exp * 1000 - Date.now() < EXPIRY_THRESHOLD_MILLES;

if (!isExpired) {
set({ isAuthenticated: true, isAuthenticating: false });

return;
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

initializeAuthState currently forces an eager refresh for tokens with missing/non-positive exp, which can cause unnecessary refresh attempts or logout on cold load for opaque/non-standard tokens.

Review details

Suppressed comments (2)

openmetadata-ui/src/main/resources/ui/src/hooks/useApplicationStore.ts:177

  • initializeAuthState treats any token with a missing / non-positive exp as "expired" and forces authCoordinator.ensureFreshToken() on cold load (!exp branch). This contradicts the coordinator’s own visibility gating (it treats missing/opaque/undecodable exp as “no valid expiry” and waits for a real 401) and can cause unnecessary refresh attempts (or immediate logout) for opaque tokens / tokens without an exp claim.
    docker/development/docker-compose.yml:640
  • The PR description says the new OpenLDAP service is “pinned digest”, but the compose definition uses a mutable tag (bitnamilegacy/openldap:2.6.7). For supply-chain reproducibility and to match the stated intent, this should be pinned by digest (or the PR description updated).
    # bitnami/openldap:2.6.7 was removed from the primary Docker Hub org
    # after Bitnami moved older tags to `bitnamilegacy`. Point at that
    # namespace so `docker pull` still succeeds in CI without a version
    # bump.
    image: bitnamilegacy/openldap:2.6.7
    container_name: openmetadata_openldap
  • Files reviewed: 102/102 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@sonarqubecloud

sonarqubecloud Bot commented Sep 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'open-metadata-airflow-apis'

Failed conditions
0.0% Coverage on New Code (required ≥ 20%)

See analysis details on SonarQube Cloud

chirag-madlani and others added 8 commits September 2, 2026 19:24
Squash of the branch's 42 commits into a single PR unit — see
git reflog on this branch for the incremental history if a bisect
lands here.

## Auth runtime — AuthCoordinator singleton (openmetadata-ui/.../utils/Auth/AuthCoordinator)

- Introduce `AuthCoordinator` with `RefreshQueue`, `ProactiveTimer`,
  `CrossTabLock` (Web Locks + BroadcastChannel), `VisibilityWatcher`,
  and a typed event bus. Central coordinator owns every silent-refresh
  path; the axios 401 interceptor and each authenticator adapter go
  through it instead of `TokenServiceUtil`.
- `CrossTabLock` attaches its BroadcastChannel listener BEFORE
  `locks.request(ifAvailable:true)` runs. A leader whose refresh
  finished in the microseconds between the request returning and the
  listener attaching would otherwise post `done` into a void
  (BroadcastChannel does not queue for late subscribers) and the
  follower would time out and run a redundant refresh, breaking the
  "exactly one /auth/refresh across tabs" guarantee.
- `ProactiveTimer` skips scheduling on `expiresAt <= 0`.
- `VisibilityWatcher` distinguishes "no exp yet" from "already-fresh"
  so tab-focus does not fire a refresh when the token is either
  unknown or comfortably fresh.
- `AuthCoordinator` waits for the renewer to register on cold-load;
  the coordinator boot path refreshes expired tokens before flipping
  `isAuthenticated`, closing the "cold-load with expired token"
  window that used to render the app with a dead token and 401 the
  first API call.
- `useApplicationStore.initializeAuthState` re-runs on callback routes
  (`/callback`, `/auth/callback`) instead of bailing early; the
  previous early-return stranded `isAuthenticating: true` so the
  top-level loader gate blocked `SamlCallback` from ever mounting.

## Per-authenticator renewers

- Register a `Renewer` contract on every authenticator so the
  coordinator can drive silent refresh through the correct SDK:
  `BasicAuthAuthenticator`, `GenericAuthenticator` (SAML +
  confidential OIDC), `OidcAuthenticator`, `MsalAuthenticator`,
  `OktaAuthenticator`, `Auth0Authenticator`.
- `MsalAuthenticator` guards `handleRedirectPromise` against
  StrictMode double-invocation.
- `OktaAuthenticator` mirrors the coordinator's fresh tokens back
  into `tokenManager` so the Okta SDK's own consumers see the new
  values.
- `OidcAuthenticator` drops the iframe double-write in the silent-
  callback path; the coordinator owns state transitions now.

## /silent-callback → dedicated HTML entry

- Move the silent-refresh iframe route off the SPA shell to a
  dedicated Vite entry (`silent-callback.html` +
  `silentCallbackEntry.ts`). Its bundle graph is exactly `oidc-client`
  + the tiny bootstrap — no React, no Antd, none of the shared
  app-utils that Rollup's `experimentalMinChunkSize` merger folds into
  the SPA entry. `OpenMetadataAssetServlet` routes extensionless
  `/silent-callback` (with or without base-path) to this HTML;
  `IndexResource` caches the raw body with per-basePath substitution.
- `MsalAuthenticator` / `Auth0Authenticator` shim reads
  (`window.__omTestMsal`, `window.__omTestAuth0`) are additionally
  gated on `isPlaywrightBuild()` — reads Vite's build-time
  `PW_E2E_BUILD` inlined by the `define:` block, isolated in its own
  module so ts-jest doesn't have to parse `import.meta`. The
  SSO-login-nightly workflow now sets `PW_E2E_BUILD=true` on its
  setup step so the mocked-SDK shims arm.

## Auth config validation gate + toast

- Replace the old ConfigErrorPage full-screen block with a
  `showErrorToast` in `AuthProvider` when the fetched config fails
  `validateAuthFieldsDetailed`. Missing top-level fields no longer
  hard-block the whole SPA.
- Track `hasValidConfig` state so Azure with an empty `clientId`
  falls through to `SignInPage` (MSAL's `PublicClientApplication
  .initialize()` rejects an empty clientId, previously leaving the
  shell stuck on `<Loader />`).

## Playwright SSO matrix — SsoScenarios.spec.ts

- New `SsoProviderFixture` interface + fixtures for basic, ldap,
  keycloak-{saml, oidc-confidential, oidc-public}, okta, msal-mock,
  auth0-mock.
- Nine matrixed scenarios (login, logout, silent refresh, multi-tab
  shared auth, cross-tab lock coalescing, cold-load with expired
  token, silent-callback bundle budget, self-signup, session-limit
  guard).
- Scenarios registered conditionally on fixture capability flags
  (`usesBackendRefresh`, `supportsCrossTab`, `supportsSilentCallback`)
  so unsupported (provider, scenario) pairs never enrol — no
  runtime `test.skip()` calls.
- Logout scenario reads tokens from the SW/IndexedDB `AppDataStore`
  DB, `keyValueStore` store, `app_state` key (JSON with `primary`
  field) — the actual storage path, not the legacy
  `localStorage['oidcIdToken']` key.
- Delete legacy `SSOAuthentication.spec.ts`,
  `OktaSessionRenewalPublic.spec.ts`, `SSOLogin.spec.ts`,
  `SSORenewal.spec.ts` — replaced by the new matrix.
- New nightly workflow `playwright-sso-login-nightly.yml` with an
  8-way provider matrix; PR trigger paths scoped to auth-touching
  BE files (`JwtFilter.java`, `AuthenticationCodeFlowHandler.java`,
  `AuthServeletHandlerFactory.java`, `SecurityUtil.java`, security
  `auth/` and `saml/` packages) so unrelated PRs don't spin up a
  40 m docker leg.

## docker-compose

- Add `openldap` service under the `sso-playwright` compose profile
  so the LDAP fixture runs against a real slapd, not a mock.
- Bootstrap LDIF seeds `ldapuser` / `openldap` under
  `dc=openmetadata,dc=org`.

## Docs

- Manual runbook (`playwright/e2e/Auth/manual/Google.md`) walks the
  tester through the SW/IndexedDB `AppDataStore -> keyValueStore ->
  app_state` path plus the localStorage fallback, matching the
  actual storage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ui-checkstyle src-eslint step runs `yarn organize-imports:cli`,
`yarn lint:base --fix`, and `yarn pretty:base --write` on changed
files and then checks `git status --porcelain`. My wrapped
three-line `if (err instanceof LockTimeoutError && attempt <
MAX_RECOVERY_ATTEMPTS)` on `AuthCoordinator.ts` fits under the
prettier printWidth, so the autofix pass collapsed it to a single
line and the git-status guard flagged the file. Applying the
prettier form directly here to close the loop; no runtime change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…validated-on-restart

# Conflicts:
#	openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs
#	openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts
…validated-on-restart

# Conflicts:
#	openmetadata-ui/src/main/resources/ui/vite.config.ts
…validated-on-restart

# Conflicts:
#	openmetadata-ui/src/main/resources/ui/eslint-suppressions.json
#	openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/OktaSessionRenewalPublic.spec.ts
#	openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOAuthentication.spec.ts
#	openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOLogin.spec.ts
#	openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSORenewal.spec.ts
#	openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOSelfSignup.spec.ts
#	openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs
#	openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx
…n-restart' into azure-oidc-session-invalidated-on-restart
…validated-on-restart

# Conflicts:
#	openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs
@gitar-bot

gitar-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 6 resolved / 7 findings

Refactors authentication token renewal behind a centralized AuthCoordinator with cross-tab coordination and adds 72 new SSO Playwright tests across 8 providers. The classifyChunk refactor removes per-connector-schema bundling that kept the connector catalog off the cold-load graph, risking regression of the bundle budget asserted by the new tests.

⚠️ Performance: classifyChunk drops per-connector-schema chunking for E2E bundle

📄 openmetadata-ui/src/main/resources/ui/vite.config.ts:120-134

The previous manualChunks had a Playwright-bundle branch that gave every file under /src/jsons/connectionSchemas/ its own app-e2e-schema-* chunk, with a comment explaining this keeps each connector schema independently lazy so the min-chunk-size pass cannot attach shared shell code and preload the full connector catalog during an authenticated app boot. The new shared classifyChunk (vite.config.ts:120-222) omits this branch entirely, so those schema modules now fall through to return undefined and become eligible for the 32 KiB experimentalMinChunkSize/minSize merger. This can re-bundle the whole connector catalog onto the boot graph, regressing the cold-load bundle budget the new SsoScenarios spec asserts. If intentional, note it; otherwise re-add the connectionSchemas branch to the classifier.

Restore the connectionSchemas branch at the top of the isPlaywrightBundle block.
if (isPlaywrightBundle) {
  // Keep every connector schema independently lazy so the min
  // chunk-size pass cannot attach shared shell code and preload the
  // full connector catalog during an authenticated app boot.
  if (normalizedId.includes('/src/jsons/connectionSchemas/')) {
    const schemaPath = normalizedId.split(
      '/src/jsons/connectionSchemas/'
    )[1];

    return `app-e2e-schema-${schemaPath
      .replace(/\.json$/, '')
      .replaceAll(/[^a-zA-Z0-9_-]/g, '-')}`;
  }
  if (
✅ 6 resolved
Performance: VisibilityWatcher refreshes token on every tab focus

📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:106-111 📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:119-129
In AuthCoordinator.install, the visibility onVisible handler calls this.ensureFreshToken() unconditionally, and ensureFreshTokendoRefresh invokes the renewer (a real network round-trip to the IdP / /auth/refresh) every time the tab becomes visible — even when the stored token is still valid. The code this replaced (handleVisibilityChange in AuthProvider) first decoded the token and only refreshed when isExpired || timeoutExpiry <= 0, otherwise just rescheduling the timer. As written, frequent tab switching causes needless refresh calls and extra IdP load. Gate the visible-handler on token freshness (read/decode the stored token and only call ensureFreshToken() when expired or within the buffer; otherwise reschedule the proactive timer).

Bug: Follower-wait cross-tab path never emits 'refreshed'

📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:171-173 📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:192-206
recoverFromFollowerWait re-reads the sibling-written token and reschedules the timer but does not bus.emit('refreshed', ...), unlike the leader path in doRefresh. AuthProvider maps refreshedsetIsAuthenticated(true) (the Bug 2 post-refresh reauth fix). So in a follower tab that was previously bounced to /signin (isAuthenticated=false), a successful cross-tab refresh restores the token and drains queued requests but never flips isAuthenticated back to true, leaving that tab stuck on the sign-in guard. Emit refreshed (with the recovered token's expiry) from recoverFromFollowerWait as well.

Edge Case: expiresAt=0 fallback can cause an immediate refresh loop

📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/ProactiveTimer.ts:19-26 📄 openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/GenericAuthenticator.tsx:66-71 📄 openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.tsx:57-60
Renewers derive expiresAt as (decoded.exp ?? 0) * 1000 (Basic/Generic) or (claims.exp ?? 0) * 1000 (Auth0), and extractDetailsFromToken returns exp: 0 for an opaque/undecodable token. When expiresAt is 0, ProactiveTimer.schedule computes delay = Math.max(0, 0 - Date.now() - bufferMs) = 0, firing ensureFreshToken() immediately and rescheduling to 0 again — a tight refresh loop that hammers the IdP. Guard the scheduler (skip scheduling when expiresAt <= 0 or clamp to a sane minimum) and/or reject the renewer result when a usable expiry cannot be determined.

Edge Case: Leader refresh failure leaves follower tabs waiting to timeout

📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/CrossTabLock.ts:41-55 📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/CrossTabLock.ts:73-87 📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:208-215
In CrossTabLock.runExclusive, this.channel.postMessage({ type: 'done' }) is only sent after work() resolves. If the leader tab's renewer throws (or the tab is closed mid-refresh), follower tabs never receive 'done' and block until the 10s waitForDone timeout, whereupon pumpQueue's catch drains the queue with null and refresh-failed force-logs-out the follower — even though it could have retried the refresh itself. Consider broadcasting a failure/abort signal so followers can promptly attempt their own refresh instead of waiting out the full timeout.

Performance: onTabVisible refreshes on every focus for tokens without exp claim

📄 openmetadata-ui/src/main/resources/ui/src/utils/Auth/AuthCoordinator/AuthCoordinator.ts:156-164
For an opaque token or a JWT with no exp claim, extractDetailsFromToken returns isExpired:false and timeoutExpiry:0. In onTabVisible the timeoutExpiry <= 0 branch then calls ensureFreshToken() on every visibility change, hitting the IdP (and cross-tab lock) each time the user switches back to the tab even though the token may still be valid. Consider distinguishing 'no expiry information' from 'within pre-expiry buffer' so a missing exp does not force a refresh on every focus.

...and 1 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Refactors authentication token renewal behind a centralized `AuthCoordinator` with cross-tab coordination and adds 72 new SSO Playwright tests across 8 providers. The `classifyChunk` refactor removes per-connector-schema bundling that kept the connector catalog off the cold-load graph, risking regression of the bundle budget asserted by the new tests.

1. ⚠️ Performance: classifyChunk drops per-connector-schema chunking for E2E bundle
   Files: openmetadata-ui/src/main/resources/ui/vite.config.ts:120-134

   The previous `manualChunks` had a Playwright-bundle branch that gave every file under `/src/jsons/connectionSchemas/` its own `app-e2e-schema-*` chunk, with a comment explaining this keeps each connector schema independently lazy so the min-chunk-size pass cannot attach shared shell code and preload the full connector catalog during an authenticated app boot. The new shared `classifyChunk` (vite.config.ts:120-222) omits this branch entirely, so those schema modules now fall through to `return undefined` and become eligible for the 32 KiB `experimentalMinChunkSize`/`minSize` merger. This can re-bundle the whole connector catalog onto the boot graph, regressing the cold-load bundle budget the new SsoScenarios spec asserts. If intentional, note it; otherwise re-add the connectionSchemas branch to the classifier.

   Fix (Restore the connectionSchemas branch at the top of the isPlaywrightBundle block.):
   if (isPlaywrightBundle) {
     // Keep every connector schema independently lazy so the min
     // chunk-size pass cannot attach shared shell code and preload the
     // full connector catalog during an authenticated app boot.
     if (normalizedId.includes('/src/jsons/connectionSchemas/')) {
       const schemaPath = normalizedId.split(
         '/src/jsons/connectionSchemas/'
       )[1];
   
       return `app-e2e-schema-${schemaPath
         .replace(/\.json$/, '')
         .replaceAll(/[^a-zA-Z0-9_-]/g, '-')}`;
     }
     if (

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants