Fix OpenBao login falling back to token auth

Add synchronous redirect-bootstrap, direct KeyCape OIDC on sign-in, and mount
watching so the UI no longer lands on ?with=token when netkingdom is hidden
from unauthenticated mount listing. Document listing_visibility tune helper.
This commit is contained in:
2026-06-19 21:04:31 +02:00
parent a6a87ae282
commit cb45f29fb2
8 changed files with 218 additions and 18 deletions

View File

@@ -49,7 +49,7 @@ http {
sub_filter_types text/html;
sub_filter_once on;
sub_filter '</head>' '<link rel="stylesheet" href="/ui/platform-overlay/overlay.css"><script src="/ui/platform-overlay/overlay.js" defer></script></head>';
sub_filter '</head>' '<script src="/ui/platform-overlay/redirect-bootstrap.js"></script><link rel="stylesheet" href="/ui/platform-overlay/overlay.css"><script src="/ui/platform-overlay/overlay.js"></script></head>';
}
}
}

View File

@@ -8,6 +8,9 @@ html.keycape-overlay-active label[for="custom-path"],
html.keycape-overlay-active #namespace,
html.keycape-overlay-active #role,
html.keycape-overlay-active #custom-path,
html.keycape-overlay-active #token,
html.keycape-overlay-active #username,
html.keycape-overlay-active #password,
html.keycape-overlay-active select[name="auth-method"],
html.keycape-overlay-active .auth-form .box.has-slim-padding.is-shadowless,
html.keycape-overlay-active .auth-form .has-bottom-margin-s {

View File

@@ -4,6 +4,8 @@
const PRESETS_URL = "/ui/platform-overlay/presets.json";
const MAX_APPLY_ATTEMPTS = 40;
const APPLY_INTERVAL_MS = 250;
const MOUNT_WATCH_MS = 500;
const MOUNT_WATCH_MAX = 24;
const DEFAULT_PRESETS = {
namespace: "",
method: "oidc",
@@ -18,7 +20,9 @@
let presets = { ...DEFAULT_PRESETS };
let applyAttempts = 0;
let applyTimer = null;
let mountWatchTimer = null;
let overlayApplied = false;
let signInHandlerInstalled = false;
function isAuthPage() {
const path = window.location.pathname;
@@ -28,6 +32,40 @@
);
}
function normalizedMount(value) {
return (value || "").replace(/\/$/, "");
}
function desiredMount() {
return normalizedMount(presets.mount || "netkingdom");
}
function currentMountFromQuery() {
return normalizedMount(
new URLSearchParams(window.location.search).get("with") || ""
);
}
function ensureAuthMountSelected() {
const mount = desiredMount();
const current = currentMountFromQuery();
if (current === mount || current === "keycape") {
return false;
}
if (!isAuthPage() || window.location.pathname.includes("/oidc/")) {
return false;
}
const params = new URLSearchParams(window.location.search);
params.set("with", `${mount}/`);
window.location.replace(
`${window.location.pathname}?${params.toString()}`
);
return true;
}
function hideNode(node) {
if (!node || node.dataset.keycapeOverlayHidden === "true") return;
const field =
@@ -45,30 +83,63 @@
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 }));
}
function ensureAuthMountSelected() {
async function redirectToKeyCape() {
const mount = presets.mount || "netkingdom";
const withValue = mount.endsWith("/") ? mount : `${mount}/`;
const params = new URLSearchParams(window.location.search);
const current = params.get("with") || "";
const role = presets.role || "platform-admin";
const redirectUri = `${window.location.origin}/ui/vault/auth/${mount}/oidc/callback`;
if (current.replace(/\/$/, "") === withValue.replace(/\/$/, "")) {
return;
const response = await fetch(`/v1/auth/${mount}/oidc/auth_url`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
role,
redirect_uri: redirectUri,
}),
});
if (!response.ok) {
throw new Error(`OIDC auth_url request failed (${response.status})`);
}
if (!isAuthPage() || window.location.pathname.includes("/oidc/")) {
return;
const payload = await response.json();
const authUrl = payload?.data?.auth_url;
if (!authUrl) {
throw new Error("OIDC auth_url missing from OpenBao response");
}
params.set("with", withValue);
const next = `${window.location.pathname}?${params.toString()}`;
if (next !== `${window.location.pathname}${window.location.search}`) {
window.location.replace(next);
}
window.location.assign(authUrl);
}
function installKeyCapeSignInHandler() {
if (signInHandlerInstalled) return;
signInHandlerInstalled = true;
document.addEventListener(
"click",
(event) => {
if (!isAuthPage()) return;
const button = event.target.closest(
'#auth-submit, button[data-test="auth-submit"], form#auth-form button[type="submit"]'
);
if (!button) return;
event.preventDefault();
event.stopPropagation();
button.disabled = true;
button.classList.add("is-loading");
redirectToKeyCape().catch(() => {
button.disabled = false;
button.classList.remove("is-loading");
});
},
true
);
}
function loginShellReady() {
@@ -101,6 +172,12 @@
.querySelectorAll('#role, input[name="role"], label[for="role"]')
.forEach(hideNode);
document
.querySelectorAll(
'#token, input[name="token"], label[for="token"], #username, input[name="username"], #password, input[name="password"]'
)
.forEach(hideNode);
document.querySelectorAll("nav.tabs").forEach((el) => {
if (el.dataset.keycapeOverlayHidden === "true") return;
el.style.display = "none";
@@ -151,6 +228,7 @@
}
document.documentElement.classList.add("keycape-overlay-active");
installKeyCapeSignInHandler();
if (loginShellReady()) {
overlayApplied = true;
@@ -167,6 +245,25 @@
}
}
function stopMountWatch() {
if (mountWatchTimer !== null) {
window.clearInterval(mountWatchTimer);
mountWatchTimer = null;
}
}
function watchAuthMount() {
stopMountWatch();
let checks = 0;
mountWatchTimer = window.setInterval(() => {
checks += 1;
if (ensureAuthMountSelected() || checks >= MOUNT_WATCH_MAX) {
stopMountWatch();
}
}, MOUNT_WATCH_MS);
}
function scheduleApply() {
stopApplyLoop();
applyAttempts = 0;
@@ -195,10 +292,16 @@
}
async function init() {
await loadPresets();
if (!isAuthPage()) return;
ensureAuthMountSelected();
await loadPresets();
if (ensureAuthMountSelected()) {
return;
}
watchAuthMount();
installKeyCapeSignInHandler();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scheduleApply, {

View File

@@ -0,0 +1,23 @@
(function () {
"use strict";
var path = window.location.pathname;
if (path.indexOf("/oidc/") !== -1) {
return;
}
if (!/\/ui\/vault\/auth(?:\/|$)/.test(path) && !/\/ui\/?$/.test(path)) {
return;
}
var params = new URLSearchParams(window.location.search);
var current = (params.get("with") || "").replace(/\/$/, "");
if (current === "netkingdom" || current === "keycape") {
return;
}
params.set("with", "netkingdom/");
window.location.replace(
window.location.pathname + "?" + params.toString()
);
})();