Files
baron_qa_write/SW문의_소스패키지/assets/js/baron-sso-auth.js
T
root 308c06516c
Deploy EG-BIM QA Gateway / deploy (push) Successful in 1m0s
fix: 전체 UI 개선
2026-11-02 13:26:14 +09:00

964 lines
28 KiB
JavaScript

const STORAGE_KEY = "baron_sso_session";
const PENDING_LOGIN_KEY = "baron_sso_pending_login";
const STATE_KEY = "baron_sso_state";
const VERIFIER_KEY = "baron_sso_code_verifier";
const RETURN_TO_KEY = "baron_sso_return_to";
const FORCE_LOGIN_KEY = "baron_sso_force_login";
const OVERLAY_ID = "baron-sso-overlay";
const STYLE_ID = "baron-sso-style";
const messages = {
ko: {
loadingTitle: "BARON SSO 확인 중",
loadingDescription: "접근 권한을 확인하고 있습니다.",
redirectTitle: "BARON SSO 로그인으로 이동합니다",
redirectDescription: "내부 사용자 인증이 필요합니다.",
redirectAction: "다시 로그인",
unauthorizedTitle: "접근 권한이 없습니다",
unauthorizedDescription: "BARON 내부 사용자 계정으로 로그인해야 합니다.",
retryAction: "다시 로그인",
configTitle: "BARON SSO 설정이 필요합니다",
configDescription: "clientId, authorizeUrl, tokenUrl 값을 먼저 설정하세요.",
exchangeTitle: "로그인 처리에 실패했습니다",
exchangeDescription: "인증 응답을 처리하지 못했습니다. 다시 로그인해 주세요.",
logoutAction: "로그아웃",
},
en: {
loadingTitle: "Checking BARON SSO",
loadingDescription: "Verifying your access.",
redirectTitle: "Redirecting to BARON SSO",
redirectDescription: "Internal user authentication is required.",
redirectAction: "Sign in again",
unauthorizedTitle: "Access denied",
unauthorizedDescription: "You must sign in with an internal BARON account.",
retryAction: "Sign in again",
configTitle: "BARON SSO configuration required",
configDescription: "Set clientId, authorizeUrl, and tokenUrl before enabling the gate.",
exchangeTitle: "Login failed",
exchangeDescription: "The authentication response could not be processed. Please try again.",
logoutAction: "Logout",
},
};
async function mergeConfig(overrides = {}) {
const module = await import("./baron-sso-config.js?v=20260721-worker2");
const baseConfig = module.default || {};
const windowConfig = window.BARON_SSO_CONFIG || {};
const config = {
...baseConfig,
...windowConfig,
...overrides,
};
config.allowedEmailDomains = normalizeArray(config.allowedEmailDomains);
config.allowedEmails = normalizeArray(config.allowedEmails);
config.requiredGroups = normalizeArray(config.requiredGroups);
config.publicPaths = normalizeArray(config.publicPaths);
config.extraAuthorizeParams = config.extraAuthorizeParams || {};
config.extraTokenParams = config.extraTokenParams || {};
config.locale = config.locale || detectLocale();
return config;
}
function normalizeArray(value) {
return Array.isArray(value) ? value.filter(Boolean) : [];
}
function getRootPrefix(pathname = window.location.pathname) {
const pathSegments = pathname.split("/").filter(Boolean);
const rootMarkerSegments = ["ko", "en", "callback", "assets", "protected", "public", "recruit"];
return pathSegments.length && !rootMarkerSegments.includes(pathSegments[0])
? `/${pathSegments[0]}`
: "";
}
function normalizePath(path) {
if (!path) {
return "/";
}
const normalizedPath = path.replace(/\/index\.html$/i, "/");
if (normalizedPath.length > 1 && normalizedPath.endsWith("/")) {
return normalizedPath.slice(0, -1);
}
return normalizedPath;
}
function matchesPublicPath(path, entry) {
const normalizedPath = normalizePath(path);
const normalizedEntry = normalizePath(entry);
if (normalizedEntry.endsWith("_")) {
return normalizedPath.startsWith(normalizedEntry);
}
return normalizedPath === normalizedEntry || normalizedPath.startsWith(`${normalizedEntry}/`);
}
function detectLocale() {
return window.location.pathname.startsWith("/en/") ? "en" : "ko";
}
function isLocalEnvironment() {
return window.location.hostname !== "baroncs.co.kr";
}
function getMessage(config, key) {
const localeMessages = messages[config.locale] || messages.ko;
return localeMessages[key];
}
function getWorkerManagedAuth() {
const workerAuth = window.BARON_WORKER_AUTH;
if (!workerAuth || workerAuth.mode !== "worker") {
return null;
}
return workerAuth;
}
function attachWorkerManagedLogout(workerAuth) {
window.baronSsoLogout = () => {
const logoutUrl = workerAuth?.logoutUrl || "/auth/logout";
window.location.assign(logoutUrl);
};
}
function installStyles() {
if (document.getElementById(STYLE_ID)) {
return;
}
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
html[data-baron-auth-pending="true"] body > :not(#${OVERLAY_ID}) {
visibility: hidden;
}
#${OVERLAY_ID} {
position: fixed;
inset: 0;
z-index: 99999;
display: none;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(9, 17, 27, 0.78);
font-family: "Pretendard", "Noto Sans KR", sans-serif;
}
#${OVERLAY_ID}.is-visible {
display: flex;
}
#${OVERLAY_ID} .baron-sso-panel {
width: min(480px, 100%);
padding: 32px 28px;
border-radius: 20px;
background: #ffffff;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.18);
color: #111827;
text-align: center;
}
#${OVERLAY_ID} .baron-sso-title {
margin: 0;
font-size: 28px;
font-weight: 700;
line-height: 1.3;
}
#${OVERLAY_ID} .baron-sso-description {
margin: 12px 0 0;
font-size: 16px;
line-height: 1.6;
color: #4b5563;
}
#${OVERLAY_ID} .baron-sso-actions {
display: flex;
gap: 12px;
justify-content: center;
margin-top: 24px;
flex-wrap: wrap;
}
#${OVERLAY_ID} button {
min-width: 160px;
padding: 12px 18px;
border: 0;
border-radius: 999px;
background: #101828;
color: #ffffff;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}
#${OVERLAY_ID} button.baron-sso-secondary {
background: #e5e7eb;
color: #111827;
}
`;
document.head.appendChild(style);
}
function waitForBody() {
if (document.body) {
return Promise.resolve();
}
return new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", resolve, { once: true });
});
}
function setPending(isPending) {
document.documentElement.toggleAttribute("data-baron-auth-pending", isPending);
}
function ensureOverlay() {
let overlay = document.getElementById(OVERLAY_ID);
if (!overlay) {
overlay = document.createElement("div");
overlay.id = OVERLAY_ID;
overlay.innerHTML = `
<div class="baron-sso-panel">
<h1 class="baron-sso-title"></h1>
<p class="baron-sso-description"></p>
<div class="baron-sso-actions"></div>
</div>
`;
document.body.appendChild(overlay);
}
return overlay;
}
function renderOverlay(config, options) {
installStyles();
const overlay = ensureOverlay();
const title = overlay.querySelector(".baron-sso-title");
const description = overlay.querySelector(".baron-sso-description");
const actions = overlay.querySelector(".baron-sso-actions");
title.textContent = options.title;
description.textContent = options.description;
actions.innerHTML = "";
(options.actions || []).forEach((action) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = action.label;
if (action.secondary) {
button.className = "baron-sso-secondary";
}
button.addEventListener("click", action.onClick);
actions.appendChild(button);
});
overlay.classList.add("is-visible");
setPending(true);
}
function hideOverlay() {
const overlay = document.getElementById(OVERLAY_ID);
if (overlay) {
overlay.classList.remove("is-visible");
}
setPending(false);
}
function isConfigured(config) {
return Boolean(config.clientId && config.authorizeUrl && config.tokenUrl);
}
function isPublicPath(config) {
const currentPath = normalizePath(window.location.pathname);
return config.publicPaths.some((entry) => matchesPublicPath(currentPath, entry));
}
function buildRedirectUri(config) {
return config.redirectUri || `${window.location.origin}${window.location.pathname}`;
}
function getCallbackPaths(config) {
const configuredPath = normalizePath(new URL(buildRedirectUri(config)).pathname);
const rootPrefix = getRootPrefix();
const candidatePaths = [
configuredPath,
`${rootPrefix}/callback.html`,
`${rootPrefix}/callback/index.html`,
];
return [...new Set(candidatePaths)];
}
function buildReturnToUrl() {
const currentUrl = new URL(window.location.href);
currentUrl.search = "";
currentUrl.hash = "";
return currentUrl.toString();
}
function isCallbackPath(config) {
return getCallbackPaths(config).includes(normalizePath(window.location.pathname));
}
function getCryptoObject() {
return window.crypto || window.msCrypto || null;
}
function fillRandomValues(bytes) {
const cryptoObject = getCryptoObject();
if (cryptoObject?.getRandomValues) {
cryptoObject.getRandomValues(bytes);
return bytes;
}
for (let index = 0; index < bytes.length; index += 1) {
bytes[index] = Math.floor(Math.random() * 256);
}
return bytes;
}
function rightRotate(value, amount) {
return (value >>> amount) | (value << (32 - amount));
}
function sha256Fallback(message) {
const constants = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
const initialHash = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
];
const bytes = new TextEncoder().encode(message);
const bitLength = bytes.length * 8;
const paddedLength = (((bytes.length + 9 + 63) >> 6) << 6);
const padded = new Uint8Array(paddedLength);
padded.set(bytes);
padded[bytes.length] = 0x80;
const bitLengthHigh = Math.floor(bitLength / 0x100000000);
const bitLengthLow = bitLength >>> 0;
padded[padded.length - 8] = (bitLengthHigh >>> 24) & 0xff;
padded[padded.length - 7] = (bitLengthHigh >>> 16) & 0xff;
padded[padded.length - 6] = (bitLengthHigh >>> 8) & 0xff;
padded[padded.length - 5] = bitLengthHigh & 0xff;
padded[padded.length - 4] = (bitLengthLow >>> 24) & 0xff;
padded[padded.length - 3] = (bitLengthLow >>> 16) & 0xff;
padded[padded.length - 2] = (bitLengthLow >>> 8) & 0xff;
padded[padded.length - 1] = bitLengthLow & 0xff;
const words = new Uint32Array(64);
const hash = initialHash.slice();
for (let offset = 0; offset < padded.length; offset += 64) {
for (let index = 0; index < 16; index += 1) {
const position = offset + index * 4;
words[index] = (
(padded[position] << 24) |
(padded[position + 1] << 16) |
(padded[position + 2] << 8) |
padded[position + 3]
) >>> 0;
}
for (let index = 16; index < 64; index += 1) {
const sigma0 = rightRotate(words[index - 15], 7) ^ rightRotate(words[index - 15], 18) ^ (words[index - 15] >>> 3);
const sigma1 = rightRotate(words[index - 2], 17) ^ rightRotate(words[index - 2], 19) ^ (words[index - 2] >>> 10);
words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;
}
let a = hash[0];
let b = hash[1];
let c = hash[2];
let d = hash[3];
let e = hash[4];
let f = hash[5];
let g = hash[6];
let h = hash[7];
for (let index = 0; index < 64; index += 1) {
const sigma1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
const choice = (e & f) ^ (~e & g);
const temp1 = (h + sigma1 + choice + constants[index] + words[index]) >>> 0;
const sigma0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
const majority = (a & b) ^ (a & c) ^ (b & c);
const temp2 = (sigma0 + majority) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
hash[0] = (hash[0] + a) >>> 0;
hash[1] = (hash[1] + b) >>> 0;
hash[2] = (hash[2] + c) >>> 0;
hash[3] = (hash[3] + d) >>> 0;
hash[4] = (hash[4] + e) >>> 0;
hash[5] = (hash[5] + f) >>> 0;
hash[6] = (hash[6] + g) >>> 0;
hash[7] = (hash[7] + h) >>> 0;
}
const digest = new Uint8Array(32);
hash.forEach((value, index) => {
const position = index * 4;
digest[position] = (value >>> 24) & 0xff;
digest[position + 1] = (value >>> 16) & 0xff;
digest[position + 2] = (value >>> 8) & 0xff;
digest[position + 3] = value & 0xff;
});
return digest;
}
function createRandomString(length = 64) {
const bytes = new Uint8Array(length);
fillRandomValues(bytes);
return base64UrlEncode(bytes).slice(0, length);
}
function base64UrlEncode(input) {
const bytes = input instanceof Uint8Array ? input : new TextEncoder().encode(String(input));
let binary = "";
bytes.forEach((byte) => {
binary += String.fromCharCode(byte);
});
return window.btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
async function createCodeChallenge(verifier) {
const cryptoObject = getCryptoObject();
if (cryptoObject?.subtle?.digest) {
const digest = await cryptoObject.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
return base64UrlEncode(new Uint8Array(digest));
}
return base64UrlEncode(sha256Fallback(verifier));
}
function savePendingLogin(state, verifier, returnTo, redirectUri) {
sessionStorage.setItem(STATE_KEY, state);
sessionStorage.setItem(VERIFIER_KEY, verifier);
sessionStorage.setItem(RETURN_TO_KEY, returnTo);
if (redirectUri) {
sessionStorage.setItem(`${PENDING_LOGIN_KEY}_redirect_uri`, redirectUri);
}
localStorage.setItem(PENDING_LOGIN_KEY, JSON.stringify({ state, verifier, returnTo, redirectUri }));
}
function clearPendingLogin() {
sessionStorage.removeItem(STATE_KEY);
sessionStorage.removeItem(VERIFIER_KEY);
sessionStorage.removeItem(RETURN_TO_KEY);
sessionStorage.removeItem(`${PENDING_LOGIN_KEY}_redirect_uri`);
localStorage.removeItem(PENDING_LOGIN_KEY);
}
function loadPendingLogin() {
const state = sessionStorage.getItem(STATE_KEY);
const verifier = sessionStorage.getItem(VERIFIER_KEY);
const returnTo = sessionStorage.getItem(RETURN_TO_KEY);
const redirectUri = sessionStorage.getItem(`${PENDING_LOGIN_KEY}_redirect_uri`);
if (state && verifier) {
return {
state,
verifier,
returnTo,
redirectUri,
};
}
const raw = localStorage.getItem(PENDING_LOGIN_KEY);
if (!raw) {
return null;
}
try {
const pendingLogin = JSON.parse(raw);
if (!pendingLogin?.state || !pendingLogin?.verifier) {
localStorage.removeItem(PENDING_LOGIN_KEY);
return null;
}
sessionStorage.setItem(STATE_KEY, pendingLogin.state);
sessionStorage.setItem(VERIFIER_KEY, pendingLogin.verifier);
if (pendingLogin.returnTo) {
sessionStorage.setItem(RETURN_TO_KEY, pendingLogin.returnTo);
}
if (pendingLogin.redirectUri) {
sessionStorage.setItem(`${PENDING_LOGIN_KEY}_redirect_uri`, pendingLogin.redirectUri);
}
return pendingLogin;
} catch {
localStorage.removeItem(PENDING_LOGIN_KEY);
return null;
}
}
function markForceLogin() {
sessionStorage.setItem(FORCE_LOGIN_KEY, "1");
}
function consumeForceLogin() {
const shouldForce = sessionStorage.getItem(FORCE_LOGIN_KEY) === "1";
sessionStorage.removeItem(FORCE_LOGIN_KEY);
return shouldForce;
}
function saveSession(session) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
}
function loadSession() {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) {
return null;
}
try {
return JSON.parse(raw);
} catch {
localStorage.removeItem(STORAGE_KEY);
return null;
}
}
function clearSession() {
localStorage.removeItem(STORAGE_KEY);
}
function isExpired(session) {
if (!session || !session.expiresAt) {
return true;
}
return Date.now() >= Number(session.expiresAt);
}
function decodeJwt(token) {
if (!token || token.split(".").length < 2) {
return null;
}
try {
const payload = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
const normalized = payload.padEnd(payload.length + ((4 - (payload.length % 4)) % 4), "=");
return JSON.parse(window.atob(normalized));
} catch {
return null;
}
}
async function fetchUserInfo(config, accessToken) {
if (!config.userInfoUrl || !accessToken) {
return null;
}
const response = await fetch(config.userInfoUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`User info request failed: ${response.status}`);
}
return response.json();
}
async function resolveProfile(config, tokenResult) {
const idTokenClaims = decodeJwt(tokenResult.id_token);
const accessTokenClaims = decodeJwt(tokenResult.access_token);
if (idTokenClaims) {
return idTokenClaims;
}
if (accessTokenClaims) {
return accessTokenClaims;
}
return fetchUserInfo(config, tokenResult.access_token);
}
function readProfileEmail(profile) {
return (
profile?.email ||
profile?.preferred_username ||
profile?.upn ||
profile?.unique_name ||
""
)
.trim()
.toLowerCase();
}
function hasRequiredGroup(config, profile) {
if (!config.requiredGroups.length) {
return true;
}
const groupValue = profile?.[config.groupClaim];
const groups = Array.isArray(groupValue) ? groupValue : typeof groupValue === "string" ? [groupValue] : [];
return config.requiredGroups.some((group) => groups.includes(group));
}
function isAuthorized(config, profile) {
if (!profile) {
return false;
}
const email = readProfileEmail(profile);
if (config.allowedEmails.length && !config.allowedEmails.includes(email)) {
return false;
}
if (config.allowedEmailDomains.length) {
const hasDomain = config.allowedEmailDomains.some((domain) => email.endsWith(`@${domain.toLowerCase()}`));
if (!hasDomain) {
return false;
}
}
return hasRequiredGroup(config, profile);
}
async function exchangeCode(config, code, verifier) {
const pendingLogin = loadPendingLogin();
const redirectUri = pendingLogin?.redirectUri || buildRedirectUri(config);
const body = new URLSearchParams({
grant_type: "authorization_code",
client_id: config.clientId,
code,
code_verifier: verifier,
redirect_uri: redirectUri,
});
Object.entries(config.extraTokenParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
body.set(key, String(value));
}
});
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body,
});
if (!response.ok) {
let errorDetail = "";
try {
const responseText = await response.text();
if (responseText) {
errorDetail = responseText;
}
} catch {
errorDetail = "";
}
const errorMessage = errorDetail
? `Token request failed: ${response.status} ${errorDetail}`
: `Token request failed: ${response.status}`;
throw new Error(errorMessage);
}
return response.json();
}
async function handleCallback(config) {
const currentUrl = new URL(window.location.href);
const code = currentUrl.searchParams.get("code");
const state = currentUrl.searchParams.get("state");
if (!code || !state) {
return false;
}
const pendingLogin = loadPendingLogin();
const expectedState = pendingLogin?.state || null;
const verifier = pendingLogin?.verifier || null;
const returnTo = pendingLogin?.returnTo || buildRedirectUri(config);
if (!expectedState || !verifier || expectedState !== state) {
clearPendingLogin();
throw new Error("Invalid OAuth state");
}
const tokenResult = await exchangeCode(config, code, verifier);
const profile = await resolveProfile(config, tokenResult);
if (!isAuthorized(config, profile)) {
clearPendingLogin();
clearSession();
throw new Error("User is not authorized");
}
const expiresIn = Number(tokenResult.expires_in || 3600);
saveSession({
accessToken: tokenResult.access_token,
idToken: tokenResult.id_token || "",
tokenType: tokenResult.token_type || "Bearer",
profile,
expiresAt: Date.now() + Math.max(expiresIn - 30, 30) * 1000,
});
clearPendingLogin();
return {
handled: true,
returnTo,
};
}
async function startLogin(config) {
const verifier = createRandomString(96);
const state = createRandomString(48);
const challenge = await createCodeChallenge(verifier);
const returnTo = buildReturnToUrl();
const redirectUri = buildRedirectUri(config);
const shouldForceLogin = consumeForceLogin();
savePendingLogin(state, verifier, returnTo, redirectUri);
const authorizeUrl = new URL(config.authorizeUrl);
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("client_id", config.clientId);
authorizeUrl.searchParams.set("redirect_uri", redirectUri);
authorizeUrl.searchParams.set("scope", config.scope);
authorizeUrl.searchParams.set("state", state);
authorizeUrl.searchParams.set("code_challenge", challenge);
authorizeUrl.searchParams.set("code_challenge_method", "S256");
Object.entries(config.extraAuthorizeParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
authorizeUrl.searchParams.set(key, String(value));
}
});
if (shouldForceLogin && !authorizeUrl.searchParams.has("prompt")) {
authorizeUrl.searchParams.set("prompt", "login");
}
renderOverlay(config, {
title: getMessage(config, "redirectTitle"),
description: getMessage(config, "redirectDescription"),
actions: [
{
label: getMessage(config, "redirectAction"),
onClick: () => {
window.location.assign(authorizeUrl.toString());
},
},
],
});
window.location.assign(authorizeUrl.toString());
}
async function restoreSession(config) {
const session = loadSession();
if (!session || isExpired(session)) {
clearSession();
return null;
}
if (!isAuthorized(config, session.profile)) {
clearSession();
return null;
}
return session;
}
function attachLogout(config) {
window.baronSsoLogout = () => {
const session = loadSession();
const idTokenHint = session?.idToken;
const localRedirectTarget = config.postLogoutRedirectUri || `${window.location.origin}/`;
const shouldUseLocalOnlyLogout = isLocalEnvironment() || !config.logoutUrl || !idTokenHint;
clearPendingLogin();
clearSession();
window.BARON_SSO_SESSION = null;
if (shouldUseLocalOnlyLogout) {
markForceLogin();
window.location.assign(localRedirectTarget);
return;
}
const logoutUrl = new URL(config.logoutUrl);
logoutUrl.searchParams.set("id_token_hint", idTokenHint);
logoutUrl.searchParams.set("client_id", config.clientId);
if (config.postLogoutRedirectUri) {
logoutUrl.searchParams.set("post_logout_redirect_uri", config.postLogoutRedirectUri);
}
window.location.assign(logoutUrl.toString());
};
}
export async function ensureBaronSsoAuth(overrides = {}) {
const currentUrl = new URL(window.location.href);
const hasCallbackParams = currentUrl.searchParams.has("code") || currentUrl.searchParams.has("state");
const workerAuth = getWorkerManagedAuth();
const workerPublicPaths = normalizeArray(overrides.publicPaths);
const workerPublicPath = workerPublicPaths.some((entry) => matchesPublicPath(window.location.pathname, entry));
const workerForceAuth = Boolean(overrides.forceAuth);
if (workerAuth) {
attachWorkerManagedLogout(workerAuth);
if (workerAuth.authenticated) {
const session = {
workerManaged: true,
profile: workerAuth.profile || {},
expiresAt: Number.MAX_SAFE_INTEGER,
};
window.BARON_SSO_SESSION = session;
hideOverlay();
return { status: "worker-authenticated", session };
}
if (workerForceAuth || !workerPublicPath) {
window.location.assign(workerAuth.loginUrl || "/auth/login");
return { status: "worker-redirecting" };
}
return { status: "public" };
}
const config = await mergeConfig(overrides);
const publicPath = isPublicPath(config);
if (!config.enabled) {
return { status: "disabled" };
}
attachLogout(config);
if (publicPath && !config.forceAuth) {
const session = await restoreSession(config);
if (session) {
window.BARON_SSO_SESSION = session;
hideOverlay();
return { status: "authenticated-public", session };
}
return { status: "public" };
}
await waitForBody();
renderOverlay(config, {
title: getMessage(config, "loadingTitle"),
description: getMessage(config, "loadingDescription"),
});
if (!isConfigured(config)) {
renderOverlay(config, {
title: getMessage(config, "configTitle"),
description: getMessage(config, "configDescription"),
});
return { status: "misconfigured" };
}
try {
if (isCallbackPath(config) && !hasCallbackParams) {
window.location.replace(config.postLogoutRedirectUri || `${window.location.origin}/`);
return { status: "callback-idle" };
}
const callbackResult = await handleCallback(config);
if (callbackResult?.handled) {
const destination = callbackResult.returnTo || `${window.location.origin}/`;
if (destination !== window.location.href) {
window.location.replace(destination);
return { status: "redirected", returnTo: destination };
}
}
const session = await restoreSession(config);
if (session) {
window.BARON_SSO_SESSION = session;
hideOverlay();
return { status: "authenticated", session };
}
await startLogin(config);
return { status: "redirecting" };
} catch (error) {
console.error("BARON SSO authentication failed.", error);
const isUnauthorized = error.message === "User is not authorized";
const detailDescription = isUnauthorized
? getMessage(config, "unauthorizedDescription")
: isLocalEnvironment()
? `${getMessage(config, "exchangeDescription")} (${error.message})`
: getMessage(config, "exchangeDescription");
clearSession();
clearPendingLogin();
renderOverlay(config, {
title: getMessage(config, "unauthorizedTitle"),
description: detailDescription,
actions: [
{
label: getMessage(config, "retryAction"),
onClick: () => {
startLogin(config);
},
},
{
label: getMessage(config, "logoutAction"),
secondary: true,
onClick: () => {
window.baronSsoLogout();
},
},
],
});
return { status: "failed", error };
}
}