diff --git a/.env.docker b/.env.docker index a918719..e5d0d3d 100644 --- a/.env.docker +++ b/.env.docker @@ -1,3 +1,4 @@ FRIGATE_PROXY=http://localhost:4000 OPENID_SERVER=https://your.server.com:443/realms/your-realm +REALM=frigate-realm CLIENT_ID=frontend-client \ No newline at end of file diff --git a/example/example.docker-compose.yml b/example/example.docker-compose.yml index 7ea73eb..ed1a73b 100644 --- a/example/example.docker-compose.yml +++ b/example/example.docker-compose.yml @@ -8,7 +8,8 @@ services: - /etc/localtime:/etc/localtime:ro # for Unix Time environment: FRIGATE_PROXY: http://localhost:4000 - OPENID_SERVER: https://server:port/realms/your-realm + OPENID_SERVER: https://server:port CLIENT_ID: frontend-client + REALM: frigate-realm ports: - 80:80 # set your port here \ No newline at end of file diff --git a/package.json b/package.json index 6dccf07..95e2c66 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@mantine/modals": "^6.0.16", "@mantine/notifications": "^6.0.16", "@monaco-editor/react": "^4.6.0", + "@react-keycloak/web": "^3.4.0", "@tabler/icons-react": "^2.24.0", "@tanstack/react-query": "^5.21.2", "@testing-library/jest-dom": "^5.14.1", @@ -35,18 +36,17 @@ "i18next-browser-languagedetector": "^7.2.0", "idb-keyval": "^6.2.1", "jwt-decode": "^4.0.0", + "keycloak-js": "^24.0.1", "mantine-react-table": "^1.0.0-beta.25", "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", "mobx-utils": "^6.0.7", "monaco-editor": "^0.46.0", "monaco-yaml": "^5.1.1", - "oidc-client-ts": "^2.2.4", "react": "^18.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-i18next": "^14.1.0", - "react-oidc-context": "^2.2.2", "react-router-dom": "^6.14.1", "react-scripts": "5.0.1", "react-use-websocket": "^4.7.0", diff --git a/src/App.tsx b/src/App.tsx index 3d46542..9629f06 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,15 @@ -import { useEffect, useState } from 'react'; -import { hasAuthParams, useAuth } from 'react-oidc-context'; -import CenterLoader from './shared/components/loaders/CenterLoader'; import { ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core'; import { useColorScheme } from '@mantine/hooks'; -import { getCookie, setCookie } from 'cookies-next'; -import AppBody from './AppBody'; -import Forbidden from './pages/403'; +import { ModalsProvider } from '@mantine/modals'; import { Notifications } from '@mantine/notifications'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import RetryErrorPage from './pages/RetryErrorPage'; -import { keycloakConfig } from '.'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { ModalsProvider } from '@mantine/modals'; +import { getCookie, setCookie } from 'cookies-next'; +import { useState } from 'react'; +import AppBody from './AppBody'; import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal'; import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal'; +import { useRealmAccessRoles } from './hooks/useRealmAccessRoles'; +import { useAdminRole } from './hooks/useAdminRole'; const queryClient = new QueryClient({ defaultOptions: { @@ -35,70 +31,14 @@ declare module '@mantine/modals' { } function App() { - const maxErrorAuthCounts = 2 const systemColorScheme = useColorScheme() const [colorScheme, setColorScheme] = useState(getCookie('mantine-color-scheme') as ColorScheme || systemColorScheme) - const [authErrorCounter, setAuthErrorCounter] = useState(0) const toggleColorScheme = (value?: ColorScheme) => { const nextColorScheme = value || (colorScheme === 'dark' ? 'light' : 'dark'); setColorScheme(nextColorScheme) setCookie('mantine-color-scheme', nextColorScheme, { maxAge: 60 * 60 * 24 * 30 }); } - const auth = useAuth() - const location = useLocation() - const navigate = useNavigate() - - // automatically sign-in - useEffect(() => { - if (!hasAuthParams() && - !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && authErrorCounter < maxErrorAuthCounts) { - console.error('Not authenticated! Redirect! ErrorCounter', authErrorCounter) - setAuthErrorCounter(prevCount => prevCount + 1) - auth.signinRedirect() - } - }, [auth, auth.isAuthenticated, auth.activeNavigator, auth.isLoading, auth.signinRedirect, authErrorCounter]) - - - if (auth.activeNavigator || auth.isLoading) { - return - } - - if (authErrorCounter > maxErrorAuthCounts) { - console.error('maxErrorAuthCounts authority', keycloakConfig.authority) - console.error('maxErrorAuthCounts client_id', keycloakConfig.client_id) - console.error('maxErrorAuthCounts redirect_uri', keycloakConfig.redirect_uri) - return auth.signinRedirect()} /> - } - - if (hasAuthParams()) { - const urlParams = new URLSearchParams(location.search); - urlParams.delete('state'); - urlParams.delete('session_state'); - urlParams.delete('code'); - urlParams.delete('iss'); - navigate(`${location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`, { replace: true }) - } - - - if (!auth.isAuthenticated && !auth.isLoading && authErrorCounter < maxErrorAuthCounts) { - if (hasAuthParams()) { - console.warn('Not authenticated, isAuthenticated:', auth.isAuthenticated) - console.warn('Not authenticated, isLoading:', auth.isLoading) - return auth.signinRedirect()} /> - } else { - console.error('Not authenticated! Redirect! Error Counter:', authErrorCounter) - setAuthErrorCounter(prevCount => prevCount + 1); - auth.signinRedirect() - } - } - - if ((!hasAuthParams() && !auth.isAuthenticated && !auth.isLoading) || auth.error) { - setAuthErrorCounter(prevCount => prevCount + 1) - console.error(`auth.error:`, auth.error) - return - } - return (
diff --git a/src/AppBody.tsx b/src/AppBody.tsx index d558902..0d37f40 100644 --- a/src/AppBody.tsx +++ b/src/AppBody.tsx @@ -8,6 +8,8 @@ import { routesPath } from './router/routes.path'; import SideBar from './shared/components/SideBar'; import { isProduction } from './shared/env.const'; import { HeaderAction } from './widgets/header/HeaderAction'; +import { useAdminRole } from "./hooks/useAdminRole"; +import { useRealmAccessRoles } from "./hooks/useRealmAccessRoles"; const AppBody = () => { const { t } = useTranslation() diff --git a/src/hooks/useAdminRole.ts b/src/hooks/useAdminRole.ts index eb2ebc2..9c0a083 100644 --- a/src/hooks/useAdminRole.ts +++ b/src/hooks/useAdminRole.ts @@ -3,29 +3,30 @@ import { frigateQueryKeys, frigateApi } from "../services/frigate.proxy/frigate. import { useRealmAccessRoles } from "./useRealmAccessRoles"; import { useEffect, useState } from "react"; +export interface AdminRole { + isLoading: boolean + isAdmin: boolean +} -export const useAdminRole = () => { - const { data: adminConfig, isError, isFetching } = useQuery({ +export const useAdminRole = (): AdminRole => { + const { data: adminConfig, isError, isLoading } = useQuery({ queryKey: [frigateQueryKeys.getAdminRole], queryFn: frigateApi.getAdminRole, - staleTime: 1000 * 60 * 60, - gcTime: 1000 * 60 * 60 * 24, + staleTime: 1000 * 60 * 60, + gcTime: 1000 * 60 * 60 * 24, }) const roles = useRealmAccessRoles() - const [initialized, setInitialized] = useState(false) - const isLoading = isFetching || roles === undefined + const [isAdmin, setIsAdmin] = useState(false) useEffect(() => { - if (!isLoading) { - setInitialized(true); + if (adminConfig) { + const checkAdmin = roles.some(role => role === adminConfig.value) + setIsAdmin(checkAdmin) + } else { + setIsAdmin(false) } - }, [isLoading]); + }, [roles, adminConfig, isLoading]) - if (!initialized || isLoading) return { isAdmin: undefined, isLoading: true } - if (isError) return { isAdmin: false, isError: true, isLoading: false } - if (!adminConfig) return { isAdmin: true, isLoading: false } - if (adminConfig && !adminConfig.value) return { isAdmin: true, isLoading: false } - const isAdmin = roles.some(role => role === adminConfig.value) - return { isAdmin, isLoading: false } + return { isLoading, isAdmin } } \ No newline at end of file diff --git a/src/hooks/useRealmAccessRoles.ts b/src/hooks/useRealmAccessRoles.ts index 1168a86..5206fbd 100644 --- a/src/hooks/useRealmAccessRoles.ts +++ b/src/hooks/useRealmAccessRoles.ts @@ -1,30 +1,36 @@ -import { jwtDecode } from "jwt-decode"; import { useState, useEffect } from "react"; -import { useAuth } from "react-oidc-context"; - -interface CustomJwtPayload { - realm_access?: { - roles: string[]; - }; -} +import { isProduction } from "../shared/env.const"; +import { useKeycloak } from "@react-keycloak/web"; export const useRealmAccessRoles = () => { - const { user } = useAuth(); const [roles, setRoles] = useState([]); + const { keycloak } = useKeycloak() useEffect(() => { - if (user) { - try { - const decoded = jwtDecode(user.access_token); - const realmAccess = decoded.realm_access; - if (realmAccess && realmAccess.roles) { - setRoles(realmAccess.roles); - } - } catch (error) { - console.error("Error decoding token:", error); + const updateRoles = () => { + const tokenRoles = keycloak.tokenParsed?.realm_access?.roles; + if (!isProduction) console.log(`tokenRoles:`, tokenRoles); + if (tokenRoles) { + setRoles(tokenRoles); + } else { + setRoles([]) } } - }, [user]); + + updateRoles() - return roles; -}; \ No newline at end of file + keycloak.onAuthSuccess = () => { + updateRoles() + } + keycloak.onAuthRefreshSuccess = () => { + updateRoles() + } + + return () => { + keycloak.onAuthSuccess = undefined + keycloak.onAuthRefreshSuccess = undefined + } + }, [keycloak, keycloak.onAuthSuccess, keycloak.onAuthRefreshSuccess ]) + + return roles +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 6eb45c2..c4e21ce 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,10 +3,11 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import reportWebVitals from './reportWebVitals'; import RootStore from './shared/stores/root.store'; -import { AuthProvider, AuthProviderProps } from 'react-oidc-context'; -import { isProduction, oidpSettings } from './shared/env.const'; import { BrowserRouter } from 'react-router-dom'; import './services/i18n'; +import { ReactKeycloakProvider } from '@react-keycloak/web'; +import keycloak from './services/keycloak-config'; +import CenterLoader from './shared/components/loaders/CenterLoader'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -14,39 +15,35 @@ const root = ReactDOM.createRoot( export const hostURL = new URL(window.location.href) -export const keycloakConfig: AuthProviderProps = { - authority: oidpSettings.server, - client_id: oidpSettings.clientId, - redirect_uri: hostURL.toString(), - onSigninCallback: () => { - const currentUrl = new URL(window.location.href); - const params = currentUrl.searchParams; - params.delete('state'); - params.delete('session_state'); - params.delete('code'); - params.delete('iss'); - const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}` - window.history.replaceState({}, document.title, newUrl) - } -} const rootStore = new RootStore() export const Context = createContext(rootStore) -if (!isProduction) { - console.log('keycloakConfig.authority', keycloakConfig.authority) - console.log('keycloakConfig.client_id', keycloakConfig.client_id) - console.log('keycloakConfig.redirect_uri', keycloakConfig.redirect_uri) -} +const eventLogger = (event: string, error?: any) => { + console.log('onKeycloakEvent', event, error); +}; + +const tokenLogger = (tokens: any) => { + console.log('onKeycloakTokens', tokens); +}; root.render( - - + } + onEvent={eventLogger} + onTokens={tokenLogger} + initOptions={{ + onLoad: 'login-required', + checkLoginIframe: false + }} + > + - - + + ); // If you want to start measuring performance in your app, pass a function diff --git a/src/pages/403.tsx b/src/pages/403.tsx index 2952199..2fe0016 100644 --- a/src/pages/403.tsx +++ b/src/pages/403.tsx @@ -5,9 +5,11 @@ import { useTranslation } from 'react-i18next'; import { Context } from '..'; import { routesPath } from '../router/routes.path'; import CogWheelWithText from '../shared/components/loaders/CogWheelWithText'; +import { useNavigate } from 'react-router-dom'; const Forbidden = () => { const { t } = useTranslation() + const navigate = useNavigate() const executed = useRef(false) const { sideBarsStore } = useContext(Context) @@ -21,7 +23,7 @@ const Forbidden = () => { }, [sideBarsStore]) const handleGoToMain = () => { - window.location.replace(routesPath.MAIN_PATH) + navigate(routesPath.MAIN_PATH) } return ( diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts index 4a42dce..8f98829 100644 --- a/src/services/frigate.proxy/frigate.api.ts +++ b/src/services/frigate.proxy/frigate.api.ts @@ -8,19 +8,10 @@ import { import { FrigateConfig } from "../../types/frigateConfig"; import { RecordSummary } from "../../types/record"; import { EventFrigate } from "../../types/event"; -import { keycloakConfig } from "../.."; import { getResolvedTimeZone } from "../../shared/utils/dateUtil"; import { FrigateStats, GetFfprobe, GetHostStorage, GetVaInfo } from "../../types/frigateStats"; -import { hostname } from "os"; import { PostSaveConfig, SaveOption } from "../../types/saveConfig"; - - -export const getToken = (): string | undefined => { - const key = `oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`; - const stored = sessionStorage.getItem(key); - const storedObject = stored ? JSON.parse(stored) : null; - return storedObject?.access_token; -} +import keycloak from "../keycloak-config"; const instanceApi = axios.create({ baseURL: proxyURL.toString(), @@ -29,9 +20,9 @@ const instanceApi = axios.create({ instanceApi.interceptors.request.use( config => { - const token = getToken(); - if (token) { - config.headers.Authorization = `Bearer ${token}` + const accessToken = keycloak.token; + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}` } return config; }, diff --git a/src/services/keycloak-config.ts b/src/services/keycloak-config.ts new file mode 100644 index 0000000..4f4c47a --- /dev/null +++ b/src/services/keycloak-config.ts @@ -0,0 +1,12 @@ +import Keycloak from "keycloak-js"; +import { oidpSettings } from "../shared/env.const"; + +const keycloakConfig = { + url: oidpSettings.server, + realm: oidpSettings.realm, + clientId: oidpSettings.clientId, + }; + + const keycloak = new Keycloak(keycloakConfig); + + export default keycloak; \ No newline at end of file diff --git a/src/shared/components/UserMenu.tsx b/src/shared/components/UserMenu.tsx index b1c4fe4..6115c39 100644 --- a/src/shared/components/UserMenu.tsx +++ b/src/shared/components/UserMenu.tsx @@ -2,10 +2,9 @@ import { Avatar, Button, Flex, Group, Menu, Text } from "@mantine/core"; import { useMediaQuery } from '@mantine/hooks'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAuth } from 'react-oidc-context'; -import { keycloakConfig } from '../..'; import { dimensions } from '../dimensions/dimensions'; import ColorSchemeToggle from './buttons/ColorSchemeToggle'; +import keycloak from "../../services/keycloak-config"; interface UserMenuProps { user: { name: string; image: string } @@ -20,15 +19,10 @@ const UserMenu = ({ user }: UserMenuProps) => { { lng: 'ru', name: 'Rus' }, ] - const auth = useAuth() const isMiddleScreen = useMediaQuery(dimensions.middleScreenSize) - - const handleLogout = async () => { - await auth.removeUser() - const id_token_hint = auth.user?.id_token - await auth.signoutRedirect({ post_logout_redirect_uri: keycloakConfig.redirect_uri, id_token_hint: id_token_hint }) + keycloak.logout({ redirectUri: window.location.origin }) } const handleChangeLanguage = async (lng: string) => { diff --git a/src/shared/components/menu/DrawerMenu.tsx b/src/shared/components/menu/DrawerMenu.tsx index 8f7c6f6..a433346 100644 --- a/src/shared/components/menu/DrawerMenu.tsx +++ b/src/shared/components/menu/DrawerMenu.tsx @@ -39,7 +39,7 @@ const DrawerMenu = ({ links }: DrawerMenuProps) => { const navigate = useNavigate() - const { isAdmin } = useAdminRole() + const isAdmin = useAdminRole() const { classes } = useStyles(); diff --git a/src/shared/components/players/VideoPlayer.tsx b/src/shared/components/players/VideoPlayer.tsx index 594ca40..7bf604b 100644 --- a/src/shared/components/players/VideoPlayer.tsx +++ b/src/shared/components/players/VideoPlayer.tsx @@ -2,14 +2,15 @@ import React, { useRef, useEffect } from 'react'; import videojs from 'video.js'; import Player from 'video.js/dist/types/player'; import 'video.js/dist/video-js.css' -import { getToken } from '../../../services/frigate.proxy/frigate.api'; import { isProduction } from '../../env.const'; +import { useKeycloak } from '@react-keycloak/web'; interface VideoPlayerProps { videoUrl: string } const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => { + const { keycloak } = useKeycloak() const executed = useRef(false) const videoRef = useRef(null); const playerRef = useRef(null); @@ -20,7 +21,7 @@ const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => { videojs.Vhs.xhr.beforeRequest = function (options: any) { options.headers = { ...options.headers, - Authorization: `Bearer ${getToken()}`, + Authorization: `Bearer ${keycloak.token}`, }; return options; }; diff --git a/src/shared/env.const.ts b/src/shared/env.const.ts index e11f5e2..0df584f 100644 --- a/src/shared/env.const.ts +++ b/src/shared/env.const.ts @@ -12,7 +12,11 @@ const oidpServer = isProduction ? window.env?.OPENID_SERVER : process.env.REACT_ const oidpServerParsed= z.string().url().safeParse(oidpServer) if (!oidpServerParsed.success) throw Error(`OPENID_SERVER must be string and URL. OPENID_SERVER:${oidpServer}`) const oidpClientId = isProduction ? window.env?.CLIENT_ID : process.env.REACT_APP_CLIENT_ID +const oidpRealm = isProduction ? window.env?.REALM : process.env.REACT_APP_REALM +const parsedRealm = z.string().safeParse(oidpRealm) +if (!parsedRealm.success) throw Error(`REALM must be string and exist. REALM:${oidpRealm}`) export const oidpSettings = { server: oidpServer || '', clientId: oidpClientId || '', + realm: oidpRealm || '', } \ No newline at end of file diff --git a/src/shared/stores/user.store.ts b/src/shared/stores/user.store.ts index 5877117..2300476 100644 --- a/src/shared/stores/user.store.ts +++ b/src/shared/stores/user.store.ts @@ -1,9 +1,7 @@ import { makeAutoObservable, runInAction } from "mobx" -import { User } from "oidc-client-ts"; import { Resource } from "../utils/resource" import { sleep } from "../utils/async.sleep"; import { z } from 'zod' -import { keycloakConfig } from "../.."; export interface UserServer { @@ -71,20 +69,20 @@ export class UserStore { } getSessionStorage() { - const oidcStorage = sessionStorage.getItem(`oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`) - if (!oidcStorage) { - return undefined; - } + // const oidcStorage = sessionStorage.getItem(`oidc.user:${keycloakConfig.authority}:${keycloakConfig.client_id}`) + // if (!oidcStorage) { + // return undefined; + // } - return User.fromStorageString(oidcStorage) + // return User.fromStorageString(oidcStorage) } getUser() { - return this.getSessionStorage()?.profile + // return this.getSessionStorage()?.profile } getAccessToken() { - return this.getSessionStorage()?.access_token + // return this.getSessionStorage()?.access_token } } diff --git a/src/widgets/header/HeaderAction.tsx b/src/widgets/header/HeaderAction.tsx index a5efe13..601b662 100644 --- a/src/widgets/header/HeaderAction.tsx +++ b/src/widgets/header/HeaderAction.tsx @@ -1,5 +1,4 @@ import { Button, Container, Flex, Group, Header, Menu, createStyles, rem } from "@mantine/core"; -import { useAuth } from 'react-oidc-context'; import { useNavigate } from 'react-router-dom'; import { useAdminRole } from "../../hooks/useAdminRole"; import { routesPath } from "../../router/routes.path"; @@ -7,6 +6,7 @@ import UserMenu from '../../shared/components/UserMenu'; import ColorSchemeToggle from "../../shared/components/buttons/ColorSchemeToggle"; import Logo from "../../shared/components/images/LogoImage"; import DrawerMenu from "../../shared/components/menu/DrawerMenu"; +import keycloak from "../../services/keycloak-config"; const HEADER_HEIGHT = rem(60) @@ -61,7 +61,6 @@ export interface HeaderActionProps { export const HeaderAction = ({ links }: HeaderActionProps) => { const { classes } = useStyles(); const navigate = useNavigate() - const auth = useAuth() const { isAdmin } = useAdminRole() const handleNavigate = (link: string) => { @@ -78,6 +77,8 @@ export const HeaderAction = ({ links }: HeaderActionProps) => { ) + const userName = keycloak.tokenParsed?.preferred_username + (isAdmin ? ' (admin)' : '') + return (
@@ -93,7 +94,7 @@ export const HeaderAction = ({ links }: HeaderActionProps) => { - +
diff --git a/yarn.lock b/yarn.lock index 2e0b5fe..df3fb15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,6 +1129,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.9.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -1979,6 +1986,22 @@ dependencies: "@babel/runtime" "^7.13.10" +"@react-keycloak/core@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@react-keycloak/core/-/core-3.2.0.tgz#e46f1951b0d7873f7f2fcd73dd0c270cb0b18db8" + integrity sha512-1yzU7gQzs+6E1v6hGqxy0Q+kpMHg9sEcke2yxZR29WoU8KNE8E50xS6UbI8N7rWsgyYw8r9W1cUPCOF48MYjzw== + dependencies: + react-fast-compare "^3.2.0" + +"@react-keycloak/web@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@react-keycloak/web/-/web-3.4.0.tgz#725d96fab8e5fa47faff9615cc08574e5dff2222" + integrity sha512-yKKSCyqBtn7dt+VckYOW1IM5NW999pPkxDZOXqJ6dfXPXstYhOQCkTZqh8l7UL14PkpsoaHDh7hSJH8whah01g== + dependencies: + "@babel/runtime" "^7.9.0" + "@react-keycloak/core" "^3.2.0" + hoist-non-react-statics "^3.3.2" + "@remix-run/router@1.15.2": version "1.15.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.2.tgz#35726510d332ba5349c6398d13259d5da184553d" @@ -3978,11 +4001,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" - integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -5649,7 +5667,7 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6818,6 +6836,11 @@ jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +js-sha256@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.0.tgz#256a921d9292f7fe98905face82e367abaca9576" + integrity sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -6961,16 +6984,19 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" -jwt-decode@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" - integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== - jwt-decode@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== +keycloak-js@^24.0.1: + version "24.0.1" + resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-24.0.1.tgz#8de412bb2b914457b0dc9387a4a824bd0e8d171d" + integrity sha512-leV4mlpa0dqYUXTAuq1ufUfk8DOSBCembjQwMwzYrM6xfHSKpcZMxviTWXqro52LMSsYAnivSKVNEvBkLzi7Eg== + dependencies: + js-sha256 "^0.11.0" + jwt-decode "^4.0.0" + keycode@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" @@ -7645,14 +7671,6 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -oidc-client-ts@^2.2.4: - version "2.4.0" - resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz#764c8a33de542026e2798de9849ce8049047d7e5" - integrity sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w== - dependencies: - crypto-js "^4.2.0" - jwt-decode "^3.1.2" - on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -8685,6 +8703,11 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== +react-fast-compare@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + react-i18next@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.0.tgz#44da74fbffd416f5d0c5307ef31735cf10cc91d9" @@ -8708,11 +8731,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-oidc-context@^2.2.2: - version "2.3.1" - resolved "https://registry.yarnpkg.com/react-oidc-context/-/react-oidc-context-2.3.1.tgz#04eea5aaea972af1a49de6b8d5ff103b9daa0e73" - integrity sha512-WdhmEU6odNzMk9pvOScxUkf6/1aduiI/nQryr7+iCl2VDnYLASDTIV/zy58KuK4VXG3fBaRKukc/mRpMjF9a3Q== - react-refresh@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"