From 6d1006f7846e10d37a0b96a1d98e47b639e34994 Mon Sep 17 00:00:00 2001 From: Mohamadzadeh <7796733@gmail.com> Date: Sat, 13 Jun 2026 11:01:16 +0330 Subject: [PATCH] add permission --- dist/index.js | 1772 +++++++++------------------------------ package.json | 2 + src/core/permission.jsx | 240 ++++++ src/index.js | 16 + vite.config.js | 2 + 5 files changed, 650 insertions(+), 1382 deletions(-) create mode 100644 src/core/permission.jsx diff --git a/dist/index.js b/dist/index.js index 237b3a7..62f7f0a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,59 +1,54 @@ -var Pe = Object.defineProperty; -var Se = (r) => { - throw TypeError(r); -}; -var je = (r, e, t) => e in r ? Pe(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t; -var f = (r, e, t) => je(r, typeof e != "symbol" ? e + "" : e, t), oe = (r, e, t) => e.has(r) || Se("Cannot " + t); -var h = (r, e, t) => (oe(r, e, "read from private field"), t ? t.call(r) : e.get(r)), A = (r, e, t) => e.has(r) ? Se("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, t), B = (r, e, t, i) => (oe(r, e, "write to private field"), i ? i.call(r, t) : e.set(r, t), t), d = (r, e, t) => (oe(r, e, "access private method"), t); -import { jsxs as W, jsx as S } from "react/jsx-runtime"; -import { useState as I, useRef as ie, useEffect as O, useCallback as H, createContext as We, useMemo as Oe } from "react"; -import { HStack as K, IconButton as G, Button as ke, Input as be, Text as j } from "@chakra-ui/react"; -import { IoChevronForwardOutline as ye, IoChevronBackOutline as ve } from "react-icons/io5"; -const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => String(r).replace(/\d/g, (e) => "۰۱۲۳۴۵۶۷۸۹"[Number(e)]), V = (r) => String(r).replace(/[۰-۹]/g, (t) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(t))), bt = ({ - currentPage: r, - totalPages: e, - totalCount: t, - onPageChange: i +import { jsxs as k, jsx as c } from "react/jsx-runtime"; +import { useState as B, useRef as T, useEffect as g, useCallback as D, createContext as W, useMemo as I, useContext as G } from "react"; +import { HStack as S, IconButton as M, Button as _, Input as F, Text as A } from "@chakra-ui/react"; +import { IoChevronForwardOutline as V, IoChevronBackOutline as j } from "react-icons/io5"; +import J from "keycloak-js"; +import { useQuery as Q } from "@tanstack/react-query"; +const fe = (e) => new Intl.NumberFormat("fa-IR").format(Number(e)), d = (e) => String(e).replace(/\d/g, (n) => "۰۱۲۳۴۵۶۷۸۹"[Number(n)]), N = (e) => String(e).replace(/[۰-۹]/g, (r) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(r))), de = ({ + currentPage: e, + totalPages: n, + totalCount: r, + onPageChange: s }) => { - const [n, o] = I(v(r)), [c, a] = I(!1), [l, u] = I(null), w = ie(null), m = [.../* @__PURE__ */ new Set([1, r, e])].filter( - (p) => p >= 1 && p <= e + const [l, i] = B(d(e)), [w, p] = B(!1), [m, o] = B(null), h = T(null), x = [.../* @__PURE__ */ new Set([1, e, n])].filter( + (t) => t >= 1 && t <= n ); - O(() => { - if (l !== null) { - r === l && (u(null), a(!1), o(v(r))); + g(() => { + if (m !== null) { + e === m && (o(null), p(!1), i(d(e))); return; } - c || o(v(r)); - }, [r, c, l]), O(() => { - if (!c || !n || l !== null) return; - const p = parseInt(V(n), 10); - if (isNaN(p)) return; - const k = setTimeout(() => { - var C; - const g = Math.min(Math.max(p, 1), e); - g !== r ? (u(g), o(v(g)), i(g)) : (a(!1), o(v(r))), (C = w.current) == null || C.blur(); + w || i(d(e)); + }, [e, w, m]), g(() => { + if (!w || !l || m !== null) return; + const t = parseInt(N(l), 10); + if (isNaN(t)) return; + const a = setTimeout(() => { + var b; + const f = Math.min(Math.max(t, 1), n); + f !== e ? (o(f), i(d(f)), s(f)) : (p(!1), i(d(e))), (b = h.current) == null || b.blur(); }, 1e3); - return () => clearTimeout(k); + return () => clearTimeout(a); }, [ - n, - c, l, + w, + m, + n, e, - r, - i + s ]); - const T = () => { - var g, C; - const p = parseInt(V(n), 10); - if (isNaN(p)) { - o(v(r)), a(!1), u(null), (g = w.current) == null || g.blur(); + const v = () => { + var f, b; + const t = parseInt(N(l), 10); + if (isNaN(t)) { + i(d(e)), p(!1), o(null), (f = h.current) == null || f.blur(); return; } - const k = Math.min(Math.max(p, 1), e); - k !== r ? (u(k), o(v(k)), i(k)) : (o(v(r)), a(!1), u(null)), (C = w.current) == null || C.blur(); + const a = Math.min(Math.max(t, 1), n); + a !== e ? (o(a), i(d(a)), s(a)) : (i(d(e)), p(!1), o(null)), (b = h.current) == null || b.blur(); }; - return e === 0 || !t ? null : /* @__PURE__ */ W( - K, + return n === 0 || !r ? null : /* @__PURE__ */ k( + S, { justifyContent: "space-around", backgroundColor: "white", @@ -64,68 +59,68 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S borderColor: "blue.200", w: "100%", children: [ - /* @__PURE__ */ W(K, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ - /* @__PURE__ */ S( - G, + /* @__PURE__ */ k(S, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => i(r - 1), - isDisabled: r === 1, - icon: /* @__PURE__ */ S(ye, {}), + onClick: () => s(e - 1), + isDisabled: e === 1, + icon: /* @__PURE__ */ c(V, {}), "aria-label": "صفحه قبلی" } ), - m.map((p) => /* @__PURE__ */ S( - ke, + x.map((t) => /* @__PURE__ */ c( + _, { size: "md", px: 2, - bg: r === p ? "#4B49AC" : "#e6e6f1", - color: r === p ? "#e6e6f1" : "#4B49AC", - _hover: { bg: r === p ? "#4B49AC" : "#cbcbe8" }, - onClick: () => i(p), + bg: e === t ? "#4B49AC" : "#e6e6f1", + color: e === t ? "#e6e6f1" : "#4B49AC", + _hover: { bg: e === t ? "#4B49AC" : "#cbcbe8" }, + onClick: () => s(t), variant: "soft", - children: v(p) + children: d(t) }, - p + t )), - /* @__PURE__ */ S( - G, + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => i(r + 1), - isDisabled: r === e, - icon: /* @__PURE__ */ S(ve, {}), + onClick: () => s(e + 1), + isDisabled: e === n, + icon: /* @__PURE__ */ c(j, {}), "aria-label": "صفحه بعدی" } ), - /* @__PURE__ */ S( - be, + /* @__PURE__ */ c( + F, { - ref: w, + ref: h, type: "text", inputMode: "numeric", placeholder: "صفحه", w: "56px", - value: n, + value: l, onFocus: () => { - a(!0), u(null), o(""); + p(!0), o(null), i(""); }, - onChange: (p) => { - const k = p.target.value, g = V(k).replace(/\D/g, ""); - o(v(g)); + onChange: (t) => { + const a = t.target.value, f = N(a).replace(/\D/g, ""); + i(d(f)); }, onBlur: () => { - n || (o(v(r)), a(!1), u(null)); + l || (i(d(e)), p(!1), o(null)); }, - onKeyDown: (p) => { - p.key === "Enter" && T(); + onKeyDown: (t) => { + t.key === "Enter" && v(); }, textAlign: "center", color: "#1D2939", @@ -135,58 +130,58 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S } ) ] }), - /* @__PURE__ */ W(K, { spacing: 2, children: [ - /* @__PURE__ */ S(j, { w: "auto", fontWeight: "bold", whiteSpace: "nowrap", children: "تعداد کل:" }), - /* @__PURE__ */ S(j, { children: v(t) }) + /* @__PURE__ */ k(S, { spacing: 2, children: [ + /* @__PURE__ */ c(A, { w: "auto", fontWeight: "bold", whiteSpace: "nowrap", children: "تعداد کل:" }), + /* @__PURE__ */ c(A, { children: d(r) }) ] }) ] } ); -}, yt = ({ - currentPage: r, - totalPages: e, - totalCount: t, - onPageChange: i +}, pe = ({ + currentPage: e, + totalPages: n, + totalCount: r, + onPageChange: s }) => { - const [n, o] = I(v(r)), [c, a] = I(!1), [l, u] = I(null), w = ie(null), m = Array.from( - /* @__PURE__ */ new Set([1, r, r + 1, e]) - ).filter((p) => p >= 1 && p <= e).sort((p, k) => p - k); - O(() => { - if (l !== null) { - r === l && (u(null), a(!1), o(v(r))); + const [l, i] = B(d(e)), [w, p] = B(!1), [m, o] = B(null), h = T(null), x = Array.from( + /* @__PURE__ */ new Set([1, e, e + 1, n]) + ).filter((t) => t >= 1 && t <= n).sort((t, a) => t - a); + g(() => { + if (m !== null) { + e === m && (o(null), p(!1), i(d(e))); return; } - c || o(v(r)); - }, [r, c, l]), O(() => { - if (!c || !n || l !== null) return; - const p = parseInt(V(n), 10); - if (isNaN(p)) return; - const k = setTimeout(() => { - var C; - const g = Math.min(Math.max(p, 1), e); - g !== r ? (u(g), o(v(g)), i(g)) : (a(!1), o(v(r))), (C = w.current) == null || C.blur(); + w || i(d(e)); + }, [e, w, m]), g(() => { + if (!w || !l || m !== null) return; + const t = parseInt(N(l), 10); + if (isNaN(t)) return; + const a = setTimeout(() => { + var b; + const f = Math.min(Math.max(t, 1), n); + f !== e ? (o(f), i(d(f)), s(f)) : (p(!1), i(d(e))), (b = h.current) == null || b.blur(); }, 1e3); - return () => clearTimeout(k); + return () => clearTimeout(a); }, [ - n, - c, l, + w, + m, + n, e, - r, - i + s ]); - const T = () => { - var g, C; - const p = parseInt(V(n), 10); - if (isNaN(p)) { - o(v(r)), a(!1), u(null), (g = w.current) == null || g.blur(); + const v = () => { + var f, b; + const t = parseInt(N(l), 10); + if (isNaN(t)) { + i(d(e)), p(!1), o(null), (f = h.current) == null || f.blur(); return; } - const k = Math.min(Math.max(p, 1), e); - k !== r ? (u(k), o(v(k)), i(k)) : (o(v(r)), a(!1), u(null)), (C = w.current) == null || C.blur(); + const a = Math.min(Math.max(t, 1), n); + a !== e ? (o(a), i(d(a)), s(a)) : (i(d(e)), p(!1), o(null)), (b = h.current) == null || b.blur(); }; - return e === 0 || !t ? null : /* @__PURE__ */ W( - K, + return n === 0 || !r ? null : /* @__PURE__ */ k( + S, { justifyContent: "space-around", backgroundColor: "white", @@ -197,73 +192,73 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S borderColor: "blue.200", w: "100%", children: [ - /* @__PURE__ */ W(K, { spacing: 2, whiteSpace: "nowrap", children: [ - /* @__PURE__ */ S(j, { fontWeight: "bold", children: "صفحه" }), - /* @__PURE__ */ S(j, { children: v(r) }), - /* @__PURE__ */ S(j, { fontWeight: "bold", children: "از" }), - /* @__PURE__ */ S(j, { children: v(e) }) + /* @__PURE__ */ k(S, { spacing: 2, whiteSpace: "nowrap", children: [ + /* @__PURE__ */ c(A, { fontWeight: "bold", children: "صفحه" }), + /* @__PURE__ */ c(A, { children: d(e) }), + /* @__PURE__ */ c(A, { fontWeight: "bold", children: "از" }), + /* @__PURE__ */ c(A, { children: d(n) }) ] }), - /* @__PURE__ */ W(K, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ - /* @__PURE__ */ S( - G, + /* @__PURE__ */ k(S, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => i(r - 1), - isDisabled: r === 1, - icon: /* @__PURE__ */ S(ye, {}), + onClick: () => s(e - 1), + isDisabled: e === 1, + icon: /* @__PURE__ */ c(V, {}), "aria-label": "صفحه قبلی" } ), - m.map((p) => /* @__PURE__ */ S( - ke, + x.map((t) => /* @__PURE__ */ c( + _, { size: "md", - bg: r === p ? "#4B49AC" : "#e6e6f1", - color: r === p ? "#e6e6f1" : "#4B49AC", - _hover: { bg: r === p ? "#4B49AC" : "#cbcbe8" }, - onClick: () => i(p), + bg: e === t ? "#4B49AC" : "#e6e6f1", + color: e === t ? "#e6e6f1" : "#4B49AC", + _hover: { bg: e === t ? "#4B49AC" : "#cbcbe8" }, + onClick: () => s(t), variant: "soft", - children: v(p) + children: d(t) }, - p + t )), - /* @__PURE__ */ S( - G, + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => i(r + 1), - isDisabled: r === e, - icon: /* @__PURE__ */ S(ve, {}), + onClick: () => s(e + 1), + isDisabled: e === n, + icon: /* @__PURE__ */ c(j, {}), "aria-label": "صفحه بعدی" } ), - /* @__PURE__ */ S( - be, + /* @__PURE__ */ c( + F, { - ref: w, + ref: h, type: "text", inputMode: "numeric", placeholder: "صفحه", w: "100px", - value: n, + value: l, onFocus: () => { - a(!0), u(null), o(""); + p(!0), o(null), i(""); }, - onChange: (p) => { - const k = p.target.value, g = V(k).replace(/\D/g, ""); - o(v(g)); + onChange: (t) => { + const a = t.target.value, f = N(a).replace(/\D/g, ""); + i(d(f)); }, onBlur: () => { - n || (o(v(r)), a(!1), u(null)); + l || (i(d(e)), p(!1), o(null)); }, - onKeyDown: (p) => { - p.key === "Enter" && T(); + onKeyDown: (t) => { + t.key === "Enter" && v(); }, textAlign: "center", color: "#1D2939", @@ -273,69 +268,69 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S } ) ] }), - /* @__PURE__ */ W(K, { spacing: 2, whiteSpace: "nowrap", children: [ - /* @__PURE__ */ S(j, { fontWeight: "bold", children: "تعداد کل:" }), - /* @__PURE__ */ S(j, { children: v(t) }) + /* @__PURE__ */ k(S, { spacing: 2, whiteSpace: "nowrap", children: [ + /* @__PURE__ */ c(A, { fontWeight: "bold", children: "تعداد کل:" }), + /* @__PURE__ */ c(A, { children: d(r) }) ] }) ] } ); -}, vt = ({ - currentPage: r, - totalPages: e, - totalCount: t, - onPageChange: i +}, me = ({ + currentPage: e, + totalPages: n, + totalCount: r, + onPageChange: s }) => { - const [n, o] = I(""), [c, a] = I(!1), [l, u] = I(null), w = ie(null), m = l ?? r, T = H( - (U) => { - const E = Math.min(Math.max(U, 1), e); - a(!1), o(""), u(null), E !== r && i(E); + const [l, i] = B(""), [w, p] = B(!1), [m, o] = B(null), h = T(null), x = m ?? e, v = D( + (y) => { + const C = Math.min(Math.max(y, 1), n); + p(!1), i(""), o(null), C !== e && s(C); }, - [r, e, i] - ), p = H(() => { - if (!n) { - a(!1), o(""), u(null); + [e, n, s] + ), t = D(() => { + if (!l) { + p(!1), i(""), o(null); return; } - const U = parseInt(V(n), 10); - if (isNaN(U)) { - a(!1), o(""), u(null); + const y = parseInt(N(l), 10); + if (isNaN(y)) { + p(!1), i(""), o(null); return; } - const E = Math.min(Math.max(U, 1), e); - a(!1), o(""), E !== r ? (u(E), i(E)) : u(null); - }, [n, r, e, i]); - O(() => { - var U; - c && ((U = w.current) == null || U.focus()); - }, [c]), O(() => { - l !== null && r === l && u(null); - }, [r, l]), O(() => { - if (!c || !n) return; - const U = setTimeout(() => { - p(); + const C = Math.min(Math.max(y, 1), n); + p(!1), i(""), C !== e ? (o(C), s(C)) : o(null); + }, [l, e, n, s]); + g(() => { + var y; + w && ((y = h.current) == null || y.focus()); + }, [w]), g(() => { + m !== null && e === m && o(null); + }, [e, m]), g(() => { + if (!w || !l) return; + const y = setTimeout(() => { + t(); }, 1e3); - return () => clearTimeout(U); - }, [n, c, p]); - const k = () => { - u(null), o(""), a(!0); - }, g = (U, E = !1) => /* @__PURE__ */ S( - ke, + return () => clearTimeout(y); + }, [l, w, t]); + const a = () => { + o(null), i(""), p(!0); + }, f = (y, C = !1) => /* @__PURE__ */ c( + _, { size: "md", - bg: E ? "#4B49AC" : "#e6e6f1", - color: E ? "#e6e6f1" : "#4B49AC", - _hover: { bg: E ? "#4B49AC" : "#cbcbe8" }, - onClick: E ? k : () => T(U), + bg: C ? "#4B49AC" : "#e6e6f1", + color: C ? "#e6e6f1" : "#4B49AC", + _hover: { bg: C ? "#4B49AC" : "#cbcbe8" }, + onClick: C ? a : () => v(y), variant: "soft", - children: v(U) + children: d(y) }, - U + y ); - if (e === 0 || !t) return null; - const C = e > 1 && m !== 1, F = e > 1 && m !== e; - return /* @__PURE__ */ S( - K, + if (n === 0 || !r) return null; + const b = n > 1 && x !== 1, E = n > 1 && x !== n; + return /* @__PURE__ */ c( + S, { justifyContent: "center", backgroundColor: "white", @@ -345,39 +340,39 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S border: "1px", borderColor: "blue.200", w: "100%", - children: /* @__PURE__ */ W(K, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ - /* @__PURE__ */ S( - G, + children: /* @__PURE__ */ k(S, { spacing: 2, alignItems: "center", justifyContent: "center", children: [ + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => T(m - 1), - isDisabled: m === 1, - icon: /* @__PURE__ */ S(ye, {}), + onClick: () => v(x - 1), + isDisabled: x === 1, + icon: /* @__PURE__ */ c(V, {}), "aria-label": "صفحه قبلی" } ), - C && g(1), - c ? /* @__PURE__ */ S( - be, + b && f(1), + w ? /* @__PURE__ */ c( + F, { - ref: w, + ref: h, type: "text", inputMode: "numeric", placeholder: "صفحه", w: "48px", - value: n, - onChange: (U) => { - const E = U.target.value, ze = V(E).replace(/\D/g, ""); - o(v(ze)); + value: l, + onChange: (y) => { + const C = y.target.value, q = N(C).replace(/\D/g, ""); + i(d(q)); }, onBlur: () => { - n ? p() : (o(""), a(!1), u(null)); + l ? t() : (i(""), p(!1), o(null)); }, - onKeyDown: (U) => { - U.key === "Enter" && p(), U.key === "Escape" && (o(""), a(!1), u(null)); + onKeyDown: (y) => { + y.key === "Enter" && t(), y.key === "Escape" && (i(""), p(!1), o(null)); }, textAlign: "center", color: "#1D2939", @@ -385,1201 +380,214 @@ const kt = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), v = (r) => S minW: 0, dir: "ltr" } - ) : g(m, !0), - F && g(e), - /* @__PURE__ */ S( - G, + ) : f(x, !0), + E && f(n), + /* @__PURE__ */ c( + M, { size: "md", bg: "#e6e6f1", color: "#4B49AC", _hover: { bg: "#cbcbe8" }, - onClick: () => T(m + 1), - isDisabled: m === e, - icon: /* @__PURE__ */ S(ve, {}), + onClick: () => v(x + 1), + isDisabled: x === n, + icon: /* @__PURE__ */ c(j, {}), "aria-label": "صفحه بعدی" } ) ] }) } ); -}, Ve = "application/json"; -var N, _, J, Y, M, q, y, L, s, ee, _e, Ee, Ae, Ie, ae, xe, ce, Le, Re, te, X, le, z, Me, de, P, re, Z, R, ue; -class Fe { - /** - * @param {KeycloakConfig} config - */ - constructor(e) { - A(this, s); - /** @type {Pick, 'resolve' | 'reject'>[]} */ - A(this, N, []); - /** @type {KeycloakAdapter} */ - A(this, _); - /** @type {boolean} */ - A(this, J, !0); - /** @type {CallbackStorage} */ - A(this, Y); - A(this, M, d(this, s, ue).call(this, console.info)); - A(this, q, d(this, s, ue).call(this, console.warn)); - /** @type {LoginIframe} */ - A(this, y, { - enable: !0, - callbackList: [], - interval: 5 - }); - /** @type {KeycloakConfig} config */ - A(this, L); - f(this, "didInitialize", !1); - f(this, "authenticated", !1); - f(this, "loginRequired", !1); - /** @type {KeycloakResponseMode} */ - f(this, "responseMode", "fragment"); - /** @type {KeycloakResponseType} */ - f(this, "responseType", "code"); - /** @type {KeycloakFlow} */ - f(this, "flow", "standard"); - /** @type {number?} */ - f(this, "timeSkew", null); - /** @type {string=} */ - f(this, "redirectUri"); - /** @type {string=} */ - f(this, "silentCheckSsoRedirectUri"); - /** @type {boolean} */ - f(this, "silentCheckSsoFallback", !0); - /** @type {KeycloakPkceMethod} */ - f(this, "pkceMethod", "S256"); - f(this, "enableLogging", !1); - /** @type {'GET' | 'POST'} */ - f(this, "logoutMethod", "GET"); - /** @type {string=} */ - f(this, "scope"); - f(this, "messageReceiveTimeout", 1e4); - /** @type {string=} */ - f(this, "idToken"); - /** @type {KeycloakTokenParsed=} */ - f(this, "idTokenParsed"); - /** @type {string=} */ - f(this, "token"); - /** @type {KeycloakTokenParsed=} */ - f(this, "tokenParsed"); - /** @type {string=} */ - f(this, "refreshToken"); - /** @type {KeycloakTokenParsed=} */ - f(this, "refreshTokenParsed"); - /** @type {string=} */ - f(this, "clientId"); - /** @type {string=} */ - f(this, "sessionId"); - /** @type {string=} */ - f(this, "subject"); - /** @type {string=} */ - f(this, "authServerUrl"); - /** @type {string=} */ - f(this, "realm"); - /** @type {KeycloakRoles=} */ - f(this, "realmAccess"); - /** @type {KeycloakResourceAccess=} */ - f(this, "resourceAccess"); - /** @type {KeycloakProfile=} */ - f(this, "profile"); - /** @type {{}=} */ - f(this, "userInfo"); - /** @type {Endpoints} */ - f(this, "endpoints"); - /** @type {number=} */ - f(this, "tokenTimeoutHandle"); - /** @type {() => void=} */ - f(this, "onAuthSuccess"); - /** @type {(errorData?: KeycloakError) => void=} */ - f(this, "onAuthError"); - /** @type {() => void=} */ - f(this, "onAuthRefreshSuccess"); - /** @type {() => void=} */ - f(this, "onAuthRefreshError"); - /** @type {() => void=} */ - f(this, "onTokenExpired"); - /** @type {() => void=} */ - f(this, "onAuthLogout"); - /** @type {(authenticated: boolean) => void=} */ - f(this, "onReady"); - /** @type {(status: 'success' | 'cancelled' | 'error', action: string) => void=} */ - f(this, "onActionUpdate"); - /** - * @param {KeycloakInitOptions} initOptions - * @returns {Promise} - */ - f(this, "init", async (e = {}) => { - var i; - if (this.didInitialize) - throw new Error("A 'Keycloak' instance can only be initialized once."); - this.didInitialize = !0, B(this, Y, Qe()); - const t = ["default", "cordova", "cordova-native"]; - if (typeof e.adapter == "string" && t.includes(e.adapter) ? B(this, _, d(this, s, ee).call(this, e.adapter)) : typeof e.adapter == "object" ? B(this, _, e.adapter) : "Cordova" in window || "cordova" in window ? B(this, _, d(this, s, ee).call(this, "cordova")) : B(this, _, d(this, s, ee).call(this, "default")), typeof e.useNonce < "u" && B(this, J, e.useNonce), typeof e.checkLoginIframe < "u" && (h(this, y).enable = e.checkLoginIframe), e.checkLoginIframeInterval && (h(this, y).interval = e.checkLoginIframeInterval), e.onLoad === "login-required" && (this.loginRequired = !0), e.responseMode) - if (e.responseMode === "query" || e.responseMode === "fragment") - this.responseMode = e.responseMode; - else - throw new Error("Invalid value for responseMode"); - if (e.flow) { - switch (e.flow) { - case "standard": - this.responseType = "code"; - break; - case "implicit": - this.responseType = "id_token token"; - break; - case "hybrid": - this.responseType = "code id_token token"; - break; - default: - throw new Error("Invalid value for flow"); - } - this.flow = e.flow; - } - if (typeof e.timeSkew == "number" && (this.timeSkew = e.timeSkew), e.redirectUri && (this.redirectUri = e.redirectUri), e.silentCheckSsoRedirectUri && (this.silentCheckSsoRedirectUri = e.silentCheckSsoRedirectUri), typeof e.silentCheckSsoFallback == "boolean" && (this.silentCheckSsoFallback = e.silentCheckSsoFallback), typeof e.pkceMethod < "u") { - if (e.pkceMethod !== "S256" && e.pkceMethod !== !1) - throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${e.pkceMethod}.`); - this.pkceMethod = e.pkceMethod; - } - return typeof e.enableLogging == "boolean" && (this.enableLogging = e.enableLogging), e.logoutMethod === "POST" && (this.logoutMethod = "POST"), typeof e.scope == "string" && (this.scope = e.scope), typeof e.messageReceiveTimeout == "number" && e.messageReceiveTimeout > 0 && (this.messageReceiveTimeout = e.messageReceiveTimeout), await d(this, s, Ie).call(this), await d(this, s, Le).call(this), await d(this, s, Re).call(this, e), (i = this.onReady) == null || i.call(this, this.authenticated), this.authenticated; - }); - /** - * @param {KeycloakLoginOptions} [options] - * @returns {Promise} - */ - f(this, "login", (e) => h(this, _).login(e)); - /** - * @param {KeycloakLoginOptions} [options] - * @returns {Promise} - */ - f(this, "createLoginUrl", async (e) => { - const t = Ce(), i = Ce(), n = h(this, _).redirectUri(e), o = { - state: t, - nonce: i, - redirectUri: n, - loginOptions: e - }; - e != null && e.prompt && (o.prompt = e.prompt); - const c = (e == null ? void 0 : e.action) === "register" ? this.endpoints.register() : this.endpoints.authorize(); - let a = (e == null ? void 0 : e.scope) || this.scope; - const l = a ? a.split(" ") : []; - l.includes("openid") || l.unshift("openid"), a = l.join(" "); - const u = new URLSearchParams([ - [ - "client_id", - /** @type {string} */ - this.clientId - ], - ["redirect_uri", n], - ["state", t], - ["response_mode", this.responseMode], - ["response_type", this.responseType], - ["scope", a] - ]); - if (h(this, J) && u.append("nonce", i), e != null && e.prompt && u.append("prompt", e.prompt), typeof (e == null ? void 0 : e.maxAge) == "number" && u.append("max_age", e.maxAge.toString()), e != null && e.loginHint && u.append("login_hint", e.loginHint), e != null && e.idpHint && u.append("kc_idp_hint", e.idpHint), e != null && e.action && e.action !== "register" && u.append("kc_action", e.action), e != null && e.locale && u.append("ui_locales", e.locale), e != null && e.acr && u.append("claims", He(e.acr)), e != null && e.acrValues && u.append("acr_values", e.acrValues), this.pkceMethod) - try { - const w = $e(96), m = await Je(this.pkceMethod, w); - o.pkceCodeVerifier = w, u.append("code_challenge", m), u.append("code_challenge_method", this.pkceMethod); - } catch (w) { - throw new Error("Failed to generate PKCE challenge.", { cause: w }); - } - return h(this, Y).add(o), `${c}?${u.toString()}`; - }); - /** - * @param {KeycloakLogoutOptions} [options] - * @returns {Promise} - */ - f(this, "logout", (e) => h(this, _).logout(e)); - /** - * @param {KeycloakLogoutOptions} [options] - * @returns {string} - */ - f(this, "createLogoutUrl", (e) => { - const t = (e == null ? void 0 : e.logoutMethod) ?? this.logoutMethod, i = this.endpoints.logout(); - if (t === "POST") - return i; - const n = new URLSearchParams([ - [ - "client_id", - /** @type {string} */ - this.clientId - ], - ["post_logout_redirect_uri", h(this, _).redirectUri(e)] - ]); - return this.idToken && n.append("id_token_hint", this.idToken), `${i}?${n.toString()}`; - }); - /** - * @param {KeycloakRegisterOptions} [options] - * @returns {Promise} - */ - f(this, "register", (e) => h(this, _).register(e)); - /** - * @param {KeycloakRegisterOptions} [options] - * @returns {Promise} - */ - f(this, "createRegisterUrl", (e) => this.createLoginUrl({ ...e, action: "register" })); - /** - * @param {KeycloakAccountOptions} [options] - * @returns {string} - */ - f(this, "createAccountUrl", (e) => { - const t = d(this, s, R).call(this); - if (!t) - throw new Error("Unable to create account URL, make sure the adapter is not configured using a generic OIDC provider."); - const i = new URLSearchParams([ - [ - "referrer", - /** @type {string} */ - this.clientId - ], - ["referrer_uri", h(this, _).redirectUri(e)] - ]); - return `${t}/account?${i.toString()}`; - }); - /** - * @returns {Promise} - */ - f(this, "accountManagement", () => h(this, _).accountManagement()); - /** - * @param {string} role - * @returns {boolean} - */ - f(this, "hasRealmRole", (e) => { - const t = this.realmAccess; - return !!t && t.roles.indexOf(e) >= 0; - }); - /** - * @param {string} role - * @param {string} [resource] - * @returns {boolean} - */ - f(this, "hasResourceRole", (e, t) => { - if (!this.resourceAccess) - return !1; - const i = this.resourceAccess[t || /** @type {string} */ - this.clientId]; - return !!i && i.roles.indexOf(e) >= 0; - }); - /** - * @returns {Promise} - */ - f(this, "loadUserProfile", async () => { - const e = d(this, s, R).call(this); - if (!e) - throw new Error("Unable to load user profile, make sure the adapter is not configured using a generic OIDC provider."); - const t = `${e}/account`, i = await Q(t, { - headers: [Te(this.token)] - }); - return this.profile = i; - }); - /** - * @returns {Promise<{}>} - */ - f(this, "loadUserInfo", async () => { - const e = this.endpoints.userinfo(), t = await Q(e, { - headers: [Te(this.token)] - }); - return this.userInfo = t; - }); - /** - * @param {number} [minValidity] - * @returns {boolean} - */ - f(this, "isTokenExpired", (e) => { - if (!this.tokenParsed || !this.refreshToken && this.flow !== "implicit") - throw new Error("Not authenticated"); - if (this.timeSkew == null) - return h(this, M).call(this, "[KEYCLOAK] Unable to determine if token is expired as timeskew is not set"), !0; - if (typeof this.tokenParsed.exp != "number") - return !1; - let t = this.tokenParsed.exp - Math.ceil((/* @__PURE__ */ new Date()).getTime() / 1e3) + this.timeSkew; - if (e) { - if (isNaN(e)) - throw new Error("Invalid minValidity"); - t -= e; - } - return t < 0; - }); - /** - * @param {number} minValidity - * @returns {Promise} - */ - f(this, "updateToken", async (e) => { - var c, a; - if (!this.refreshToken) - throw new Error("Unable to update token, no refresh token available."); - e = e || 5, h(this, y).enable && await d(this, s, X).call(this); - let t = !1; - if (e === -1 ? (t = !0, h(this, M).call(this, "[KEYCLOAK] Refreshing token: forced refresh")) : (!this.tokenParsed || this.isTokenExpired(e)) && (t = !0, h(this, M).call(this, "[KEYCLOAK] Refreshing token: token expired")), !t) - return !1; - const { promise: i, resolve: n, reject: o } = Promise.withResolvers(); - if (h(this, N).push({ resolve: n, reject: o }), h(this, N).length === 1) { - const l = this.endpoints.token(); - let u = (/* @__PURE__ */ new Date()).getTime(); - try { - const w = await at( - l, - this.refreshToken, - /** @type {string} */ - this.clientId - ); - h(this, M).call(this, "[KEYCLOAK] Token refreshed"), u = (u + (/* @__PURE__ */ new Date()).getTime()) / 2, d(this, s, Z).call(this, w.access_token, w.refresh_token, w.id_token, u), (c = this.onAuthRefreshSuccess) == null || c.call(this); - for (let m = h(this, N).pop(); m != null; m = h(this, N).pop()) - m.resolve(!0); - } catch (w) { - h(this, q).call(this, "[KEYCLOAK] Failed to refresh token"), w instanceof Be && w.response.status === 400 && this.clearToken(), (a = this.onAuthRefreshError) == null || a.call(this); - for (let m = h(this, N).pop(); m != null; m = h(this, N).pop()) - m.reject(w); - } - } - return await i; - }); - f(this, "clearToken", () => { - var e; - this.token && (d(this, s, Z).call(this), (e = this.onAuthLogout) == null || e.call(this), this.loginRequired && this.login()); - }); - if (typeof e != "string" && !me(e)) - throw new Error("The 'Keycloak' constructor must be provided with a configuration object, or a URL to a JSON configuration file."); - if (me(e)) { - const t = "oidcProvider" in e ? ["clientId"] : ["url", "realm", "clientId"]; - for (const i of t) - if (!(i in e)) - throw new Error(`The configuration object is missing the required '${i}' property.`); - } - globalThis.isSecureContext || h(this, q).call(this, `[KEYCLOAK] Keycloak JS must be used in a 'secure context' to function properly as it relies on browser APIs that are otherwise not available. -Continuing to run your application insecurely will lead to unexpected behavior and breakage. - -For more information see: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts`), B(this, L, e); - } -} -N = new WeakMap(), _ = new WeakMap(), J = new WeakMap(), Y = new WeakMap(), M = new WeakMap(), q = new WeakMap(), y = new WeakMap(), L = new WeakMap(), s = new WeakSet(), /** - * @param {"default" | "cordova" | "cordova-native"} type - * @returns {KeycloakAdapter} - */ -ee = function(e) { - if (e === "default") - return d(this, s, _e).call(this); - if (e === "cordova") - return h(this, y).enable = !1, d(this, s, Ee).call(this); - if (e === "cordova-native") - return h(this, y).enable = !1, d(this, s, Ae).call(this); - throw new Error("invalid adapter type: " + e); -}, /** - * @returns {KeycloakAdapter} - */ -_e = function() { - const e = (t) => (t == null ? void 0 : t.redirectUri) || this.redirectUri || globalThis.location.href; - return { - login: async (t) => (window.location.assign(await this.createLoginUrl(t)), await new Promise(() => { - })), - logout: async (t) => { - if (((t == null ? void 0 : t.logoutMethod) ?? this.logoutMethod) === "GET") { - window.location.replace(this.createLogoutUrl(t)); - return; - } - const n = document.createElement("form"); - n.setAttribute("method", "POST"), n.setAttribute("action", this.createLogoutUrl(t)), n.style.display = "none"; - const o = { - id_token_hint: this.idToken, - client_id: this.clientId, - post_logout_redirect_uri: e(t) - }; - for (const [c, a] of Object.entries(o)) { - const l = document.createElement("input"); - l.setAttribute("type", "hidden"), l.setAttribute("name", c), l.setAttribute( - "value", - /** @type {string} */ - a - ), n.appendChild(l); - } - document.body.appendChild(n), n.submit(); - }, - register: async (t) => (window.location.assign(await this.createRegisterUrl(t)), await new Promise(() => { - })), - accountManagement: async () => { - const t = this.createAccountUrl(); - if (typeof t < "u") - window.location.href = t; - else - throw new Error("Not supported by the OIDC server"); - return await new Promise(() => { - }); - }, - redirectUri: e - }; -}, /** - * @returns {KeycloakAdapter} - */ -Ee = function() { - const e = (c, a, l) => window.cordova && window.cordova.InAppBrowser ? window.cordova.InAppBrowser.open(c, a, l) : window.open(c, a, l), t = (c) => c && c.cordovaOptions ? Object.keys(c.cordovaOptions).reduce((a, l) => (a[l] = c.cordovaOptions[l], a), {}) : {}, i = (c) => Object.keys(c).reduce((a, l) => (a.push(l + "=" + c[l]), a), []).join(","), n = (c) => { - const a = t(c); - return a.location = "no", c && c.prompt === "none" && (a.hidden = "yes"), i(a); - }, o = () => this.redirectUri || "http://localhost"; - return { - login: async (c) => { - const a = n(c), l = await this.createLoginUrl(c), u = e(l, "_blank", a); - let w = !1, m = !1; - function T() { - m = !0, u.close(); - } - return await new Promise((p, k) => { - u.addEventListener("loadstart", async (g) => { - if (g.url.indexOf(o()) === 0) { - const C = d(this, s, z).call(this, g.url); - w = !0, T(); - try { - await d(this, s, P).call(this, C), p(); - } catch (F) { - k(F); - } - } - }), u.addEventListener("loaderror", async (g) => { - if (!w) - if (g.url.indexOf(o()) === 0) { - const C = d(this, s, z).call(this, g.url); - w = !0, T(); - try { - await d(this, s, P).call(this, C), p(); - } catch (F) { - k(F); - } - } else - k(new Error("Unable to process login.")), T(); - }), u.addEventListener("exit", function(g) { - m || k(new Error("User closed the login window.")); - }); - }); - }, - logout: async (c) => { - const a = this.createLogoutUrl(c), l = e(a, "_blank", "location=no,hidden=yes,clearcache=yes"); - let u = !1; - l.addEventListener("loadstart", (w) => { - w.url.indexOf(o()) === 0 && l.close(); - }), l.addEventListener("loaderror", (w) => { - w.url.indexOf(o()) === 0 || (u = !0), l.close(); - }), await new Promise((w, m) => { - l.addEventListener("exit", () => { - u ? m(new Error("User closed the login window.")) : (this.clearToken(), w()); - }); - }); - }, - register: async (c) => { - const a = await this.createRegisterUrl(), l = n(c), u = e(a, "_blank", l); - await new Promise((m, T) => { - u.addEventListener("loadstart", async (p) => { - if (p.url.indexOf(o()) === 0) { - u.close(); - const k = d(this, s, z).call(this, p.url); - try { - await d(this, s, P).call(this, k), m(); - } catch (g) { - T(g); - } - } - }); - }); - }, - accountManagement: async () => { - const c = this.createAccountUrl(); - if (typeof c < "u") { - const a = e(c, "_blank", "location=no"); - a.addEventListener("loadstart", function(l) { - l.url.indexOf(o()) === 0 && a.close(); - }); - } else - throw new Error("Not supported by the OIDC server"); - }, - redirectUri: () => o() - }; -}, /** - * @returns {KeycloakAdapter} - */ -Ae = function() { - return { - login: async (e) => { - const t = await this.createLoginUrl(e); - await new Promise((i, n) => { - universalLinks.subscribe("keycloak", async (o) => { - universalLinks.unsubscribe("keycloak"), window.cordova.plugins.browsertab.close(); - const c = d(this, s, z).call(this, o.url); - try { - await d(this, s, P).call(this, c), i(); - } catch (a) { - n(a); - } - }), window.cordova.plugins.browsertab.openUrl(t); - }); - }, - logout: async (e) => { - const t = this.createLogoutUrl(e); - await new Promise((i) => { - universalLinks.subscribe("keycloak", () => { - universalLinks.unsubscribe("keycloak"), window.cordova.plugins.browsertab.close(), this.clearToken(), i(); - }), window.cordova.plugins.browsertab.openUrl(t); - }); - }, - register: async (e) => { - const t = await this.createRegisterUrl(e); - await new Promise((i, n) => { - universalLinks.subscribe("keycloak", async (o) => { - universalLinks.unsubscribe("keycloak"), window.cordova.plugins.browsertab.close(); - const c = d(this, s, z).call(this, o.url); - try { - await d(this, s, P).call(this, c), i(); - } catch (a) { - n(a); - } - }), window.cordova.plugins.browsertab.openUrl(t); - }); - }, - accountManagement: async () => { - const e = this.createAccountUrl(); - if (typeof e < "u") - window.cordova.plugins.browsertab.openUrl(e); - else - throw new Error("Not supported by the OIDC server"); - }, - redirectUri: (e) => e && e.redirectUri ? e.redirectUri : this.redirectUri ? this.redirectUri : "http://localhost" - }; -}, Ie = async function() { - if (typeof h(this, L) == "string") { - const e = await ot(h(this, L)); - this.authServerUrl = e["auth-server-url"], this.realm = e.realm, this.clientId = e.resource, d(this, s, ae).call(this); - } else - this.clientId = h(this, L).clientId, "oidcProvider" in h(this, L) ? await d(this, s, xe).call(this, h(this, L).oidcProvider) : (this.authServerUrl = h(this, L).url, this.realm = h(this, L).realm, d(this, s, ae).call(this)); -}, /** - * @returns {void} - */ -ae = function() { - this.endpoints = { - authorize: () => d(this, s, R).call(this) + "/protocol/openid-connect/auth", - token: () => d(this, s, R).call(this) + "/protocol/openid-connect/token", - logout: () => d(this, s, R).call(this) + "/protocol/openid-connect/logout", - checkSessionIframe: () => d(this, s, R).call(this) + "/protocol/openid-connect/login-status-iframe.html", - thirdPartyCookiesIframe: () => d(this, s, R).call(this) + "/protocol/openid-connect/3p-cookies/step1.html", - register: () => d(this, s, R).call(this) + "/protocol/openid-connect/registrations", - userinfo: () => d(this, s, R).call(this) + "/protocol/openid-connect/userinfo" - }; -}, xe = async function(e) { - if (typeof e == "string") { - const t = `${Ue(e)}/.well-known/openid-configuration`, i = await nt(t); - d(this, s, ce).call(this, i); - } else - d(this, s, ce).call(this, e); -}, /** - * @param {OpenIdProviderMetadata} config - * @returns {void} - */ -ce = function(e) { - this.endpoints = { - authorize() { - return e.authorization_endpoint; - }, - token() { - return e.token_endpoint; - }, - logout() { - if (!e.end_session_endpoint) - throw new Error("Not supported by the OIDC server"); - return e.end_session_endpoint; - }, - checkSessionIframe() { - if (!e.check_session_iframe) - throw new Error("Not supported by the OIDC server"); - return e.check_session_iframe; - }, - register() { - throw new Error('Redirection to "Register user" page not supported in standard OIDC mode'); - }, - userinfo() { - if (!e.userinfo_endpoint) - throw new Error("Not supported by the OIDC server"); - return e.userinfo_endpoint; - } - }; -}, Le = async function() { - if (!h(this, y).enable && !this.silentCheckSsoRedirectUri || typeof this.endpoints.thirdPartyCookiesIframe != "function") - return; - const e = document.createElement("iframe"); - e.setAttribute("src", this.endpoints.thirdPartyCookiesIframe()), e.setAttribute("sandbox", "allow-storage-access-by-user-activation allow-scripts allow-same-origin"), e.setAttribute("title", "keycloak-3p-check-iframe"), e.style.display = "none", document.body.appendChild(e); - const t = new Promise((i) => { - const n = (o) => { - e.contentWindow === o.source && (o.data !== "supported" && o.data !== "unsupported" || (o.data === "unsupported" && (h(this, q).call(this, `[KEYCLOAK] Your browser is blocking access to 3rd-party cookies, this means: - - - It is not possible to retrieve tokens without redirecting to the Keycloak server (a.k.a. no support for silent authentication). - - It is not possible to automatically detect changes to the session status (such as the user logging out in another tab). - -For more information see: https://www.keycloak.org/securing-apps/javascript-adapter#_modern_browsers`), h(this, y).enable = !1, this.silentCheckSsoFallback && (this.silentCheckSsoRedirectUri = void 0)), document.body.removeChild(e), window.removeEventListener("message", n), i())); - }; - window.addEventListener("message", n, !1); - }); - return await Ge(t, this.messageReceiveTimeout, "Timeout when waiting for 3rd party check iframe message."); -}, Re = async function(e) { - var o, c, a; - const t = d(this, s, z).call(this, window.location.href); - if (t != null && t.newUrl && window.history.replaceState(window.history.state, "", t.newUrl), t && t.valid) { - await d(this, s, te).call(this), await d(this, s, P).call(this, t); - return; - } - const i = async (l) => { - const u = {}; - l || (u.prompt = "none"), e.locale && (u.locale = e.locale), await this.login(u); - }, n = async () => { - switch (e.onLoad) { - case "check-sso": - h(this, y).enable ? (await d(this, s, te).call(this), await d(this, s, X).call(this) || (this.silentCheckSsoRedirectUri ? await d(this, s, le).call(this) : await i(!1))) : this.silentCheckSsoRedirectUri ? await d(this, s, le).call(this) : await i(!1); - break; - case "login-required": - await i(!0); - break; - default: - throw new Error("Invalid value for onLoad"); - } - }; - if (e.token && e.refreshToken) - if (d(this, s, Z).call(this, e.token, e.refreshToken, e.idToken), h(this, y).enable) - await d(this, s, te).call(this), await d(this, s, X).call(this) && ((o = this.onAuthSuccess) == null || o.call(this), d(this, s, re).call(this)); - else - try { - await this.updateToken(-1), (c = this.onAuthSuccess) == null || c.call(this); - } catch (l) { - if ((a = this.onAuthError) == null || a.call(this), e.onLoad) - await n(); - else - throw l; - } - else e.onLoad && await n(); -}, te = async function() { - if (!h(this, y).enable || h(this, y).iframe) - return; - const e = document.createElement("iframe"); - h(this, y).iframe = e, e.setAttribute("src", this.endpoints.checkSessionIframe()), e.setAttribute("sandbox", "allow-storage-access-by-user-activation allow-scripts allow-same-origin"), e.setAttribute("title", "keycloak-session-iframe"), e.style.display = "none", document.body.appendChild(e); - const t = (n) => { - var c; - if (n.origin !== h(this, y).iframeOrigin || ((c = h(this, y).iframe) == null ? void 0 : c.contentWindow) !== n.source || !(n.data === "unchanged" || n.data === "changed" || n.data === "error")) - return; - n.data !== "unchanged" && this.clearToken(); - const o = h(this, y).callbackList; - h(this, y).callbackList = []; - for (const a of o.reverse()) - n.data === "error" ? a(new Error("Error while checking login iframe")) : a(null, n.data === "unchanged"); - }; - window.addEventListener("message", t, !1), await new Promise((n) => { - e.addEventListener("load", () => { - const o = this.endpoints.authorize(); - o.startsWith("/") ? h(this, y).iframeOrigin = globalThis.location.origin : h(this, y).iframeOrigin = new URL(o).origin, n(); - }); - }); -}, X = async function() { - if (!h(this, y).iframe || !h(this, y).iframeOrigin) - return; - const e = `${this.clientId} ${this.sessionId ? this.sessionId : ""}`, t = h(this, y).iframeOrigin; - return await new Promise((n, o) => { - var a, l; - const c = (u, w) => u ? o(u) : n( - /** @type {boolean} */ - w - ); - h(this, y).callbackList.push(c), h(this, y).callbackList.length === 1 && ((l = (a = h(this, y).iframe) == null ? void 0 : a.contentWindow) == null || l.postMessage(e, t)); - }); -}, le = async function() { - const e = document.createElement("iframe"), t = await this.createLoginUrl({ prompt: "none", redirectUri: this.silentCheckSsoRedirectUri }); - return e.setAttribute("src", t), e.setAttribute("sandbox", "allow-storage-access-by-user-activation allow-scripts allow-same-origin"), e.setAttribute("title", "keycloak-silent-check-sso"), e.style.display = "none", document.body.appendChild(e), await new Promise((i, n) => { - const o = async (c) => { - if (c.origin !== window.location.origin || e.contentWindow !== c.source) - return; - const a = d(this, s, z).call(this, c.data); - try { - await d(this, s, P).call(this, a), i(); - } catch (l) { - n(l); - } - document.body.removeChild(e), window.removeEventListener("message", o); - }; - window.addEventListener("message", o); - }); -}, /** - * @param {string} url - */ -z = function(e) { - const t = d(this, s, Me).call(this, e); - if (!t) - return; - const i = h(this, Y).get(t.state); - return i && (t.valid = !0, t.redirectUri = i.redirectUri, t.storedNonce = i.nonce, t.prompt = i.prompt, t.pkceCodeVerifier = i.pkceCodeVerifier, t.loginOptions = i.loginOptions), t; -}, /** - * @param {string} urlString - */ -Me = function(e) { - let t = []; - switch (this.flow) { - case "standard": - t = ["code", "state", "session_state", "kc_action_status", "kc_action", "iss"]; - break; - case "implicit": - t = ["access_token", "token_type", "id_token", "state", "session_state", "expires_in", "kc_action_status", "kc_action", "iss"]; - break; - case "hybrid": - t = ["access_token", "token_type", "id_token", "code", "state", "session_state", "expires_in", "kc_action_status", "kc_action", "iss"]; - break; - } - t.push("error"), t.push("error_description"), t.push("error_uri"); - const i = new URL(e); - let n = "", o; - if (this.responseMode === "query" && i.searchParams.size > 0 ? (o = d(this, s, de).call(this, i.search, t), i.search = o.paramsString, n = i.toString()) : this.responseMode === "fragment" && i.hash.length > 0 && (o = d(this, s, de).call(this, i.hash.substring(1), t), i.hash = o.paramsString, n = i.toString()), o != null && o.oauthParams) { - if (this.flow === "standard" || this.flow === "hybrid") { - if ((o.oauthParams.code || o.oauthParams.error) && o.oauthParams.state) - return o.oauthParams.newUrl = n, o.oauthParams; - } else if (this.flow === "implicit" && (o.oauthParams.access_token || o.oauthParams.error) && o.oauthParams.state) - return o.oauthParams.newUrl = n, o.oauthParams; - } -}, /** - * @typedef {Object} ParsedCallbackParams - * @property {string} paramsString - * @property {Record} oauthParams - */ -/** - * @param {string} paramsString - * @param {string[]} supportedParams - * @returns {ParsedCallbackParams} - */ -de = function(e, t) { - const i = e.split("&"), n = {}; - let o = ""; - for (const c of i.reverse()) { - const a = new URLSearchParams(c).entries().next().value; - if (!a) { - o = "&" + o; - continue; - } - const [l, u] = a; - t.includes(l) && !(l in n) ? n[l] = u : o = o.length === 0 ? c : c + "&" + o; - } - return { - paramsString: o, - oauthParams: n - }; -}, P = async function(e) { - var a, l, u, w; - const { code: t, error: i, prompt: n } = e; - let o = (/* @__PURE__ */ new Date()).getTime(); - const c = (m, T, p) => { - if (o = (o + (/* @__PURE__ */ new Date()).getTime()) / 2, d(this, s, Z).call(this, m, T, p, o), h(this, J) && this.idTokenParsed && this.idTokenParsed.nonce !== e.storedNonce) - throw h(this, M).call(this, "[KEYCLOAK] Invalid nonce, clearing token"), this.clearToken(), new Error("Invalid nonce."); - }; - if (e.kc_action_status && this.onActionUpdate && this.onActionUpdate(e.kc_action_status, e.kc_action), i) { - if (n !== "none") - if (e.error_description && e.error_description === "authentication_expired") - await this.login(e.loginOptions); - else { - const m = { error: i, error_description: e.error_description }; - throw (a = this.onAuthError) == null || a.call(this, m), m; - } - return; - } else this.flow !== "standard" && (e.access_token || e.id_token) && (c(e.access_token, void 0, e.id_token), (l = this.onAuthSuccess) == null || l.call(this)); - if (this.flow !== "implicit" && t) - try { - const m = await st( - this.endpoints.token(), - t, - /** @type {string} */ - this.clientId, - e.redirectUri, - e.pkceCodeVerifier - ); - c(m.access_token, m.refresh_token, m.id_token), this.flow === "standard" && ((u = this.onAuthSuccess) == null || u.call(this)), d(this, s, re).call(this); - } catch (m) { - throw (w = this.onAuthError) == null || w.call(this), m; - } -}, re = async function() { - h(this, y).enable && this.token && (await lt(h(this, y).interval * 1e3), await d(this, s, X).call(this) && await d(this, s, re).call(this)); -}, /** - * @param {string} [token] - * @param {string} [refreshToken] - * @param {string} [idToken] - * @param {number} [timeLocal] - */ -Z = function(e, t, i, n) { - if (this.tokenTimeoutHandle && (clearTimeout(this.tokenTimeoutHandle), this.tokenTimeoutHandle = void 0), t ? (this.refreshToken = t, this.refreshTokenParsed = ne(t)) : (delete this.refreshToken, delete this.refreshTokenParsed), i ? (this.idToken = i, this.idTokenParsed = ne(i)) : (delete this.idToken, delete this.idTokenParsed), e) { - if (this.token = e, this.tokenParsed = ne(e), this.sessionId = this.tokenParsed.sid, this.authenticated = !0, this.subject = this.tokenParsed.sub, this.realmAccess = this.tokenParsed.realm_access, this.resourceAccess = this.tokenParsed.resource_access, n && (this.timeSkew = Math.floor(n / 1e3) - this.tokenParsed.iat), this.timeSkew !== null && (h(this, M).call(this, "[KEYCLOAK] Estimated time difference between browser and server is " + this.timeSkew + " seconds"), this.onTokenExpired)) { - const o = (this.tokenParsed.exp - (/* @__PURE__ */ new Date()).getTime() / 1e3 + this.timeSkew) * 1e3; - h(this, M).call(this, "[KEYCLOAK] Token expires in " + Math.round(o / 1e3) + " s"), o <= 0 ? this.onTokenExpired() : this.tokenTimeoutHandle = window.setTimeout(this.onTokenExpired, o); - } - } else - delete this.token, delete this.tokenParsed, delete this.subject, delete this.realmAccess, delete this.resourceAccess, this.authenticated = !1; -}, /** - * @returns {string=} - */ -R = function() { - if (!(typeof this.authServerUrl > "u")) - return `${Ue(this.authServerUrl)}/realms/${encodeURIComponent( - /** @type {string} */ - this.realm - )}`; -}, /** - * @param {Function} fn - * @returns {(message: string) => void} - */ -ue = function(e) { - return (t) => { - this.enableLogging && e.call(console, t); - }; }; -function Ce() { - if (typeof crypto > "u" || typeof crypto.randomUUID > "u") - throw new Error("Web Crypto API is not available."); - return crypto.randomUUID(); -} -function He(r) { - return JSON.stringify({ - id_token: { - acr: r - } - }); -} -function $e(r) { - return Ye(r, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); -} -async function Je(r, e) { - if (r !== "S256") - throw new TypeError(`Invalid value for 'pkceMethod', expected 'S256' but got '${r}'.`); - const t = new Uint8Array(await tt(e)); - return et(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); -} -function Ye(r, e) { - const t = qe(r), i = new Array(r); - for (let n = 0; n < r; n++) - i[n] = e.charCodeAt(t[n] % e.length); - return String.fromCharCode.apply(null, i); -} -function qe(r) { - if (typeof crypto > "u" || typeof crypto.getRandomValues > "u") - throw new Error("Web Crypto API is not available."); - return crypto.getRandomValues(new Uint8Array(r)); -} -function Ge(r, e, t) { - let i; - const n = new Promise(function(o, c) { - i = window.setTimeout(function() { - c(new Error(t)); - }, e); - }); - return Promise.race([r, n]).finally(function() { - clearTimeout(i); - }); -} -function Qe() { - try { - return new Xe(); - } catch { - return new Ze(); - } -} -const $ = "kc-callback-"; -var x, he, De, fe, Ne; -class Xe { - constructor() { - A(this, x); - globalThis.localStorage.setItem("kc-test", "test"), globalThis.localStorage.removeItem("kc-test"); - } - /** - * @param {string} [state] - * @returns {CallbackState | null} - */ - get(e) { - if (!e) - return null; - d(this, x, he).call(this); - const t = $ + e, i = globalThis.localStorage.getItem(t); - return i ? (globalThis.localStorage.removeItem(t), JSON.parse(i)) : null; - } - /** - * @param {CallbackState} state - */ - add(e) { - d(this, x, he).call(this); - const t = $ + e.state, i = JSON.stringify({ - ...e, - // Set the expiry time to 1 hour from now. - expires: Date.now() + 60 * 60 * 1e3 - }); - try { - globalThis.localStorage.setItem(t, i); - } catch { - d(this, x, De).call(this), globalThis.localStorage.setItem(t, i); - } - } -} -x = new WeakSet(), /** - * Clears all values from local storage that are no longer valid. - */ -he = function() { - const e = Date.now(); - for (const [t, i] of d(this, x, fe).call(this)) { - const n = d(this, x, Ne).call(this, i); - (n === null || n < e) && globalThis.localStorage.removeItem(t); - } -}, /** - * Clears all known values from local storage. - */ -De = function() { - for (const [e] of d(this, x, fe).call(this)) - globalThis.localStorage.removeItem(e); -}, /** - * Gets all entries stored in local storage that are known to be managed by this class. - * @returns {[string, string][]} An array of key-value pairs. - */ -fe = function() { - return Object.entries(globalThis.localStorage).filter(([e]) => e.startsWith($)); -}, /** - * Parses the expiry time from a value stored in local storage. - * @param {string} value - * @returns {number | null} The expiry time in milliseconds, or `null` if the value is malformed. - */ -Ne = function(e) { - let t; - try { - t = JSON.parse(e); - } catch { - return null; - } - return me(t) && "expires" in t && typeof t.expires == "number" ? t.expires : null; -}; -var D, Ke, pe, we; -class Ze { - constructor() { - A(this, D); - } - /** - * @param {string} [state] - * @returns {CallbackState | null} - */ - get(e) { - if (!e) - return null; - const t = d(this, D, Ke).call(this, $ + e); - return d(this, D, pe).call(this, $ + e, "", d(this, D, we).call(this, -100)), t ? JSON.parse(t) : null; - } - /** - * @param {CallbackState} state - */ - add(e) { - d(this, D, pe).call(this, $ + e.state, JSON.stringify(e), d(this, D, we).call(this, 60)); - } -} -D = new WeakSet(), /** - * @param {string} key - * @returns - */ -Ke = function(e) { - const t = e + "=", i = document.cookie.split(";"); - for (let n = 0; n < i.length; n++) { - let o = i[n]; - for (; o.charAt(0) === " "; ) - o = o.substring(1); - if (o.indexOf(t) === 0) - return o.substring(t.length, o.length); - } - return ""; -}, /** - * @param {string} key - * @param {string} value - * @param {Date} expirationDate - */ -pe = function(e, t, i) { - const n = e + "=" + t + "; expires=" + i.toUTCString() + "; "; - document.cookie = n; -}, /** - * @param {number} minutes - * @returns {Date} - */ -we = function(e) { - const t = /* @__PURE__ */ new Date(); - return t.setTime(t.getTime() + e * 60 * 1e3), t; -}; -function et(r) { - const e = String.fromCodePoint(...r); - return btoa(e); -} -async function tt(r) { - const t = new TextEncoder().encode(r); - if (typeof crypto > "u" || typeof crypto.subtle > "u") - throw new Error("Web Crypto API is not available."); - return await crypto.subtle.digest("SHA-256", t); -} -function ne(r) { - const [, e] = r.split("."); - if (typeof e != "string") - throw new Error("Unable to decode token, payload not found."); - let t; - try { - t = rt(e); - } catch (i) { - throw new Error("Unable to decode token, payload is not a valid Base64URL value.", { cause: i }); - } - try { - return JSON.parse(t); - } catch (i) { - throw new Error("Unable to decode token, payload is not a valid JSON value.", { cause: i }); - } -} -function rt(r) { - let e = r.replaceAll("-", "+").replaceAll("_", "/"); - switch (e.length % 4) { - case 0: - break; - case 2: - e += "=="; - break; - case 3: - e += "="; - break; - default: - throw new Error("Input is not of the correct length."); - } - try { - return it(e); - } catch { - return atob(e); - } -} -function it(r) { - return decodeURIComponent(atob(r).replace(/(.)/g, (e, t) => { - let i = t.charCodeAt(0).toString(16).toUpperCase(); - return i.length < 2 && (i = "0" + i), "%" + i; - })); -} -function me(r) { - return typeof r == "object" && r !== null; -} -async function ot(r) { - return await Q(r); -} -async function nt(r) { - return await Q(r); -} -async function st(r, e, t, i, n) { - const o = new URLSearchParams([ - ["code", e], - ["grant_type", "authorization_code"], - ["client_id", t], - ["redirect_uri", i] - ]); - return n && o.append("code_verifier", n), await Q(r, { - method: "POST", - credentials: "include", - body: o - }); -} -async function at(r, e, t) { - const i = new URLSearchParams([ - ["grant_type", "refresh_token"], - ["refresh_token", e], - ["client_id", t] - ]); - return await Q(r, { - method: "POST", - credentials: "include", - body: i - }); -} -async function Q(r, e = {}) { - const t = new Headers(e.headers); - return t.set("Accept", Ve), await (await ct(r, { - ...e, - headers: t - })).json(); -} -async function ct(r, e) { - const t = await fetch(r, e); - if (!t.ok) - throw new Be("Server responded with an invalid status.", { response: t }); - return t; -} -function Te(r) { - if (!r) - throw new Error("Unable to build authorization header, token is not set, make sure the user is authenticated."); - return ["Authorization", `bearer ${r}`]; -} -function Ue(r) { - return r.endsWith("/") ? r.slice(0, -1) : r; -} -class Be extends Error { - /** - * @param {string} message - * @param {NetworkErrorOptions} options - */ - constructor(t, i) { - super(t, i); - /** @type {Response} */ - f(this, "response"); - this.response = i.response; - } -} -const lt = (r) => new Promise((e) => setTimeout(e, r)); -let b = null, se = null, ge = !1; -const dt = We(null), ut = ({ url: r, realm: e, clientId: t }) => (b || (b = new Fe({ - url: r, - realm: e, - clientId: t -})), b), ht = ({ config: r, initOptions: e = {} }) => { - const t = ut(r); - return se || (se = t.init({ +let u = null, z = null, K = !1; +const $ = W(null), H = ({ url: e, realm: n, clientId: r }) => (u || (u = new J({ + url: e, + realm: n, + clientId: r +})), u), Y = ({ config: e, initOptions: n = {} }) => { + const r = H(e); + return z || (z = r.init({ onLoad: "login-required", checkLoginIframe: !1, flow: "implicit", - ...e - }).then((i) => (ge = !0, i)).catch((i) => { - throw ge = !0, i; - })), se; -}, St = () => ge, Ct = () => !!(b != null && b.authenticated), Tt = () => (b == null ? void 0 : b.token) ?? null, Ut = async (r = 30) => b ? b.updateToken(r) : !1, _t = (r) => b == null ? void 0 : b.logout(r), Et = (r) => b == null ? void 0 : b.login(r), At = ({ - children: r, - config: e, - initOptions: t, - loading: i = null + ...n + }).then((s) => (K = !0, s)).catch((s) => { + throw K = !0, s; + })), z; +}, he = () => K, U = () => !!(u != null && u.authenticated), X = () => (u == null ? void 0 : u.token) ?? null, be = async (e = 30) => u ? u.updateToken(e) : !1, Z = (e) => u == null ? void 0 : u.logout(e), we = (e) => u == null ? void 0 : u.login(e), xe = ({ + children: e, + config: n, + initOptions: r, + loading: s = null }) => { - const [n, o] = I(!1), [c, a] = I(!1), [l, u] = I(null), w = ie(!1), m = H(() => { - b && (a(!!b.authenticated), u(b.token ?? null)); + const [l, i] = B(!1), [w, p] = B(!1), [m, o] = B(null), h = T(!1), x = D(() => { + u && (p(!!u.authenticated), o(u.token ?? null)); }, []); - O(() => (w.current = !0, ht({ config: e, initOptions: t }).then((C) => { - w.current && (a(!!C), u((b == null ? void 0 : b.token) ?? null), o(!0)); + g(() => (h.current = !0, Y({ config: n, initOptions: r }).then((b) => { + h.current && (p(!!b), o((u == null ? void 0 : u.token) ?? null), i(!0)); }).catch(() => { - w.current && (a(!1), u(null), o(!0)); + h.current && (p(!1), o(null), i(!0)); }), () => { - w.current = !1; - }), [e, t]); - const T = H(() => b == null ? void 0 : b.login(), []), p = H((C) => b == null ? void 0 : b.logout(C), []), k = H( - async (C = 30) => { - if (!b) return !1; - const F = await b.updateToken(C); - return m(), F; + h.current = !1; + }), [n, r]); + const v = D(() => u == null ? void 0 : u.login(), []), t = D((b) => u == null ? void 0 : u.logout(b), []), a = D( + async (b = 30) => { + if (!u) return !1; + const E = await u.updateToken(b); + return x(), E; }, - [m] - ), g = Oe( + [x] + ), f = I( () => ({ - initialized: n, - authenticated: c, - token: l, - login: T, - logout: p, - updateToken: k + initialized: l, + authenticated: w, + token: m, + login: v, + logout: t, + updateToken: a }), - [n, c, l, T, p, k] + [l, w, m, v, t, a] ); - return n ? /* @__PURE__ */ S(dt.Provider, { value: g, children: r }) : i; + return l ? /* @__PURE__ */ c($.Provider, { value: f, children: e }) : s; +}, R = W({ + user: null, + can: () => !1, + cannot: () => !0, + permissions: void 0, + isLoading: !1, + error: null +}), ye = ({ + user: e = null, + children: n, + clientId: r, + permissionsUrl: s, + storageKey: l = "permissions", + loading: i = /* @__PURE__ */ c("div", { children: "Loading permissions..." }), + errorFallback: w = /* @__PURE__ */ c("div", { children: "Error loading permissions" }) +}) => { + const [p, m] = B( + () => P(l) + ), { data: o, isLoading: h, error: x } = oe({ + clientId: r, + permissionsUrl: s + }), v = o ?? p; + g(() => { + o && (ee(l, o), m(o)); + }, [o, l]); + const t = I( + () => ne(v), + [v] + ), a = D( + (E = "") => te(t, E), + [t] + ), f = D((E = "") => !a(E), [a]), b = I( + () => ({ + user: e, + can: a, + cannot: f, + permissions: v, + isLoading: h, + error: x + }), + [e, a, f, v, h, x] + ); + return x && !v ? w : h && !v ? i : /* @__PURE__ */ c(R.Provider, { value: b, children: n }); +}, P = (e = "permissions") => { + if (!(typeof window > "u")) + try { + const n = localStorage.getItem(e); + return n ? JSON.parse(n) : void 0; + } catch { + return; + } +}, ee = (e = "permissions", n) => { + if (!(typeof window > "u")) + try { + localStorage.setItem(e, JSON.stringify(n)); + } catch { + } +}, ve = (e = "permissions") => { + if (!(typeof window > "u")) + try { + localStorage.removeItem(e); + } catch { + } +}, ne = (e) => e ? new Set(e.map((n) => n.name)) : /* @__PURE__ */ new Set(), te = (e, n = "") => { + if (!n) return !1; + if (e.has(n) || [...e].some((s) => s.startsWith(n))) return !0; + let r = n; + for (; r.includes(":"); ) + if (r = r.slice(0, r.lastIndexOf(":")), e.has(`${r}:*`)) return !0; + return !1; +}, O = { + all: ["permissions"], + list: (e) => [...O.all, e] +}, ie = async ({ permissionsUrl: e, clientId: n }) => { + const r = X(), s = new URL(e); + s.searchParams.set("client_id", n); + const l = await fetch(s.toString(), { + method: "GET", + headers: { + ...r ? { Authorization: `Bearer ${r}` } : {} + } + }); + if (!l.ok) + throw new Error("Failed to load permissions"); + return l.json(); +}, L = () => { + const e = G(R); + if (!e) + throw new Error("usePermission must be used within PermissionProvider"); + return e; +}, oe = ({ clientId: e, permissionsUrl: n }) => Q({ + queryKey: O.list(e), + queryFn: () => ie({ clientId: e, permissionsUrl: n }), + enabled: !!e && !!n && U(), + staleTime: 1 / 0, + refetchOnWindowFocus: !1, + refetchOnReconnect: !1 +}), Ce = ({ + perm: e, + children: n, + fallback: r = /* @__PURE__ */ c("div", { children: "You are not allowed to do this" }) +}) => { + const { can: s } = L(); + return s(e) ? n : r; +}, Be = ({ children: e }) => { + const { can: n, permissions: r } = L(); + return r && n("admin") ? e : null; +}, ge = ({ children: e }) => { + const { can: n, isLoading: r } = L(); + return g(() => { + r || n("panel_access") || Z(); + }, [r, n]), e; }; export { - At as KeycloakProvider, - bt as LightPagination, - yt as Pagination, - vt as SimplePagination, - Tt as getKeycloakToken, - ht as initKeycloak, - Ct as isKeycloakAuthenticated, - St as isKeycloakInitialized, - Et as loginKeycloak, - _t as logoutKeycloak, - V as toEnDigits, - v as toFaDigits, - kt as toFaNumber, - Ut as updateKeycloakToken + Be as AdminGuard, + Ce as Can, + xe as KeycloakProvider, + de as LightPagination, + pe as Pagination, + ge as PanelAccessGuard, + ye as PermissionProvider, + me as SimplePagination, + te as checkPermission, + ve as clearCachedPermissions, + ne as createPermissionSet, + P as getCachedPermissions, + X as getKeycloakToken, + ie as getPermissions, + Y as initKeycloak, + U as isKeycloakAuthenticated, + he as isKeycloakInitialized, + we as loginKeycloak, + Z as logoutKeycloak, + O as permissionKeys, + ee as setCachedPermissions, + N as toEnDigits, + d as toFaDigits, + fe as toFaNumber, + be as updateKeycloakToken, + L as usePermission, + oe as usePermissionsQuery }; diff --git a/package.json b/package.json index 2e100de..71e8bde 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ }, "peerDependencies": { "@chakra-ui/react": "^3.0.0", + "@tanstack/react-query": "^5.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "react-icons": "^5.0.0" }, "devDependencies": { + "@tanstack/react-query": "^5.0.0", "@vitejs/plugin-react": "4.3.4", "vite": "5.4.19" }, diff --git a/src/core/permission.jsx b/src/core/permission.jsx new file mode 100644 index 0000000..c66b185 --- /dev/null +++ b/src/core/permission.jsx @@ -0,0 +1,240 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + getKeycloakToken, + isKeycloakAuthenticated, + logoutKeycloak, +} from "./keycloak.jsx"; + +// contexts + +const PermissionContext = createContext({ + user: null, + can: () => false, + cannot: () => true, + permissions: undefined, + isLoading: false, + error: null, +}); + +// providers + +export const PermissionProvider = ({ + user = null, + children, + clientId, + permissionsUrl, + storageKey = "permissions", + loading =
Loading permissions...
, + errorFallback =
Error loading permissions
, +}) => { + const [cachedPermissions, setCachedPermissionsState] = useState(() => + getCachedPermissions(storageKey), + ); + + const { data, isLoading, error } = usePermissionsQuery({ + clientId, + permissionsUrl, + }); + + const permissions = data ?? cachedPermissions; + + useEffect(() => { + if (data) { + setCachedPermissions(storageKey, data); + setCachedPermissionsState(data); + } + }, [data, storageKey]); + + const permissionSet = useMemo( + () => createPermissionSet(permissions), + [permissions], + ); + + const can = useCallback( + (perm = "") => checkPermission(permissionSet, perm), + [permissionSet], + ); + + const cannot = useCallback((perm = "") => !can(perm), [can]); + + const value = useMemo( + () => ({ + user, + can, + cannot, + permissions, + isLoading, + error, + }), + [user, can, cannot, permissions, isLoading, error], + ); + + if (error && !permissions) { + return errorFallback; + } + + if (isLoading && !permissions) { + return loading; + } + + return ( + + {children} + + ); +}; + +// storages + +export const getCachedPermissions = (storageKey = "permissions") => { + if (typeof window === "undefined") return undefined; + + try { + const localPermissions = localStorage.getItem(storageKey); + return localPermissions ? JSON.parse(localPermissions) : undefined; + } catch { + return undefined; + } +}; + +export const setCachedPermissions = ( + storageKey = "permissions", + permissions, +) => { + if (typeof window === "undefined") return; + + try { + localStorage.setItem(storageKey, JSON.stringify(permissions)); + } catch {} +}; + +export const clearCachedPermissions = (storageKey = "permissions") => { + if (typeof window === "undefined") return; + + try { + localStorage.removeItem(storageKey); + } catch {} +}; + +// utils + +export const createPermissionSet = (permissions) => { + if (!permissions) return new Set(); + + return new Set(permissions.map((permission) => permission.name)); +}; + +export const checkPermission = (permissionSet, perm = "") => { + if (!perm) return false; + + if (permissionSet.has(perm)) return true; + + if ([...permissionSet].some((x) => x.startsWith(perm))) return true; + + let current = perm; + + while (current.includes(":")) { + current = current.slice(0, current.lastIndexOf(":")); + + if (permissionSet.has(`${current}:*`)) return true; + } + + return false; +}; + +// keys + +export const permissionKeys = { + all: ["permissions"], + list: (clientId) => [...permissionKeys.all, clientId], +}; + +// apis + +export const getPermissions = async ({ permissionsUrl, clientId }) => { + const token = getKeycloakToken(); + + const url = new URL(permissionsUrl); + url.searchParams.set("client_id", clientId); + + const response = await fetch(url.toString(), { + method: "GET", + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + + if (!response.ok) { + throw new Error("Failed to load permissions"); + } + + return response.json(); +}; + +// hooks + +export const usePermission = () => { + const context = useContext(PermissionContext); + + if (!context) { + throw new Error("usePermission must be used within PermissionProvider"); + } + + return context; +}; + +export const usePermissionsQuery = ({ clientId, permissionsUrl }) => { + return useQuery({ + queryKey: permissionKeys.list(clientId), + queryFn: () => getPermissions({ clientId, permissionsUrl }), + enabled: + Boolean(clientId) && + Boolean(permissionsUrl) && + isKeycloakAuthenticated(), + staleTime: Infinity, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }); +}; + +// components + +export const Can = ({ + perm, + children, + fallback =
You are not allowed to do this
, +}) => { + const { can } = usePermission(); + + return can(perm) ? children : fallback; +}; + +export const AdminGuard = ({ children }) => { + const { can, permissions } = usePermission(); + + if (!permissions) return null; + + return can("admin") ? children : null; +}; + +export const PanelAccessGuard = ({ children }) => { + const { can, isLoading } = usePermission(); + + useEffect(() => { + if (isLoading) return; + + if (!can("panel_access")) { + logoutKeycloak(); + } + }, [isLoading, can]); + + return children; +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 28cb1ae..0193af2 100644 --- a/src/index.js +++ b/src/index.js @@ -14,3 +14,19 @@ export { logoutKeycloak, loginKeycloak, } from "./core/keycloak.jsx"; + +export { + PermissionProvider, + usePermission, + usePermissionsQuery, + Can, + AdminGuard, + PanelAccessGuard, + getCachedPermissions, + setCachedPermissions, + clearCachedPermissions, + createPermissionSet, + checkPermission, + permissionKeys, + getPermissions, +} from "./core/permission.jsx"; diff --git a/vite.config.js b/vite.config.js index e15713a..69f3bb9 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,6 +16,8 @@ export default defineConfig({ "react-dom", "react/jsx-runtime", "@chakra-ui/react", + "keycloak-js", + "@tanstack/react-query", "react-icons", "react-icons/fa", "react-icons/fa6",