From a6a87ae2829e92d4468742e6d9f99a3b210903ea Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 19 Jun 2026 20:58:44 +0200 Subject: [PATCH] Fix OpenBao login overlay runaway DOM loop and slow loads Replace the MutationObserver feedback loop with bounded, idempotent apply retries so Firefox no longer hangs on the auth page. Route static UI assets and API calls around HTML sub_filter injection to keep bundles compressed. --- helm/openbao-ui-overlay/nginx.conf | 12 ++++- helm/openbao-ui-overlay/overlay.js | 80 ++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/helm/openbao-ui-overlay/nginx.conf b/helm/openbao-ui-overlay/nginx.conf index 15a1e67..8facdf3 100644 --- a/helm/openbao-ui-overlay/nginx.conf +++ b/helm/openbao-ui-overlay/nginx.conf @@ -26,6 +26,16 @@ http { add_header Cache-Control "public, max-age=300"; } + # Static UI bundles and API calls bypass HTML injection and stay compressed. + location ~ ^/(v1|ui/assets|ui/engines-dist|ui/favicon\.svg) { + proxy_pass http://openbao_upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location / { proxy_pass http://openbao_upstream; proxy_http_version 1.1; @@ -33,7 +43,7 @@ http { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # Disable upstream compression so sub_filter can rewrite HTML. + # Disable upstream compression only for HTML shell injection. proxy_set_header Accept-Encoding ""; proxy_buffering on; diff --git a/helm/openbao-ui-overlay/overlay.js b/helm/openbao-ui-overlay/overlay.js index 52c83a4..d6a2fe8 100644 --- a/helm/openbao-ui-overlay/overlay.js +++ b/helm/openbao-ui-overlay/overlay.js @@ -2,6 +2,8 @@ "use strict"; const PRESETS_URL = "/ui/platform-overlay/presets.json"; + const MAX_APPLY_ATTEMPTS = 40; + const APPLY_INTERVAL_MS = 250; const DEFAULT_PRESETS = { namespace: "", method: "oidc", @@ -14,6 +16,9 @@ }; let presets = { ...DEFAULT_PRESETS }; + let applyAttempts = 0; + let applyTimer = null; + let overlayApplied = false; function isAuthPage() { const path = window.location.pathname; @@ -24,19 +29,23 @@ } function hideNode(node) { - if (!node) return; + if (!node || node.dataset.keycapeOverlayHidden === "true") return; const field = node.closest(".field.is-horizontal") || node.closest(".field") || node.closest(".box") || node; + if (field.dataset.keycapeOverlayHidden === "true") return; field.style.display = "none"; field.setAttribute("aria-hidden", "true"); + field.dataset.keycapeOverlayHidden = "true"; } function setInputValue(input, value) { - if (!input || input.value === value) return; + if (!input || input.dataset.keycapeOverlayPreset === value) return; input.value = value; + input.dataset.keycapeOverlayPreset = value; + // Fire once so Ember picks up the preset without a mutation feedback loop. input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); } @@ -62,8 +71,16 @@ } } + function loginShellReady() { + return Boolean( + document.querySelector(".login-form") || + document.querySelector(".auth-form") || + document.querySelector(".toolbar-namespace-picker") + ); + } + function applyDom() { - if (!isAuthPage()) return; + if (!isAuthPage() || overlayApplied) return false; hideNode(document.querySelector(".toolbar-namespace-picker")); document @@ -85,8 +102,10 @@ .forEach(hideNode); document.querySelectorAll("nav.tabs").forEach((el) => { + if (el.dataset.keycapeOverlayHidden === "true") return; el.style.display = "none"; el.setAttribute("aria-hidden", "true"); + el.dataset.keycapeOverlayHidden = "true"; }); document @@ -94,7 +113,10 @@ .forEach(hideNode); document.querySelectorAll("h1.title.is-3").forEach((heading) => { - if (/Sign in to OpenBao|Authenticate/.test(heading.textContent)) { + if ( + /Sign in to OpenBao|Authenticate/.test(heading.textContent) && + heading.textContent !== presets.title + ) { heading.textContent = presets.title; } }); @@ -102,7 +124,9 @@ document .querySelectorAll('#auth-submit, button[data-test="auth-submit"]') .forEach((button) => { - button.textContent = presets.signInLabel; + if (button.textContent !== presets.signInLabel) { + button.textContent = presets.signInLabel; + } }); document @@ -111,7 +135,9 @@ document .querySelectorAll('#role, input[name="role"]') - .forEach((input) => setInputValue(input, presets.role || "platform-admin")); + .forEach((input) => + setInputValue(input, presets.role || "platform-admin") + ); if (!document.getElementById("keycape-overlay-banner")) { const banner = document.createElement("div"); @@ -125,6 +151,36 @@ } document.documentElement.classList.add("keycape-overlay-active"); + + if (loginShellReady()) { + overlayApplied = true; + return true; + } + + return false; + } + + function stopApplyLoop() { + if (applyTimer !== null) { + window.clearInterval(applyTimer); + applyTimer = null; + } + } + + function scheduleApply() { + stopApplyLoop(); + applyAttempts = 0; + + const tick = () => { + applyAttempts += 1; + if (applyDom() || applyAttempts >= MAX_APPLY_ATTEMPTS) { + stopApplyLoop(); + return; + } + }; + + tick(); + applyTimer = window.setInterval(tick, APPLY_INTERVAL_MS); } async function loadPresets() { @@ -138,12 +194,6 @@ } } - function observe() { - const observer = new MutationObserver(() => applyDom()); - observer.observe(document.body, { childList: true, subtree: true }); - applyDom(); - } - async function init() { await loadPresets(); if (!isAuthPage()) return; @@ -151,9 +201,11 @@ ensureAuthMountSelected(); if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", observe); + document.addEventListener("DOMContentLoaded", scheduleApply, { + once: true, + }); } else { - observe(); + scheduleApply(); } }