Align environment variables between consoles (#30125)

* change to make authServerUrl the same as authUrl

fixes: #29641
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* Remove `authUrl` entirely

Signed-off-by: Jon Koops <jonkoops@gmail.com>

* Remove file that is unrelated

Signed-off-by: Jon Koops <jonkoops@gmail.com>

* Split out and align environment variables between consoles

Signed-off-by: Jon Koops <jonkoops@gmail.com>

* Restore removed variables to preserve backwards compatibility

Signed-off-by: Jon Koops <jonkoops@gmail.com>

* Also deprecate the `authUrl` for the Admin Console

Signed-off-by: Jon Koops <jonkoops@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Jon Koops <jonkoops@gmail.com>
Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-06-06 08:36:46 +02:00 committed by GitHub
parent 2576429def
commit 5897334ddb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 242 additions and 176 deletions

View file

@ -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.

View file

@ -22,7 +22,7 @@ import { KeycloakProvider } from "@keycloak/keycloak-account-ui";
//...
<KeycloakProvider environment={{
authUrl: "http://localhost:8080",
authServerUrl: "http://localhost:8080",
realm: "master",
clientId: "security-admin-console"
}}>

View file

@ -149,27 +149,28 @@
<script id="environment" type="application/json">
{
"authUrl": "${authUrl}",
"baseUrl": "${baseUrl}",
"authServerUrl": "${authServerUrl}",
"realm": "${realm.name}",
"clientId": "${clientId}",
"resourceUrl": "${resourceUrl}",
"logo": "${properties.logo!""}",
"logoUrl": "${properties.logoUrl!""}",
"baseUrl": "${baseUrl}",
"locale": "${locale}",
"referrerName": "${referrerName!""}",
"referrerUrl": "${referrer_uri!""}",
"features": {
"isRegistrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
"isEditUserNameAllowed": ${realm.editUsernameAllowed?c},
"isInternationalizationEnabled": ${realm.isInternationalizationEnabled()?c},
"isLinkedAccountsEnabled": ${realm.identityFederationEnabled?c},
"isMyResourcesEnabled": ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},
"deleteAccountAllowed": ${deleteAccountAllowed?c},
"updateEmailFeatureEnabled": ${updateEmailFeatureEnabled?c},
"updateEmailActionEnabled": ${updateEmailActionEnabled?c},
"isViewGroupsEnabled": ${isViewGroupsEnabled?c},
"isOid4VciEnabled": ${isOid4VciEnabled?c}
},
"referrerName": "${referrerName!""}",
"referrerUrl": "${referrer_uri!""}"
"isRegistrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
"isEditUserNameAllowed": ${realm.editUsernameAllowed?c},
"isInternationalizationEnabled": ${realm.isInternationalizationEnabled()?c},
"isLinkedAccountsEnabled": ${realm.identityFederationEnabled?c},
"isMyResourcesEnabled": ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},
"deleteAccountAllowed": ${deleteAccountAllowed?c},
"updateEmailFeatureEnabled": ${updateEmailFeatureEnabled?c},
"updateEmailActionEnabled": ${updateEmailActionEnabled?c},
"isViewGroupsEnabled": ${isViewGroupsEnabled?c},
"isOid4VciEnabled": ${isOid4VciEnabled?c}
}
}
</script>
</body>

View file

@ -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<BaseEnvironment>) {
{},
new URL(
joinPath(
context.environment.authUrl +
context.environment.authServerUrl +
"/realms/" +
context.environment.realm +
"/.well-known/openid-credential-issuer",

View file

@ -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<AccountEnvironment>,
context: KeycloakContext<BaseEnvironment>,
info: UserRepresentation,
): Promise<void> {
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",

View file

@ -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) =>

View file

@ -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);

View file

@ -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) => {

View file

@ -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<AccountEnvironment>();
const context = useEnvironment<Environment>();
const [userProfileMetadata, setUserProfileMetadata] =
useState<UserProfileMetadata>();
const [supportedLocales, setSupportedLocales] = useState<string[]>([]);

View file

@ -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";

View file

@ -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<MenuItem[]>();
const context = useEnvironment<AccountEnvironment>();
const context = useEnvironment<Environment>();
usePromise((signal) => fetchContentJson({ signal, context }), setMenuItems);
return (
@ -86,7 +83,7 @@ function NavMenuItem({ menuItem }: NavMenuItemProps) {
const { t } = useTranslation();
const {
environment: { features },
} = useEnvironment<AccountEnvironment>();
} = useEnvironment<Environment>();
const { pathname } = useLocation();
const isActive = useMemo(
() => matchMenuItem(pathname, menuItem),

View file

@ -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";

View file

@ -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";

View file

@ -124,16 +124,16 @@
<![CDATA[
<script id="environment" type="application/json">
{
"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}"
}
</script>
</body>

View file

@ -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<Environment>();
const [adminClient, setAdminClient] = useState<KeycloakAdminClient>();
useEffect(() => {

View file

@ -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 = () => (
<KeycloakProvider environment={environment}>

View file

@ -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 {

View file

@ -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);

View file

@ -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 };

View file

@ -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<AdminEnvironment>();
const { environment } = useEnvironment<Environment>();
const { t } = useTranslation();
const id = "saml";

View file

@ -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<Environment>();
const { control } = useFormContext();
const alias = useWatch({ control, name: "alias" });
@ -54,7 +56,7 @@ export const SamlGeneralSettings = ({
>
<FormattedLink
title={t("samlEndpointsLabel")}
href={`${environment.authUrl}/realms/${realm}/broker/${alias}/endpoint/descriptor`}
href={`${environment.authServerUrl}/realms/${realm}/broker/${alias}/endpoint/descriptor`}
isInline
/>
</FormGroup>

View file

@ -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<AdminEnvironment>();
const { environment } = useEnvironment<Environment>();
const { t } = useTranslation();
const { refresh: refreshRealms } = useRealms();
const { addAlert, addError } = useAlerts();

View file

@ -49,7 +49,7 @@ export const KeycloakProvider = <T extends BaseEnvironment>({
const [error, setError] = useState<unknown>();
const keycloak = useMemo(() => {
const keycloak = new Keycloak({
url: environment.authUrl,
url: environment.authServerUrl,
realm: environment.realm,
clientId: environment.clientId,
});

View file

@ -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<string, string | number | boolean> {
export function getInjectedEnvironment<T>(defaults: T): T {
const element = document.getElementById("environment");
let env = {} as Record<string, string | number | boolean>;
let env = {} as T;
// Attempt to parse the contents as JSON and return its value.
try {
@ -115,6 +39,6 @@ function getInjectedEnvironment(): Record<string, string | number | boolean> {
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 };
}

View file

@ -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 {

View file

@ -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);

View file

@ -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());