Refactor initalization of Keycloak JS in Admin Console (#20100)

This commit is contained in:
Jon Koops 2023-05-03 11:27:27 +00:00 committed by GitHub
parent 75ea22bad2
commit 228da0e29a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 72 additions and 79 deletions

View file

@ -1,6 +1,4 @@
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import { Page } from "@patternfly/react-core"; import { Page } from "@patternfly/react-core";
import type Keycloak from "keycloak-js";
import { PropsWithChildren, Suspense } from "react"; import { PropsWithChildren, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
@ -15,7 +13,7 @@ import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner";
import { RealmsProvider } from "./context/RealmsContext"; import { RealmsProvider } from "./context/RealmsContext";
import { RecentRealmsProvider } from "./context/RecentRealms"; import { RecentRealmsProvider } from "./context/RecentRealms";
import { AccessContextProvider } from "./context/access/Access"; import { AccessContextProvider } from "./context/access/Access";
import { AdminClientContext } from "./context/auth/AdminClient"; import { AdminClientProvider } from "./context/auth/AdminClient";
import { RealmContextProvider } from "./context/realm-context/RealmContext"; import { RealmContextProvider } from "./context/realm-context/RealmContext";
import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider"; import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider";
import { WhoAmIContextProvider } from "./context/whoami/WhoAmI"; import { WhoAmIContextProvider } from "./context/whoami/WhoAmI";
@ -24,17 +22,8 @@ import { AuthWall } from "./root/AuthWall";
export const mainPageContentId = "kc-main-content-page-container"; export const mainPageContentId = "kc-main-content-page-container";
export type AdminClientProps = { const AppContexts = ({ children }: PropsWithChildren) => (
keycloak: Keycloak; <AdminClientProvider>
adminClient: KeycloakAdminClient;
};
const AppContexts = ({
children,
keycloak,
adminClient,
}: PropsWithChildren<AdminClientProps>) => (
<AdminClientContext.Provider value={{ keycloak, adminClient }}>
<WhoAmIContextProvider> <WhoAmIContextProvider>
<RealmsProvider> <RealmsProvider>
<RealmContextProvider> <RealmContextProvider>
@ -50,12 +39,12 @@ const AppContexts = ({
</RealmContextProvider> </RealmContextProvider>
</RealmsProvider> </RealmsProvider>
</WhoAmIContextProvider> </WhoAmIContextProvider>
</AdminClientContext.Provider> </AdminClientProvider>
); );
export const App = ({ keycloak, adminClient }: AdminClientProps) => { export const App = () => {
return ( return (
<AppContexts keycloak={keycloak} adminClient={adminClient}> <AppContexts>
<Page <Page
header={<Header />} header={<Header />}
isManagedSidebar isManagedSidebar

View file

@ -15,16 +15,16 @@ import { HelpIcon } from "@patternfly/react-icons";
import { ReactNode, useState } from "react"; import { ReactNode, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { HelpHeader } from "./components/help-enabler/HelpHeader";
import { useHelp } from "ui-shared"; import { useHelp } from "ui-shared";
import { useAdminClient } from "./context/auth/AdminClient";
import { HelpHeader } from "./components/help-enabler/HelpHeader";
import { useRealm } from "./context/realm-context/RealmContext"; import { useRealm } from "./context/realm-context/RealmContext";
import { useWhoAmI } from "./context/whoami/WhoAmI"; import { useWhoAmI } from "./context/whoami/WhoAmI";
import { toDashboard } from "./dashboard/routes/Dashboard"; import { toDashboard } from "./dashboard/routes/Dashboard";
import environment from "./environment"; import environment from "./environment";
import { keycloak } from "./keycloak";
const ManageAccountDropdownItem = () => { const ManageAccountDropdownItem = () => {
const { keycloak } = useAdminClient();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<DropdownItem <DropdownItem
@ -38,7 +38,6 @@ const ManageAccountDropdownItem = () => {
}; };
const SignOutDropdownItem = () => { const SignOutDropdownItem = () => {
const { keycloak } = useAdminClient();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<DropdownItem <DropdownItem
@ -134,8 +133,7 @@ export const Header = () => {
const { realm } = useRealm(); const { realm } = useRealm();
const headerTools = () => { const headerTools = () => {
const adminClient = useAdminClient(); const picture = keycloak.tokenParsed?.picture;
const picture = adminClient.keycloak.tokenParsed?.picture;
return ( return (
<PageHeaderTools> <PageHeaderTools>
<PageHeaderToolsGroup <PageHeaderToolsGroup

View file

@ -0,0 +1,20 @@
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import environment from "./environment";
import { keycloak } from "./keycloak";
export const adminClient = new KeycloakAdminClient();
adminClient.setConfig({ realmName: environment.loginRealm });
adminClient.baseUrl = environment.authUrl;
adminClient.registerTokenProvider({
async getAccessToken() {
try {
await keycloak.updateToken(5);
} catch (error) {
keycloak.login();
}
return keycloak.token;
},
});

View file

@ -2,8 +2,9 @@ import { NetworkError } from "@keycloak/keycloak-admin-client";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { sortBy } from "lodash-es"; import { sortBy } from "lodash-es";
import { PropsWithChildren, useCallback, useMemo, useState } from "react"; import { PropsWithChildren, useCallback, useMemo, useState } from "react";
import { createNamedContext, useRequiredContext } from "ui-shared"; import { createNamedContext, useRequiredContext } from "ui-shared";
import { keycloak } from "../keycloak";
import { useAdminClient, useFetch } from "./auth/AdminClient"; import { useAdminClient, useFetch } from "./auth/AdminClient";
type RealmsContextProps = { type RealmsContextProps = {
@ -19,7 +20,7 @@ export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
); );
export const RealmsProvider = ({ children }: PropsWithChildren) => { export const RealmsProvider = ({ children }: PropsWithChildren) => {
const { keycloak, adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const [realms, setRealms] = useState<RealmRepresentation[]>([]); const [realms, setRealms] = useState<RealmRepresentation[]>([]);
const [refreshCount, setRefreshCount] = useState(0); const [refreshCount, setRefreshCount] = useState(0);

View file

@ -1,19 +1,24 @@
import KeycloakAdminClient from "@keycloak/keycloak-admin-client"; import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import Keycloak from "keycloak-js"; import { DependencyList, PropsWithChildren, useEffect } from "react";
import { DependencyList, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary"; import { useErrorHandler } from "react-error-boundary";
import environment from "../../environment";
import { createNamedContext, useRequiredContext } from "ui-shared"; import { createNamedContext, useRequiredContext } from "ui-shared";
import { adminClient } from "../../admin-client";
export type AdminClientProps = { export type AdminClientProps = {
keycloak: Keycloak;
adminClient: KeycloakAdminClient; adminClient: KeycloakAdminClient;
}; };
export const AdminClientContext = createNamedContext< const AdminClientContext = createNamedContext<AdminClientProps | undefined>(
AdminClientProps | undefined "AdminClientContext",
>("AdminClientContext", undefined); undefined
);
export const AdminClientProvider = ({ children }: PropsWithChildren) => (
<AdminClientContext.Provider value={{ adminClient }}>
{children}
</AdminClientContext.Provider>
);
export const useAdminClient = () => useRequiredContext(AdminClientContext); export const useAdminClient = () => useRequiredContext(AdminClientContext);
@ -58,33 +63,3 @@ export function useFetch<T>(
return () => controller.abort(); return () => controller.abort();
}, deps); }, deps);
} }
export async function initAdminClient() {
const keycloak = new Keycloak({
url: environment.authServerUrl,
realm: environment.loginRealm,
clientId: environment.isRunningAsTheme
? "security-admin-console"
: "security-admin-console-v2",
});
await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" });
const adminClient = new KeycloakAdminClient();
adminClient.setConfig({ realmName: environment.loginRealm });
adminClient.baseUrl = environment.authUrl;
adminClient.registerTokenProvider({
async getAccessToken() {
try {
await keycloak.updateToken(5);
} catch (error) {
keycloak.login();
}
return keycloak.token;
},
});
return { keycloak, adminClient };
}

View file

@ -1,22 +1,20 @@
import { init, use, InitOptions, TOptions } from "i18next"; import { InitOptions, TOptions, init, use } from "i18next";
import HttpBackend, { LoadPathOption } from "i18next-http-backend"; import HttpBackend, { LoadPathOption } from "i18next-http-backend";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import { adminClient } from "./admin-client";
import environment from "./environment"; import environment from "./environment";
import { getAuthorizationHeaders } from "./utils/getAuthorizationHeaders";
import { addTrailingSlash } from "./util"; import { addTrailingSlash } from "./util";
import { getAuthorizationHeaders } from "./utils/getAuthorizationHeaders";
export const DEFAULT_LOCALE = "en"; export const DEFAULT_LOCALE = "en";
export async function initI18n(adminClient: KeycloakAdminClient) { export async function initI18n() {
const options = await initOptions(adminClient); const options = await initOptions();
await init(options); await init(options);
} }
const initOptions = async ( const initOptions = async (): Promise<InitOptions> => {
adminClient: KeycloakAdminClient
): Promise<InitOptions> => {
const constructLoadPath: LoadPathOption = (_, namespaces) => { const constructLoadPath: LoadPathOption = (_, namespaces) => {
if (namespaces[0] === "overrides") { if (namespaces[0] === "overrides") {
return `${addTrailingSlash(adminClient.baseUrl)}admin/realms/${ return `${addTrailingSlash(adminClient.baseUrl)}admin/realms/${

View file

@ -0,0 +1,11 @@
import Keycloak from "keycloak-js";
import environment from "./environment";
export const keycloak = new Keycloak({
url: environment.authServerUrl,
realm: environment.loginRealm,
clientId: environment.isRunningAsTheme
? "security-admin-console"
: "security-admin-console-v2",
});

View file

@ -5,10 +5,16 @@ import { StrictMode } from "react";
import { render } from "react-dom"; import { render } from "react-dom";
import { createHashRouter, RouterProvider } from "react-router-dom"; import { createHashRouter, RouterProvider } from "react-router-dom";
import { initI18n } from "./i18n";
import { keycloak } from "./keycloak";
import { RootRoute } from "./routes"; import { RootRoute } from "./routes";
import "./index.css"; import "./index.css";
// Initialize required components before rendering app.
await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" });
await initI18n();
const router = createHashRouter([RootRoute]); const router = createHashRouter([RootRoute]);
const container = document.getElementById("app"); const container = document.getElementById("app");

View file

@ -2,8 +2,6 @@ import type { AccessType } from "@keycloak/keycloak-admin-client/lib/defs/whoAmI
import type { TFunction } from "i18next"; import type { TFunction } from "i18next";
import type { ComponentType } from "react"; import type { ComponentType } from "react";
import type { NonIndexRouteObject, RouteObject } from "react-router"; import type { NonIndexRouteObject, RouteObject } from "react-router";
import { initAdminClient } from "./context/auth/AdminClient";
import { initI18n } from "./i18n";
import { App } from "./App"; import { App } from "./App";
import { PageNotFoundSection } from "./PageNotFoundSection"; import { PageNotFoundSection } from "./PageNotFoundSection";
@ -56,12 +54,8 @@ export const routes: AppRouteObject[] = [
NotFoundRoute, NotFoundRoute,
]; ];
const { keycloak, adminClient } = await initAdminClient();
await initI18n(adminClient);
export const RootRoute: RouteObject = { export const RootRoute: RouteObject = {
path: "/", path: "/",
element: <App keycloak={keycloak} adminClient={adminClient} />, element: <App />,
children: routes, children: routes,
}; };

View file

@ -24,6 +24,7 @@ import {
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useWhoAmI } from "../context/whoami/WhoAmI"; import { useWhoAmI } from "../context/whoami/WhoAmI";
import { keycloak } from "../keycloak";
import { toUser } from "../user/routes/User"; import { toUser } from "../user/routes/User";
import useFormatDate from "../utils/useFormatDate"; import useFormatDate from "../utils/useFormatDate";
@ -78,7 +79,7 @@ export default function SessionsTable({
const { realm } = useRealm(); const { realm } = useRealm();
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const { t } = useTranslation("sessions"); const { t } = useTranslation("sessions");
const { keycloak, adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { addError } = useAlerts(); const { addError } = useAlerts();
const formatDate = useFormatDate(); const formatDate = useFormatDate();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);