Remove redirect-bootstrap and mount polling that fought Ember's token fallback. Keep cosmetic overlay and direct KeyCape OIDC on sign-in only.
258 lines
7.0 KiB
JavaScript
258 lines
7.0 KiB
JavaScript
(function () {
|
|
"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",
|
|
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 };
|
|
let applyAttempts = 0;
|
|
let applyTimer = null;
|
|
let overlayApplied = false;
|
|
let signInHandlerInstalled = false;
|
|
|
|
function isAuthPage() {
|
|
const path = window.location.pathname;
|
|
return (
|
|
/\/ui\/vault\/auth(?:\/|$)/.test(path) ||
|
|
/\/ui\/?$/.test(path)
|
|
);
|
|
}
|
|
|
|
function isOidcCallbackPage() {
|
|
return window.location.pathname.includes("/oidc/");
|
|
}
|
|
|
|
function hideNode(node) {
|
|
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.dataset.keycapeOverlayPreset === value) return;
|
|
input.value = value;
|
|
input.dataset.keycapeOverlayPreset = value;
|
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
}
|
|
|
|
async function redirectToKeyCape() {
|
|
const mount = presets.mount || "netkingdom";
|
|
const role = presets.role || "platform-admin";
|
|
const redirectUri = `${window.location.origin}/ui/vault/auth/${mount}/oidc/callback`;
|
|
|
|
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})`);
|
|
}
|
|
|
|
const payload = await response.json();
|
|
const authUrl = payload?.data?.auth_url;
|
|
if (!authUrl) {
|
|
throw new Error("OIDC auth_url missing from OpenBao response");
|
|
}
|
|
|
|
window.location.assign(authUrl);
|
|
}
|
|
|
|
function installKeyCapeSignInHandler() {
|
|
if (signInHandlerInstalled) return;
|
|
signInHandlerInstalled = true;
|
|
|
|
document.addEventListener(
|
|
"click",
|
|
(event) => {
|
|
if (!isAuthPage() || isOidcCallbackPage()) 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() {
|
|
return Boolean(
|
|
document.querySelector(".login-form") ||
|
|
document.querySelector(".auth-form") ||
|
|
document.querySelector(".toolbar-namespace-picker")
|
|
);
|
|
}
|
|
|
|
function applyDom() {
|
|
if (!isAuthPage() || isOidcCallbackPage() || overlayApplied) return false;
|
|
|
|
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(
|
|
'#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";
|
|
el.setAttribute("aria-hidden", "true");
|
|
el.dataset.keycapeOverlayHidden = "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
|
|
) {
|
|
heading.textContent = presets.title;
|
|
}
|
|
});
|
|
|
|
document
|
|
.querySelectorAll('#auth-submit, button[data-test="auth-submit"]')
|
|
.forEach((button) => {
|
|
if (button.textContent !== presets.signInLabel) {
|
|
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");
|
|
installKeyCapeSignInHandler();
|
|
|
|
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() {
|
|
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 };
|
|
}
|
|
}
|
|
|
|
async function init() {
|
|
if (!isAuthPage() || isOidcCallbackPage()) return;
|
|
|
|
await loadPresets();
|
|
installKeyCapeSignInHandler();
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", scheduleApply, {
|
|
once: true,
|
|
});
|
|
} else {
|
|
scheduleApply();
|
|
}
|
|
}
|
|
|
|
init();
|
|
})(); |