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.
This commit is contained in:
2026-06-19 20:58:44 +02:00
parent 6ddf4e56b4
commit a6a87ae282
2 changed files with 77 additions and 15 deletions

View File

@@ -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;

View File

@@ -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();
}
}