Add KeyCape login overlay gateway for OpenBao browser UI
Streamline bao.coulomb.social login as "Sign in with KeyCape" via a versioned nginx gateway that injects overlay assets and proxies to OpenBao. Disable chart ingress in favor of the overlay ingress, wire make openbao-deploy, and add openbao-verify-login-overlay with upstream drift detection.
This commit is contained in:
161
helm/openbao-ui-overlay/overlay.js
Normal file
161
helm/openbao-ui-overlay/overlay.js
Normal file
@@ -0,0 +1,161 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const PRESETS_URL = "/ui/platform-overlay/presets.json";
|
||||
const DEFAULT_PRESETS = {
|
||||
namespace: "",
|
||||
method: "oidc",
|
||||
mount: "netkingdom",
|
||||
role: "platform-admin",
|
||||
title: "Sign in with KeyCape",
|
||||
signInLabel: "Sign in with KeyCape",
|
||||
banner:
|
||||
"Platform operators authenticate through KeyCape at kc.coulomb.social.",
|
||||
};
|
||||
|
||||
let presets = { ...DEFAULT_PRESETS };
|
||||
|
||||
function isAuthPage() {
|
||||
const path = window.location.pathname;
|
||||
return (
|
||||
/\/ui\/vault\/auth(?:\/|$)/.test(path) ||
|
||||
/\/ui\/?$/.test(path)
|
||||
);
|
||||
}
|
||||
|
||||
function hideNode(node) {
|
||||
if (!node) return;
|
||||
const field =
|
||||
node.closest(".field.is-horizontal") ||
|
||||
node.closest(".field") ||
|
||||
node.closest(".box") ||
|
||||
node;
|
||||
field.style.display = "none";
|
||||
field.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
function setInputValue(input, value) {
|
||||
if (!input || input.value === value) return;
|
||||
input.value = value;
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
|
||||
function ensureAuthMountSelected() {
|
||||
const mount = presets.mount || "netkingdom";
|
||||
const withValue = mount.endsWith("/") ? mount : `${mount}/`;
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const current = params.get("with") || "";
|
||||
|
||||
if (current.replace(/\/$/, "") === withValue.replace(/\/$/, "")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthPage() || window.location.pathname.includes("/oidc/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
params.set("with", withValue);
|
||||
const next = `${window.location.pathname}?${params.toString()}`;
|
||||
if (next !== `${window.location.pathname}${window.location.search}`) {
|
||||
window.location.replace(next);
|
||||
}
|
||||
}
|
||||
|
||||
function applyDom() {
|
||||
if (!isAuthPage()) return;
|
||||
|
||||
hideNode(document.querySelector(".toolbar-namespace-picker"));
|
||||
document
|
||||
.querySelectorAll(
|
||||
'#namespace, input[name="namespace"], label[for="namespace"]'
|
||||
)
|
||||
.forEach(hideNode);
|
||||
|
||||
document
|
||||
.querySelectorAll('select[name="auth-method"], #auth-method')
|
||||
.forEach((el) => hideNode(el.closest(".field") || el));
|
||||
|
||||
document
|
||||
.querySelectorAll('#custom-path, input[name="custom-path"]')
|
||||
.forEach(hideNode);
|
||||
|
||||
document
|
||||
.querySelectorAll('#role, input[name="role"], label[for="role"]')
|
||||
.forEach(hideNode);
|
||||
|
||||
document.querySelectorAll("nav.tabs").forEach((el) => {
|
||||
el.style.display = "none";
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll(".auth-form .has-bottom-margin-s")
|
||||
.forEach(hideNode);
|
||||
|
||||
document.querySelectorAll("h1.title.is-3").forEach((heading) => {
|
||||
if (/Sign in to OpenBao|Authenticate/.test(heading.textContent)) {
|
||||
heading.textContent = presets.title;
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll('#auth-submit, button[data-test="auth-submit"]')
|
||||
.forEach((button) => {
|
||||
button.textContent = presets.signInLabel;
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll('#namespace, input[name="namespace"]')
|
||||
.forEach((input) => setInputValue(input, presets.namespace || ""));
|
||||
|
||||
document
|
||||
.querySelectorAll('#role, input[name="role"]')
|
||||
.forEach((input) => setInputValue(input, presets.role || "platform-admin"));
|
||||
|
||||
if (!document.getElementById("keycape-overlay-banner")) {
|
||||
const banner = document.createElement("div");
|
||||
banner.id = "keycape-overlay-banner";
|
||||
banner.className = "keycape-overlay-banner";
|
||||
banner.textContent = presets.banner;
|
||||
const loginForm = document.querySelector(".login-form");
|
||||
if (loginForm) {
|
||||
loginForm.prepend(banner);
|
||||
}
|
||||
}
|
||||
|
||||
document.documentElement.classList.add("keycape-overlay-active");
|
||||
}
|
||||
|
||||
async function loadPresets() {
|
||||
try {
|
||||
const response = await fetch(PRESETS_URL, { cache: "no-store" });
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
presets = { ...DEFAULT_PRESETS, ...data };
|
||||
} catch (_error) {
|
||||
presets = { ...DEFAULT_PRESETS };
|
||||
}
|
||||
}
|
||||
|
||||
function observe() {
|
||||
const observer = new MutationObserver(() => applyDom());
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
applyDom();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await loadPresets();
|
||||
if (!isAuthPage()) return;
|
||||
|
||||
ensureAuthMountSelected();
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", observe);
|
||||
} else {
|
||||
observe();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
Reference in New Issue
Block a user