107 lines
2.9 KiB
JavaScript
107 lines
2.9 KiB
JavaScript
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,
|
|
scope: "openid profile email",
|
|
});
|
|
|
|
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 });
|
|
}
|