import Keycloak from "keycloak-js"; import { reactive } from "vue"; export const auth = reactive({ ready: false, enabled: false, authenticated: false, username: "", email: "", groups: [], loginUrl: "", resetUrl: "", token: "", }); let keycloak = null; let initPromise = null; function normalizeGroups(groups) { if (!Array.isArray(groups)) return []; return groups .filter((g) => typeof g === "string") .map((g) => g.replace(/^\//, "")) .filter(Boolean); } function updateFromToken() { const parsed = keycloak?.tokenParsed || {}; auth.authenticated = Boolean(keycloak?.authenticated); auth.token = keycloak?.token || ""; auth.username = parsed.preferred_username || ""; auth.email = parsed.email || ""; auth.groups = normalizeGroups(parsed.groups); } export async function initAuth() { if (initPromise) return initPromise; initPromise = (async () => { try { const resp = await fetch("/api/auth/config", { headers: { Accept: "application/json" } }); if (!resp.ok) throw new Error(`auth config ${resp.status}`); const cfg = await resp.json(); auth.enabled = Boolean(cfg.enabled); auth.loginUrl = cfg.login_url || ""; auth.resetUrl = cfg.reset_url || ""; if (!auth.enabled) return; keycloak = new Keycloak({ url: cfg.url, realm: cfg.realm, clientId: cfg.client_id, }); const authenticated = await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256", silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`, checkLoginIframe: true, }); auth.authenticated = authenticated; updateFromToken(); keycloak.onAuthSuccess = () => updateFromToken(); keycloak.onAuthLogout = () => updateFromToken(); keycloak.onAuthRefreshSuccess = () => updateFromToken(); keycloak.onTokenExpired = () => { keycloak .updateToken(30) .then(() => updateFromToken()) .catch(() => updateFromToken()); }; window.setInterval(() => { if (!keycloak?.authenticated) return; keycloak.updateToken(60).then(updateFromToken).catch(() => {}); }, 30_000); } catch { auth.enabled = false; } finally { auth.ready = true; } })(); return initPromise; } export async function login(redirectPath = window.location.pathname + window.location.search + window.location.hash) { if (!keycloak) return; const redirectUri = new URL(redirectPath, window.location.origin).toString(); await keycloak.login({ redirectUri }); } export async function logout() { if (!keycloak) return; await keycloak.logout({ redirectUri: window.location.origin }); } export async function authFetch(url, options = {}) { const headers = new Headers(options.headers || {}); if (auth.token) headers.set("Authorization", `Bearer ${auth.token}`); return fetch(url, { ...options, headers }); }