From 5897334ddb640c4edc5b7424c485b1ed05394a48 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 6 Jun 2024 08:36:46 +0200 Subject: [PATCH] Align environment variables between consoles (#30125) * change to make authServerUrl the same as authUrl fixes: #29641 Signed-off-by: Erik Jan de Wit * Remove `authUrl` entirely Signed-off-by: Jon Koops * Remove file that is unrelated Signed-off-by: Jon Koops * Split out and align environment variables between consoles Signed-off-by: Jon Koops * Restore removed variables to preserve backwards compatibility Signed-off-by: Jon Koops * Also deprecate the `authUrl` for the Admin Console Signed-off-by: Jon Koops --------- Signed-off-by: Erik Jan de Wit Signed-off-by: Jon Koops Co-authored-by: Jon Koops --- .../topics/changes/changes-25_0_0.adoc | 19 +++ js/apps/account-ui/README.md | 2 +- js/apps/account-ui/pom.xml | 29 ++--- js/apps/account-ui/src/api.ts | 11 +- js/apps/account-ui/src/api/methods.ts | 10 +- js/apps/account-ui/src/api/request.ts | 15 ++- js/apps/account-ui/src/environment.ts | 61 +++++++++ js/apps/account-ui/src/i18n.ts | 5 +- .../src/personal-info/PersonalInfo.tsx | 5 +- js/apps/account-ui/src/root/Header.tsx | 13 +- js/apps/account-ui/src/root/PageNav.tsx | 13 +- js/apps/account-ui/src/root/Root.tsx | 4 +- js/apps/account-ui/src/routes.tsx | 3 +- js/apps/admin-ui/pom.xml | 12 +- js/apps/admin-ui/src/App.tsx | 10 +- js/apps/admin-ui/src/Root.tsx | 4 +- js/apps/admin-ui/src/admin-client.ts | 7 +- js/apps/admin-ui/src/environment.ts | 33 +++++ js/apps/admin-ui/src/i18n/i18n.ts | 3 +- .../add/SamlConnectSettings.tsx | 11 +- .../add/SamlGeneralSettings.tsx | 12 +- .../src/realm-settings/RealmSettingsTabs.tsx | 6 +- .../ui-shared/src/context/KeycloakContext.tsx | 2 +- js/libs/ui-shared/src/context/environment.ts | 116 +++--------------- js/libs/ui-shared/src/main.ts | 6 +- .../resources/account/AccountConsole.java | 5 +- .../resources/admin/AdminConsole.java | 1 + 27 files changed, 242 insertions(+), 176 deletions(-) create mode 100644 js/apps/account-ui/src/environment.ts create mode 100644 js/apps/admin-ui/src/environment.ts diff --git a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc index 70209777a1..a1a68590cd 100644 --- a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc @@ -438,3 +438,22 @@ An external {jdgserver_name} deployment is supported for multi-site setups as ou The Oracle Database JDBC driver is no longer part of the Keycloak distribution. If you wish to use Oracle DB, you must manually install a version of the Oracle Driver that is compatible with your specific environment. Instructions for this process can be found in the https://www.keycloak.org/server/db[Configuring the database] {section}. + += Deprecated theme variables + +The following variables were deprecated in the Account theme: + +* `authUrl`. Use `authServerUrl` instead. + +The following variables from the environment script injected into the page of the Account theme are deprecated: + +* `authUrl`. Use `authServerUrl` instead. +* `features.isInternationalizationEnabled`. Do not use this variable. + +The following variables were deprecated in the Admin theme: + +* `authUrl`. Do not use this variable. + +The following variables from the environment script injected into the page of the Admin theme are deprecated: + +* `authUrl`. Do not use this variable. diff --git a/js/apps/account-ui/README.md b/js/apps/account-ui/README.md index 80d4da3b39..9d03f353f3 100644 --- a/js/apps/account-ui/README.md +++ b/js/apps/account-ui/README.md @@ -22,7 +22,7 @@ import { KeycloakProvider } from "@keycloak/keycloak-account-ui"; //... diff --git a/js/apps/account-ui/pom.xml b/js/apps/account-ui/pom.xml index 7b7b41894d..9378e6035a 100644 --- a/js/apps/account-ui/pom.xml +++ b/js/apps/account-ui/pom.xml @@ -149,27 +149,28 @@ diff --git a/js/apps/account-ui/src/api.ts b/js/apps/account-ui/src/api.ts index d5a8f58fda..91087130fa 100644 --- a/js/apps/account-ui/src/api.ts +++ b/js/apps/account-ui/src/api.ts @@ -1,13 +1,16 @@ -import { KeycloakContext } from "@keycloak/keycloak-ui-shared"; -import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/environment"; +import { + KeycloakContext, + type BaseEnvironment, +} from "@keycloak/keycloak-ui-shared"; + import { CallOptions } from "./api/methods"; import { Links, parseLinks } from "./api/parse-links"; import { parseResponse } from "./api/parse-response"; import { + CredentialsIssuer, Permission, Resource, Scope, - CredentialsIssuer, SupportedCredentialConfiguration, } from "./api/representations"; import { request } from "./api/request"; @@ -85,7 +88,7 @@ export async function getIssuer(context: KeycloakContext) { {}, new URL( joinPath( - context.environment.authUrl + + context.environment.authServerUrl + "/realms/" + context.environment.realm + "/.well-known/openid-credential-issuer", diff --git a/js/apps/account-ui/src/api/methods.ts b/js/apps/account-ui/src/api/methods.ts index 45ac408786..c264605550 100644 --- a/js/apps/account-ui/src/api/methods.ts +++ b/js/apps/account-ui/src/api/methods.ts @@ -1,8 +1,8 @@ import { - AccountEnvironment, - KeycloakContext, + BaseEnvironment, + type KeycloakContext, } from "@keycloak/keycloak-ui-shared"; -import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/environment"; + import { joinPath } from "../utils/joinPath"; import { parseResponse } from "./parse-response"; import { @@ -45,7 +45,7 @@ export async function getSupportedLocales({ } export async function savePersonalInfo( - context: KeycloakContext, + context: KeycloakContext, info: UserRepresentation, ): Promise { const response = await request("/", context, { body: info, method: "POST" }); @@ -134,7 +134,7 @@ export async function linkAccount( ) { const redirectUri = encodeURIComponent( joinPath( - context.environment.authUrl, + context.environment.authServerUrl, "realms", context.environment.realm, "account", diff --git a/js/apps/account-ui/src/api/request.ts b/js/apps/account-ui/src/api/request.ts index 31a5aa283b..e01f31d98e 100644 --- a/js/apps/account-ui/src/api/request.ts +++ b/js/apps/account-ui/src/api/request.ts @@ -1,6 +1,9 @@ -import { KeycloakContext } from "@keycloak/keycloak-ui-shared"; -import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/environment"; +import { + KeycloakContext, + type BaseEnvironment, +} from "@keycloak/keycloak-ui-shared"; import Keycloak from "keycloak-js"; + import { joinPath } from "../utils/joinPath"; import { CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON } from "./constants"; @@ -50,7 +53,13 @@ export async function request( export const url = (environment: BaseEnvironment, path: string) => new URL( - joinPath(environment.authUrl, "realms", environment.realm, "account", path), + joinPath( + environment.authServerUrl, + "realms", + environment.realm, + "account", + path, + ), ); export const token = (keycloak: Keycloak) => diff --git a/js/apps/account-ui/src/environment.ts b/js/apps/account-ui/src/environment.ts new file mode 100644 index 0000000000..c3f9ac3bd5 --- /dev/null +++ b/js/apps/account-ui/src/environment.ts @@ -0,0 +1,61 @@ +import { + getInjectedEnvironment, + type BaseEnvironment, +} from "@keycloak/keycloak-ui-shared"; + +export type Environment = BaseEnvironment & { + /** The URL to the root of the account console. */ + baseUrl: string; + /** The locale of the user */ + locale: string; + /** Name of the referrer application in the back link */ + referrerName?: string; + /** UR to the referrer application in the back link */ + referrerUrl?: string; + /** Feature flags */ + features: Feature; +}; + +export type Feature = { + isRegistrationEmailAsUsername: boolean; + isEditUserNameAllowed: boolean; + isLinkedAccountsEnabled: boolean; + isMyResourcesEnabled: boolean; + deleteAccountAllowed: boolean; + updateEmailFeatureEnabled: boolean; + updateEmailActionEnabled: boolean; + isViewGroupsEnabled: boolean; + isOid4VciEnabled: boolean; +}; + +// During development the realm can be passed as a query parameter when redirecting back from Keycloak. +const realm = + new URLSearchParams(window.location.search).get("realm") || + location.pathname.match("/realms/(.*?)/account")?.[1] || + "master"; + +const defaultEnvironment: Environment = { + // Base environment variables + authServerUrl: "http://localhost:8180", + realm: realm, + clientId: "security-admin-console-v2", + resourceUrl: "http://localhost:8080", + logo: "/logo.svg", + logoUrl: "/", + // Account Console specific environment variables + baseUrl: `http://localhost:8180/realms/${realm}/account/`, + locale: "en", + features: { + isRegistrationEmailAsUsername: false, + isEditUserNameAllowed: true, + isLinkedAccountsEnabled: true, + isMyResourcesEnabled: true, + deleteAccountAllowed: true, + updateEmailFeatureEnabled: true, + updateEmailActionEnabled: true, + isViewGroupsEnabled: true, + isOid4VciEnabled: true, + }, +}; + +export const environment = getInjectedEnvironment(defaultEnvironment); diff --git a/js/apps/account-ui/src/i18n.ts b/js/apps/account-ui/src/i18n.ts index 651a7ee9df..d1bec9faf0 100644 --- a/js/apps/account-ui/src/i18n.ts +++ b/js/apps/account-ui/src/i18n.ts @@ -1,7 +1,8 @@ import { LanguageDetectorModule, createInstance } from "i18next"; import HttpBackend from "i18next-http-backend"; import { initReactI18next } from "react-i18next"; -import { environment } from "@keycloak/keycloak-ui-shared"; + +import { environment } from "./environment"; import { joinPath } from "./utils/joinPath"; const DEFAULT_LOCALE = "en"; @@ -28,7 +29,7 @@ export const i18n = createInstance({ }, backend: { loadPath: joinPath( - environment.authUrl, + environment.authServerUrl, `resources/${environment.realm}/account/{{lng}}`, ), parse: (data: string) => { diff --git a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx index 37e932b854..c176f5c5b3 100644 --- a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx +++ b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx @@ -1,5 +1,4 @@ import { - AccountEnvironment, UserProfileFields, beerify, debeerify, @@ -20,6 +19,7 @@ import { TFunction } from "i18next"; import { useState } from "react"; import { ErrorOption, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; + import { getPersonalInfo, getSupportedLocales, @@ -30,12 +30,13 @@ import { UserRepresentation, } from "../api/representations"; import { Page } from "../components/page/Page"; +import type { Environment } from "../environment"; import { TFuncKey, i18n } from "../i18n"; import { usePromise } from "../utils/usePromise"; export const PersonalInfo = () => { const { t } = useTranslation(); - const context = useEnvironment(); + const context = useEnvironment(); const [userProfileMetadata, setUserProfileMetadata] = useState(); const [supportedLocales, setSupportedLocales] = useState([]); diff --git a/js/apps/account-ui/src/root/Header.tsx b/js/apps/account-ui/src/root/Header.tsx index 024c34b6f1..687bb5a81e 100644 --- a/js/apps/account-ui/src/root/Header.tsx +++ b/js/apps/account-ui/src/root/Header.tsx @@ -1,13 +1,14 @@ +import { + KeycloakMasthead, + label, + useEnvironment, +} from "@keycloak/keycloak-ui-shared"; import { Button } from "@patternfly/react-core"; import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons"; import { useTranslation } from "react-i18next"; import { useHref } from "react-router-dom"; -import { - KeycloakMasthead, - environment, - label, - useEnvironment, -} from "@keycloak/keycloak-ui-shared"; + +import { environment } from "../environment"; import { joinPath } from "../utils/joinPath"; import style from "./header.module.css"; diff --git a/js/apps/account-ui/src/root/PageNav.tsx b/js/apps/account-ui/src/root/PageNav.tsx index c0be3b8015..0463401e46 100644 --- a/js/apps/account-ui/src/root/PageNav.tsx +++ b/js/apps/account-ui/src/root/PageNav.tsx @@ -1,3 +1,4 @@ +import { useEnvironment } from "@keycloak/keycloak-ui-shared"; import { Nav, NavExpandable, @@ -22,13 +23,9 @@ import { useLinkClickHandler, useLocation, } from "react-router-dom"; -import { - AccountEnvironment, - environment, - useEnvironment, - type Feature, -} from "@keycloak/keycloak-ui-shared"; + import fetchContentJson from "../content/fetchContent"; +import { environment, type Environment, type Feature } from "../environment"; import { TFuncKey } from "../i18n"; import { usePromise } from "../utils/usePromise"; @@ -49,7 +46,7 @@ export type MenuItem = RootMenuItem | MenuItemWithChildren; export const PageNav = () => { const [menuItems, setMenuItems] = useState(); - const context = useEnvironment(); + const context = useEnvironment(); usePromise((signal) => fetchContentJson({ signal, context }), setMenuItems); return ( @@ -86,7 +83,7 @@ function NavMenuItem({ menuItem }: NavMenuItemProps) { const { t } = useTranslation(); const { environment: { features }, - } = useEnvironment(); + } = useEnvironment(); const { pathname } = useLocation(); const isActive = useMemo( () => matchMenuItem(pathname, menuItem), diff --git a/js/apps/account-ui/src/root/Root.tsx b/js/apps/account-ui/src/root/Root.tsx index 8527a544fb..74c848c62c 100644 --- a/js/apps/account-ui/src/root/Root.tsx +++ b/js/apps/account-ui/src/root/Root.tsx @@ -1,7 +1,9 @@ -import { KeycloakProvider, environment } from "@keycloak/keycloak-ui-shared"; +import { KeycloakProvider } from "@keycloak/keycloak-ui-shared"; import { Page, Spinner } from "@patternfly/react-core"; import { Suspense } from "react"; import { Outlet } from "react-router-dom"; + +import { environment } from "../environment"; import { Header } from "./Header"; import { PageNav } from "./PageNav"; diff --git a/js/apps/account-ui/src/routes.tsx b/js/apps/account-ui/src/routes.tsx index 3aafc25141..a54e8b1f46 100644 --- a/js/apps/account-ui/src/routes.tsx +++ b/js/apps/account-ui/src/routes.tsx @@ -1,6 +1,7 @@ import { lazy } from "react"; import type { IndexRouteObject, RouteObject } from "react-router-dom"; -import { environment } from "@keycloak/keycloak-ui-shared"; + +import { environment } from "./environment"; import { ErrorPage } from "./root/ErrorPage"; import { Root } from "./root/Root"; diff --git a/js/apps/admin-ui/pom.xml b/js/apps/admin-ui/pom.xml index d6a62f1cac..adcc43a658 100644 --- a/js/apps/admin-ui/pom.xml +++ b/js/apps/admin-ui/pom.xml @@ -124,16 +124,16 @@ { + "authUrl": "${authUrl}", + "authServerUrl": "${authServerUrl}", "realm": "${loginRealm!"master"}", "clientId": "${clientId}", - "authServerUrl": "${authServerUrl}", - "authUrl": "${authUrl}", - "consoleBaseUrl": "${consoleBaseUrl}", "resourceUrl": "${resourceUrl}", - "masterRealm": "${masterRealm}", - "resourceVersion": "${resourceVersion}", "logo": "${properties.logo!""}", - "logoUrl": "${properties.logoUrl!""}" + "logoUrl": "${properties.logoUrl!""}", + "consoleBaseUrl": "${consoleBaseUrl}", + "masterRealm": "${masterRealm}", + "resourceVersion": "${resourceVersion}" } diff --git a/js/apps/admin-ui/src/App.tsx b/js/apps/admin-ui/src/App.tsx index 09a6601990..e4697ddca4 100644 --- a/js/apps/admin-ui/src/App.tsx +++ b/js/apps/admin-ui/src/App.tsx @@ -1,11 +1,12 @@ import KeycloakAdminClient from "@keycloak/keycloak-admin-client"; -import { Page } from "@patternfly/react-core"; -import { PropsWithChildren, Suspense, useEffect, useState } from "react"; -import { Outlet } from "react-router-dom"; import { mainPageContentId, useEnvironment, } from "@keycloak/keycloak-ui-shared"; +import { Page } from "@patternfly/react-core"; +import { PropsWithChildren, Suspense, useEffect, useState } from "react"; +import { Outlet } from "react-router-dom"; + import { Header } from "./PageHeader"; import { PageNav } from "./PageNav"; import { AdminClientContext, initAdminClient } from "./admin-client"; @@ -23,6 +24,7 @@ import { AccessContextProvider } from "./context/access/Access"; import { RealmContextProvider } from "./context/realm-context/RealmContext"; import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider"; import { WhoAmIContextProvider } from "./context/whoami/WhoAmI"; +import type { Environment } from "./environment"; import { SubGroups } from "./groups/SubGroupsContext"; import { AuthWall } from "./root/AuthWall"; @@ -47,7 +49,7 @@ const AppContexts = ({ children }: PropsWithChildren) => ( ); export const App = () => { - const { keycloak, environment } = useEnvironment(); + const { keycloak, environment } = useEnvironment(); const [adminClient, setAdminClient] = useState(); useEffect(() => { diff --git a/js/apps/admin-ui/src/Root.tsx b/js/apps/admin-ui/src/Root.tsx index 2bc35c6b3b..e075abd93b 100644 --- a/js/apps/admin-ui/src/Root.tsx +++ b/js/apps/admin-ui/src/Root.tsx @@ -1,5 +1,7 @@ -import { KeycloakProvider, environment } from "@keycloak/keycloak-ui-shared"; +import { KeycloakProvider } from "@keycloak/keycloak-ui-shared"; + import { App } from "./App"; +import { environment } from "./environment"; export const Root = () => ( diff --git a/js/apps/admin-ui/src/admin-client.ts b/js/apps/admin-ui/src/admin-client.ts index 0bd4d02cd5..ee8dff6700 100644 --- a/js/apps/admin-ui/src/admin-client.ts +++ b/js/apps/admin-ui/src/admin-client.ts @@ -3,9 +3,10 @@ import { createNamedContext, useRequiredContext, } from "@keycloak/keycloak-ui-shared"; -import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/environment"; import type Keycloak from "keycloak-js"; +import type { Environment } from "./environment"; + export type AdminClientProps = { keycloak: Keycloak; adminClient: KeycloakAdminClient; @@ -19,12 +20,12 @@ export const useAdminClient = () => useRequiredContext(AdminClientContext); export async function initAdminClient( keycloak: Keycloak, - environment: BaseEnvironment, + environment: Environment, ) { const adminClient = new KeycloakAdminClient(); adminClient.setConfig({ realmName: environment.realm }); - adminClient.baseUrl = environment.authUrl; + adminClient.baseUrl = environment.authServerUrl; adminClient.registerTokenProvider({ async getAccessToken() { try { diff --git a/js/apps/admin-ui/src/environment.ts b/js/apps/admin-ui/src/environment.ts new file mode 100644 index 0000000000..36040f40a3 --- /dev/null +++ b/js/apps/admin-ui/src/environment.ts @@ -0,0 +1,33 @@ +import { + getInjectedEnvironment, + type BaseEnvironment, +} from "@keycloak/keycloak-ui-shared"; + +export type Environment = BaseEnvironment & { + /** The URL to the base of the Admin Console. */ + consoleBaseUrl: string; + /** The name of the master realm. */ + masterRealm: string; + /** The version hash of the auth server. */ + resourceVersion: string; +}; + +// During development the realm can be passed as a query parameter when redirecting back from Keycloak. +const realm = + new URLSearchParams(window.location.search).get("realm") || "master"; + +const defaultEnvironment: Environment = { + // Base environment variables + authServerUrl: "http://localhost:8180", + realm: realm, + clientId: "security-admin-console-v2", + resourceUrl: "http://localhost:8080", + logo: "/logo.svg", + logoUrl: "", + // Admin Console specific environment variables + consoleBaseUrl: "/admin/master/console/", + masterRealm: "master", + resourceVersion: "unknown", +}; + +export const environment = getInjectedEnvironment(defaultEnvironment); diff --git a/js/apps/admin-ui/src/i18n/i18n.ts b/js/apps/admin-ui/src/i18n/i18n.ts index bc68e4418f..070bb67d61 100644 --- a/js/apps/admin-ui/src/i18n/i18n.ts +++ b/js/apps/admin-ui/src/i18n/i18n.ts @@ -1,7 +1,8 @@ import { createInstance } from "i18next"; import HttpBackend from "i18next-http-backend"; import { initReactI18next } from "react-i18next"; -import { environment } from "@keycloak/keycloak-ui-shared"; + +import { environment } from "../environment"; import { joinPath } from "../utils/joinPath"; type KeyValue = { key: string; value: string }; diff --git a/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx index 3d58064509..e74651078d 100644 --- a/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx @@ -1,18 +1,19 @@ import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation"; -import { FormGroup, Title } from "@patternfly/react-core"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; import { - AdminEnvironment, FormErrorText, HelpItem, TextControl, useEnvironment, } from "@keycloak/keycloak-ui-shared"; +import { FormGroup, Title } from "@patternfly/react-core"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + import { useAdminClient } from "../../admin-client"; import { FileUploadForm } from "../../components/json-file-upload/FileUploadForm"; import { useRealm } from "../../context/realm-context/RealmContext"; +import type { Environment } from "../../environment"; import { addTrailingSlash } from "../../util"; import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders"; import { DiscoveryEndpointField } from "../component/DiscoveryEndpointField"; @@ -24,7 +25,7 @@ type FormFields = IdentityProviderRepresentation & { export const SamlConnectSettings = () => { const { adminClient } = useAdminClient(); - const { environment } = useEnvironment(); + const { environment } = useEnvironment(); const { t } = useTranslation(); const id = "saml"; diff --git a/js/apps/admin-ui/src/identity-providers/add/SamlGeneralSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/SamlGeneralSettings.tsx index eab5ded67f..a1a25cc86f 100644 --- a/js/apps/admin-ui/src/identity-providers/add/SamlGeneralSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/SamlGeneralSettings.tsx @@ -1,13 +1,15 @@ -import { FormGroup } from "@patternfly/react-core"; -import { useFormContext, useWatch } from "react-hook-form"; -import { useTranslation } from "react-i18next"; import { HelpItem, TextControl, useEnvironment, } from "@keycloak/keycloak-ui-shared"; +import { FormGroup } from "@patternfly/react-core"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + import { FormattedLink } from "../../components/external-link/FormattedLink"; import { useRealm } from "../../context/realm-context/RealmContext"; +import type { Environment } from "../../environment"; import { DisplayOrder } from "../component/DisplayOrder"; import { RedirectUrl } from "../component/RedirectUrl"; @@ -22,7 +24,7 @@ export const SamlGeneralSettings = ({ }: SamlGeneralSettingsProps) => { const { t } = useTranslation(); const { realm } = useRealm(); - const { environment } = useEnvironment(); + const { environment } = useEnvironment(); const { control } = useFormContext(); const alias = useWatch({ control, name: "alias" }); @@ -54,7 +56,7 @@ export const SamlGeneralSettings = ({ > diff --git a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx index 017d0708bd..b3e1a49000 100644 --- a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx +++ b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx @@ -1,7 +1,7 @@ import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata"; -import { AdminEnvironment, useEnvironment } from "@keycloak/keycloak-ui-shared"; +import { useEnvironment } from "@keycloak/keycloak-ui-shared"; import { AlertVariant, ButtonVariant, @@ -16,6 +16,7 @@ import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; + import { useAdminClient } from "../admin-client"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; @@ -29,6 +30,7 @@ import { useRealms } from "../context/RealmsContext"; import { useAccess } from "../context/access/Access"; import { useRealm } from "../context/realm-context/RealmContext"; import { toDashboard } from "../dashboard/routes/Dashboard"; +import type { Environment } from "../environment"; import helpUrls from "../help-urls"; import { convertFormValuesToObject, convertToFormValues } from "../util"; import { getAuthorizationHeaders } from "../utils/getAuthorizationHeaders"; @@ -74,7 +76,7 @@ const RealmSettingsHeader = ({ refresh, }: RealmSettingsHeaderProps) => { const { adminClient } = useAdminClient(); - const { environment } = useEnvironment(); + const { environment } = useEnvironment(); const { t } = useTranslation(); const { refresh: refreshRealms } = useRealms(); const { addAlert, addError } = useAlerts(); diff --git a/js/libs/ui-shared/src/context/KeycloakContext.tsx b/js/libs/ui-shared/src/context/KeycloakContext.tsx index d19660ff41..4ea95bd5b6 100644 --- a/js/libs/ui-shared/src/context/KeycloakContext.tsx +++ b/js/libs/ui-shared/src/context/KeycloakContext.tsx @@ -49,7 +49,7 @@ export const KeycloakProvider = ({ const [error, setError] = useState(); const keycloak = useMemo(() => { const keycloak = new Keycloak({ - url: environment.authUrl, + url: environment.authServerUrl, realm: environment.realm, clientId: environment.clientId, }); diff --git a/js/libs/ui-shared/src/context/environment.ts b/js/libs/ui-shared/src/context/environment.ts index d5d726ea2e..62e8fed7bc 100644 --- a/js/libs/ui-shared/src/context/environment.ts +++ b/js/libs/ui-shared/src/context/environment.ts @@ -1,109 +1,33 @@ -export const DEFAULT_REALM = "master"; - -export type Feature = { - isRegistrationEmailAsUsername: boolean; - isEditUserNameAllowed: boolean; - isInternationalizationEnabled: boolean; - isLinkedAccountsEnabled: boolean; - isEventsEnabled: boolean; - isMyResourcesEnabled: boolean; - isTotpConfigured: boolean; - deleteAccountAllowed: boolean; - updateEmailFeatureEnabled: boolean; - updateEmailActionEnabled: boolean; - isViewGroupsEnabled: boolean; - isOid4VciEnabled: boolean; -}; - +/** The base environment variables that are shared between the Admin and Account Consoles. */ export type BaseEnvironment = { - /** The URL to the root of the auth server. */ - authUrl: string; - /** The URL to the root of the account console. */ - baseUrl: string; - /** The realm used to authenticate the user to the Account Console. */ + /** + * The URL to the root of the Keycloak server, this is **NOT** always equivalent to the URL of the Admin Console. + * For example, the Keycloak server could be hosted on `auth.example.com` and Admin Console may be hosted on `admin.example.com`. + * + * @see {@link https://www.keycloak.org/server/hostname#_administration_console} + */ + authServerUrl: string; + /** The identifier of the realm used to authenticate the user. */ realm: string; - /** The identifier of the client used to authenticate the user to the Account Console. */ + /** The identifier of the client used to authenticate the user. */ clientId: string; - /** The URL to resources such as the files in the `public` directory. */ + /** The base URL of the resources. */ resourceUrl: string; - /** Indicates the src for the Brand image */ + /** The source URL for the the logo image. */ logo: string; - /** Indicates the url to be followed when Brand image is clicked */ + /** The URL to be followed when the logo is clicked. */ logoUrl: string; }; -export type AdminEnvironment = BaseEnvironment & { - /** The URL to the root of the auth server. */ - authServerUrl: string; - /** The name of the master realm. */ - masterRealm: string; - /** The URL to the base of the Admin UI. */ - consoleBaseUrl: string; - /** The version hash of the auth server. */ - resourceVersion: string; -}; - -export type AccountEnvironment = BaseEnvironment & { - /** The locale of the user */ - locale: string; - /** Feature flags */ - features: Feature; - /** Name of the referrer application in the back link */ - referrerName?: string; - /** UR to the referrer application in the back link */ - referrerUrl?: string; -}; - -// During development the realm can be passed as a query parameter when redirecting back from Keycloak. -const realm = - new URLSearchParams(window.location.search).get("realm") || - location.pathname.match("/realms/(.*?)/account")?.[1]; - -const defaultEnvironment: AdminEnvironment & AccountEnvironment = { - authUrl: "http://localhost:8180", - authServerUrl: "http://localhost:8180", - baseUrl: `http://localhost:8180/realms/${realm ?? DEFAULT_REALM}/account/`, - realm: realm ?? DEFAULT_REALM, - clientId: "security-admin-console-v2", - resourceUrl: "http://localhost:8080", - logo: "/logo.svg", - logoUrl: "/", - locale: "en", - consoleBaseUrl: "/admin/master/console/", - masterRealm: "master", - resourceVersion: "unknown", - features: { - isRegistrationEmailAsUsername: false, - isEditUserNameAllowed: true, - isInternationalizationEnabled: true, - isLinkedAccountsEnabled: true, - isEventsEnabled: true, - isMyResourcesEnabled: true, - isTotpConfigured: true, - deleteAccountAllowed: true, - updateEmailFeatureEnabled: true, - updateEmailActionEnabled: true, - isViewGroupsEnabled: true, - isOid4VciEnabled: false, - }, -}; - -// Merge the default and injected environment variables together. -const environment = { - ...defaultEnvironment, - ...getInjectedEnvironment(), -}; - -export { environment }; - /** - * Extracts the environment variables that are passed if the application is running as a Keycloak theme. + * Extracts the environment variables that are passed if the application is running as a Keycloak theme and combines them with the provided defaults. * These variables are injected by Keycloak into the `index.ftl` as a script tag, the contents of which can be parsed as JSON. + * + * @argument defaults - The default values to fall to if a value is not present in the environment. */ -function getInjectedEnvironment(): Record { +export function getInjectedEnvironment(defaults: T): T { const element = document.getElementById("environment"); - - let env = {} as Record; + let env = {} as T; // Attempt to parse the contents as JSON and return its value. try { @@ -115,6 +39,6 @@ function getInjectedEnvironment(): Record { console.error("Unable to parse environment variables."); } - // Otherwise, return an empty record. - return env; + // Return the merged environment variables with the defaults. + return { ...defaults, ...env }; } diff --git a/js/libs/ui-shared/src/main.ts b/js/libs/ui-shared/src/main.ts index ef47fd6809..ec8d993af3 100644 --- a/js/libs/ui-shared/src/main.ts +++ b/js/libs/ui-shared/src/main.ts @@ -7,10 +7,8 @@ export { type KeycloakContext, } from "./context/KeycloakContext"; export { - environment, - type AccountEnvironment, - type AdminEnvironment, - type Feature, + getInjectedEnvironment, + type BaseEnvironment, } from "./context/environment"; export { ContinueCancelModal } from "./continue-cancel/ContinueCancelModal"; export { diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java index 618b1c8acd..16e45713d7 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountConsole.java @@ -105,7 +105,10 @@ public class AccountConsole implements AccountResourceProvider { URI adminBaseUri = session.getContext().getUri(UrlType.ADMIN).getBaseUri(); URI authUrl = uriInfo.getBaseUri(); - map.put("authUrl", authUrl.getPath().endsWith("/") ? authUrl : authUrl + "/"); + var authServerUrl = authUrl.getPath().endsWith("/") ? authUrl : authUrl + "/"; + // TODO: The 'authUrl' variable is deprecated and only exists to provide backwards compatibility for older themes, it should be removed in a future version. + map.put("authUrl", authServerUrl); + map.put("authServerUrl", authServerUrl); map.put("baseUrl", accountBaseUrl.getPath().endsWith("/") ? accountBaseUrl : accountBaseUrl + "/"); map.put("realm", realm); map.put("clientId", Constants.ACCOUNT_CONSOLE_CLIENT_ID); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 1c4a0e3a23..0a44e244d8 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -346,6 +346,7 @@ public class AdminConsole { } map.put("authServerUrl", authServerBaseUrl); + // TODO: The 'authUrl' variable is deprecated and only exists to provide backwards compatibility for older themes, it should be removed in a future version. map.put("authUrl", adminBaseUrl); map.put("consoleBaseUrl", Urls.adminConsoleRoot(adminBaseUri, realm.getName()).getPath()); map.put("resourceUrl", Urls.themeRoot(adminBaseUri).getPath() + "/admin/" + theme.getName());