added acr loa map field to realm settings (#2736)
* added acr loa map field to realm settings fixes: #2668 * changed to use one form per tab, instead of shared * fixed test
This commit is contained in:
parent
f775fd6329
commit
afbbdaaf52
14 changed files with 620 additions and 617 deletions
|
@ -237,7 +237,6 @@ describe("Realm settings events tab tests", () => {
|
|||
sidebarPage.goToRealmSettings();
|
||||
|
||||
cy.findByTestId("rs-localization-tab").click();
|
||||
|
||||
cy.findByTestId("internationalization-disabled").click({ force: true });
|
||||
|
||||
cy.get(realmSettingsPage.supportedLocalesTypeahead)
|
||||
|
@ -245,10 +244,14 @@ describe("Realm settings events tab tests", () => {
|
|||
.get(".pf-c-select__menu-item")
|
||||
.contains("Dansk")
|
||||
.click();
|
||||
|
||||
cy.get("#kc-l-supported-locales").click();
|
||||
|
||||
cy.intercept("GET", `/admin/realms/${realmName}/localization/en*`).as(
|
||||
"load"
|
||||
);
|
||||
|
||||
cy.findByTestId("localization-tab-save").click();
|
||||
cy.wait("@load");
|
||||
|
||||
addBundle();
|
||||
|
||||
|
|
|
@ -99,25 +99,25 @@
|
|||
"noKeysDescription": "You haven't created any active keys",
|
||||
"certificate": "Certificate",
|
||||
"loginScreenCustomization": "Login screen customization",
|
||||
"userRegistration": "User registration",
|
||||
"registrationAllowed": "User registration",
|
||||
"userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.",
|
||||
"forgotPassword": "Forgot password",
|
||||
"resetPasswordAllowed": "Forgot password",
|
||||
"forgotPasswordHelpText": "Show a link on login page for user to click when they have forgotten their credentials.",
|
||||
"rememberMe": "Remember me",
|
||||
"rememberMeHelpText": "Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.",
|
||||
"emailSettings": "Email settings",
|
||||
"emailAsUsername": "Email as username",
|
||||
"registrationEmailAsUsername": "Email as username",
|
||||
"emailAsUsernameHelpText": "Allow users to set email as username.",
|
||||
"loginWithEmail": "Login with email",
|
||||
"loginWithEmailAllowed": "Login with email",
|
||||
"loginWithEmailHelpText": "Allow users to log in with their email address.",
|
||||
"duplicateEmails": "Duplicate emails",
|
||||
"duplicateEmailsAllowed": "Duplicate emails",
|
||||
"duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.",
|
||||
"provideEmailTitle": "Provide your email address",
|
||||
"provideEmail": "To test connection, you should provide your email address first.",
|
||||
"verifyEmail": "Verify email",
|
||||
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
|
||||
"userInfoSettings": "User info settings",
|
||||
"editUsername": "Edit username",
|
||||
"editUsernameAllowed": "Edit username",
|
||||
"enableSwitchSuccess": "{{switch}} changed successfully",
|
||||
"enableSwitchError": "Could not enable / disable due to {{error}}",
|
||||
"testConnection": "Test connection",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import "@patternfly/patternfly/patternfly.min.css";
|
||||
@import "@patternfly/patternfly/patternfly-addons.css";
|
||||
|
||||
.keycloak__pageheader_brand {
|
||||
img.keycloak__pageheader_brand {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { getBaseUrl } from "../util";
|
||||
import { convertToFormValues, getBaseUrl } from "../util";
|
||||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
|
@ -24,25 +24,28 @@ import { FormAccess } from "../components/form-access/FormAccess";
|
|||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { FormattedLink } from "../components/external-link/FormattedLink";
|
||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { KeyValueInput } from "../components/key-value-form/KeyValueInput";
|
||||
|
||||
type RealmSettingsGeneralTabProps = {
|
||||
realm: RealmRepresentation;
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const RealmSettingsGeneralTab = ({
|
||||
realm,
|
||||
save,
|
||||
reset,
|
||||
}: RealmSettingsGeneralTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const { realm: realmName } = useRealm();
|
||||
const form = useForm<RealmRepresentation>({ shouldUnregister: false });
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { isDirty },
|
||||
} = useFormContext();
|
||||
} = form;
|
||||
const isFeatureEnabled = useIsFeatureEnabled();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
|
@ -50,6 +53,19 @@ export const RealmSettingsGeneralTab = ({
|
|||
|
||||
const requireSslTypes = ["all", "external", "none"];
|
||||
|
||||
const setupForm = () => {
|
||||
convertToFormValues(realm, setValue);
|
||||
if (realm.attributes?.["acr.loa.map"]) {
|
||||
const result = Object.entries(
|
||||
JSON.parse(realm.attributes["acr.loa.map"])
|
||||
).flatMap(([key, value]) => ({ key, value }));
|
||||
result.concat({ key: "", value: "" });
|
||||
setValue("attributes.acr.loa.map", result);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(setupForm, []);
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
|
@ -143,6 +159,20 @@ export const RealmSettingsGeneralTab = ({
|
|||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("clients:acrToLoAMapping")}
|
||||
fieldId="acrToLoAMapping"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:acrToLoAMapping"
|
||||
fieldLabelId="clients:acrToLoAMapping"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
<KeyValueInput name="attributes.acr.loa.map" />
|
||||
</FormProvider>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("userManagedAccess")}
|
||||
|
@ -237,7 +267,7 @@ export const RealmSettingsGeneralTab = ({
|
|||
<Button
|
||||
data-testid="general-tab-revert"
|
||||
variant="link"
|
||||
onClick={reset}
|
||||
onClick={setupForm}
|
||||
>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cloneDeep, isEqual, uniqWith } from "lodash-es";
|
||||
import { Controller, useForm, useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
|
@ -49,10 +49,10 @@ import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTa
|
|||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
import { convertToFormValues } from "../util";
|
||||
|
||||
type LocalizationTabProps = {
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
refresh: () => void;
|
||||
realm: RealmRepresentation;
|
||||
};
|
||||
|
@ -67,11 +67,7 @@ export type BundleForm = {
|
|||
messageBundle: KeyValueType;
|
||||
};
|
||||
|
||||
export const LocalizationTab = ({
|
||||
save,
|
||||
reset,
|
||||
realm,
|
||||
}: LocalizationTabProps) => {
|
||||
export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] =
|
||||
|
@ -82,7 +78,9 @@ export const LocalizationTab = ({
|
|||
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
|
||||
const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE);
|
||||
|
||||
const { getValues, control, handleSubmit, formState } = useFormContext();
|
||||
const { setValue, getValues, control, handleSubmit, formState } = useForm({
|
||||
shouldUnregister: false,
|
||||
});
|
||||
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
|
||||
const [messageBundles, setMessageBundles] = useState<[string, string][]>([]);
|
||||
const [tableRows, setTableRows] = useState<IRow[]>([]);
|
||||
|
@ -93,6 +91,15 @@ export const LocalizationTab = ({
|
|||
const { realm: currentRealm } = useRealm();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
|
||||
const setupForm = () => {
|
||||
convertToFormValues(realm, setValue);
|
||||
if (realm.supportedLocales?.length === 0) {
|
||||
setValue("supportedLocales", [DEFAULT_LOCALE]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(setupForm, []);
|
||||
|
||||
const watchSupportedLocales = useWatch<string[]>({
|
||||
control,
|
||||
name: "supportedLocales",
|
||||
|
@ -475,7 +482,7 @@ export const LocalizationTab = ({
|
|||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
|
|
|
@ -5,7 +5,6 @@ import { FormAccess } from "../components/form-access/FormAccess";
|
|||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
|
@ -15,26 +14,31 @@ type RealmSettingsLoginTabProps = {
|
|||
refresh: () => void;
|
||||
};
|
||||
|
||||
type SwitchType = { [K in keyof RealmRepresentation]: boolean };
|
||||
|
||||
export const RealmSettingsLoginTab = ({
|
||||
realm,
|
||||
refresh,
|
||||
}: RealmSettingsLoginTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
|
||||
const form = useForm<RealmRepresentation>({ mode: "onChange" });
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const adminClient = useAdminClient();
|
||||
const { realm: realmName } = useRealm();
|
||||
|
||||
const updateSwitchValue = async (name: string) => {
|
||||
const switchValues = form.getValues();
|
||||
const updateSwitchValue = async (switches: SwitchType | SwitchType[]) => {
|
||||
const name = Array.isArray(switches)
|
||||
? Object.keys(switches[0])[0]
|
||||
: Object.keys(switches)[0];
|
||||
|
||||
try {
|
||||
await adminClient.realms.update(
|
||||
{
|
||||
realm: realmName,
|
||||
},
|
||||
switchValues
|
||||
Array.isArray(switches)
|
||||
? switches.reduce((realm, s) => Object.assign(realm, s), realm)
|
||||
: Object.assign(realm, switches)
|
||||
);
|
||||
addAlert(t("enableSwitchSuccess", { switch: t(name) }));
|
||||
refresh();
|
||||
|
@ -51,66 +55,50 @@ export const RealmSettingsLoginTab = ({
|
|||
>
|
||||
<FormAccess isHorizontal role="manage-realm">
|
||||
<FormGroup
|
||||
label={t("userRegistration")}
|
||||
label={t("registrationAllowed")}
|
||||
fieldId="kc-user-reg"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("userRegistrationHelpText")}
|
||||
fieldLabelId="realm-settings:userRegistration"
|
||||
fieldLabelId="realm-settings:registrationAllowed"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="registrationAllowed"
|
||||
defaultValue={realm.registrationAllowed}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-user-reg-switch"
|
||||
data-testid="user-reg-switch"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("userRegistration");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-user-reg-switch"
|
||||
data-testid="user-reg-switch"
|
||||
value={realm.registrationAllowed ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.registrationAllowed}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({ registrationAllowed: value });
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("forgotPassword")}
|
||||
label={t("resetPasswordAllowed")}
|
||||
fieldId="kc-forgot-pw"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings:forgotPasswordHelpText"
|
||||
fieldLabelId="realm-settings:forgotPassword"
|
||||
fieldLabelId="realm-settings:resetPasswordAllowed"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
<Switch
|
||||
id="kc-forgot-pw-switch"
|
||||
data-testid="forgot-pw-switch"
|
||||
name="resetPasswordAllowed"
|
||||
defaultValue={realm.resetPasswordAllowed}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-forgot-pw-switch"
|
||||
data-testid="forgot-pw-switch"
|
||||
name="resetPasswordAllowed"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("forgotPassword");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
value={realm.resetPasswordAllowed ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.resetPasswordAllowed}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({ resetPasswordAllowed: value });
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -124,24 +112,16 @@ export const RealmSettingsLoginTab = ({
|
|||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="rememberMe"
|
||||
defaultValue={realm.rememberMe}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-remember-me-switch"
|
||||
data-testid="remember-me-switch"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("rememberMe");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-remember-me-switch"
|
||||
data-testid="remember-me-switch"
|
||||
value={realm.rememberMe ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.rememberMe}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({ rememberMe: value });
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
|
@ -149,103 +129,92 @@ export const RealmSettingsLoginTab = ({
|
|||
<FormPanel className="kc-email-settings" title={t("emailSettings")}>
|
||||
<FormAccess isHorizontal role="manage-realm">
|
||||
<FormGroup
|
||||
label={t("emailAsUsername")}
|
||||
label={t("registrationEmailAsUsername")}
|
||||
fieldId="kc-email-as-username"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings:emailAsUsernameHelpText"
|
||||
fieldLabelId="realm-settings:emailAsUsername"
|
||||
fieldLabelId="realm-settings:registrationEmailAsUsername"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="registrationEmailAsUsername"
|
||||
defaultValue={realm.registrationEmailAsUsername}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-email-as-username-switch"
|
||||
data-testid="email-as-username-switch"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("emailAsUsername");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-email-as-username-switch"
|
||||
data-testid="email-as-username-switch"
|
||||
value={realm.registrationEmailAsUsername ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.registrationEmailAsUsername}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue([
|
||||
{
|
||||
registrationEmailAsUsername: value,
|
||||
},
|
||||
{
|
||||
duplicateEmailsAllowed: false,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("loginWithEmail")}
|
||||
label={t("loginWithEmailAllowed")}
|
||||
fieldId="kc-login-with-email"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings:loginWithEmailHelpText"
|
||||
fieldLabelId="realm-settings:loginWithEmail"
|
||||
fieldLabelId="realm-settings:loginWithEmailAllowed"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="loginWithEmailAllowed"
|
||||
defaultValue={realm.loginWithEmailAllowed}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-login-with-email-switch"
|
||||
data-testid="login-with-email-switch"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("loginWithEmail");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-login-with-email-switch"
|
||||
data-testid="login-with-email-switch"
|
||||
value={realm.loginWithEmailAllowed ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.loginWithEmailAllowed}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue([
|
||||
{
|
||||
loginWithEmailAllowed: value,
|
||||
},
|
||||
{ duplicateEmailsAllowed: false },
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("duplicateEmails")}
|
||||
label={t("duplicateEmailsAllowed")}
|
||||
fieldId="kc-duplicate-emails"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings:duplicateEmailsHelpText"
|
||||
fieldLabelId="realm-settings:duplicateEmails"
|
||||
fieldLabelId="realm-settings:duplicateEmailsAllowed"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="duplicateEmailsAllowed"
|
||||
defaultValue={realm.duplicateEmailsAllowed}
|
||||
control={form.control}
|
||||
render={({ onChange }) => (
|
||||
<Switch
|
||||
id="kc-duplicate-emails-switch"
|
||||
data-testid="duplicate-emails-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={
|
||||
form.getValues().duplicateEmailsAllowed &&
|
||||
!form.getValues().loginWithEmailAllowed &&
|
||||
!form.getValues().registrationEmailAsUsername
|
||||
}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("duplicateEmails");
|
||||
}}
|
||||
isDisabled={
|
||||
form.getValues().loginWithEmailAllowed ||
|
||||
form.getValues().registrationEmailAsUsername
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-duplicate-emails-switch"
|
||||
data-testid="duplicate-emails-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={
|
||||
realm.duplicateEmailsAllowed ||
|
||||
realm.loginWithEmailAllowed ||
|
||||
realm.registrationEmailAsUsername
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({
|
||||
duplicateEmailsAllowed: value,
|
||||
});
|
||||
}}
|
||||
isDisabled={
|
||||
realm.loginWithEmailAllowed || realm.registrationEmailAsUsername
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -259,25 +228,17 @@ export const RealmSettingsLoginTab = ({
|
|||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
<Switch
|
||||
id="kc-verify-email-switch"
|
||||
data-testid="verify-email-switch"
|
||||
name="verifyEmail"
|
||||
defaultValue={realm.verifyEmail}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-verify-email-switch"
|
||||
data-testid="verify-email-switch"
|
||||
name="verifyEmail"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("verifyEmail");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
value={realm.verifyEmail ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.verifyEmail}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({ verifyEmail: value });
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
|
@ -288,34 +249,26 @@ export const RealmSettingsLoginTab = ({
|
|||
>
|
||||
<FormAccess isHorizontal role="manage-realm">
|
||||
<FormGroup
|
||||
label={t("editUsername")}
|
||||
label={t("editUsernameAllowed")}
|
||||
fieldId="kc-edit-username"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:editUsername"
|
||||
fieldLabelId="realm-settings:editUsername"
|
||||
fieldLabelId="realm-settings:editUsernameAllowed"
|
||||
/>
|
||||
}
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="editUsernameAllowed"
|
||||
defaultValue={realm.editUsernameAllowed}
|
||||
control={form.control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-edit-username-switch"
|
||||
data-testid="edit-username-switch"
|
||||
value={value ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
updateSwitchValue("userInfoSettings");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
id="kc-edit-username-switch"
|
||||
data-testid="edit-username-switch"
|
||||
value={realm.editUsernameAllowed ? "on" : "off"}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={realm.editUsernameAllowed}
|
||||
onChange={(value) => {
|
||||
updateSwitchValue({ editUsernameAllowed: value });
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import type { RealmSettingsParams } from "./routes/RealmSettings";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { RealmSettingsTabs } from "./RealmSettingsTabs";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export default function RealmSettingsSection() {
|
||||
const adminClient = useAdminClient();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { realm: realmName } = useParams<RealmSettingsParams>();
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
const refresh = () => {
|
||||
setKey(key + 1);
|
||||
setRealm(undefined);
|
||||
};
|
||||
|
||||
useFetch(() => adminClient.realms.findOne({ realm: realmName }), setRealm, [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
AlertVariant,
|
||||
|
@ -34,7 +34,7 @@ import { RealmSettingsEmailTab } from "./EmailTab";
|
|||
import { EventsTab } from "./event-config/EventsTab";
|
||||
import { RealmSettingsGeneralTab } from "./GeneralTab";
|
||||
import { RealmSettingsLoginTab } from "./LoginTab";
|
||||
import { SecurityDefences } from "./security-defences/SecurityDefences";
|
||||
import { SecurityDefenses } from "./security-defences/SecurityDefenses";
|
||||
import { RealmSettingsSessionsTab } from "./SessionsTab";
|
||||
import { RealmSettingsThemesTab } from "./ThemesTab";
|
||||
import { RealmSettingsTokensTab } from "./TokensTab";
|
||||
|
@ -53,7 +53,7 @@ import { UserProfileTab } from "./user-profile/UserProfileTab";
|
|||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||
import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies";
|
||||
import { KeysTab } from "./keys/KeysTab";
|
||||
import { DEFAULT_LOCALE } from "../i18n";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
|
||||
type RealmSettingsHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -176,55 +176,59 @@ export const RealmSettingsTabs = ({
|
|||
const history = useHistory();
|
||||
const isFeatureEnabled = useIsFeatureEnabled();
|
||||
|
||||
const form = useForm({ mode: "onChange", shouldUnregister: false });
|
||||
const { control, getValues, setValue, reset: resetForm } = form;
|
||||
|
||||
const { control, setValue, getValues } = useForm({
|
||||
mode: "onChange",
|
||||
shouldUnregister: false,
|
||||
});
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
const refreshHeader = () => {
|
||||
setKey(new Date().getTime());
|
||||
setKey(key + 1);
|
||||
};
|
||||
|
||||
const setupForm = (r: RealmRepresentation = realm) => {
|
||||
convertToFormValues(r, setValue);
|
||||
if (r.supportedLocales?.length === 0) {
|
||||
setValue("supportedLocales", [DEFAULT_LOCALE]);
|
||||
}
|
||||
resetForm(getValues());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setupForm();
|
||||
}, []);
|
||||
useEffect(setupForm, []);
|
||||
|
||||
const save = async (r: RealmRepresentation) => {
|
||||
r = convertFormValuesToObject(r);
|
||||
if (
|
||||
r.attributes?.["acr.loa.map"] &&
|
||||
typeof r.attributes["acr.loa.map"] !== "string"
|
||||
) {
|
||||
const map = JSON.stringify(
|
||||
Object.fromEntries(
|
||||
(r.attributes["acr.loa.map"] as KeyValueType[])
|
||||
.filter(({ key }) => key !== "")
|
||||
.map(({ key, value }) => [key, value])
|
||||
)
|
||||
);
|
||||
r.attributes["acr.loa.map"] = map !== "{}" ? map : "[]";
|
||||
}
|
||||
|
||||
const save = async (realm: RealmRepresentation) => {
|
||||
try {
|
||||
realm = convertFormValuesToObject(realm);
|
||||
|
||||
await adminClient.realms.update(
|
||||
{ realm: realmName },
|
||||
{
|
||||
...realm,
|
||||
id: realmName,
|
||||
...r,
|
||||
id: r.realm,
|
||||
}
|
||||
);
|
||||
setupForm(realm);
|
||||
const isRealmRenamed = realmName !== realm.realm;
|
||||
if (isRealmRenamed) {
|
||||
await refreshRealms();
|
||||
history.push(toRealmSettings({ realm: realm.realm!, tab: "general" }));
|
||||
}
|
||||
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("realm-settings:saveError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const userProfileEnabled = useWatch({
|
||||
control,
|
||||
name: "attributes.userProfileEnabled",
|
||||
defaultValue: "false",
|
||||
});
|
||||
const isRealmRenamed = realmName !== (r.realm || realm.realm);
|
||||
if (isRealmRenamed) {
|
||||
await refreshRealms();
|
||||
history.push(toRealmSettings({ realm: r.realm!, tab: "general" }));
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
|
||||
const route = (tab: RealmSettingsTab | undefined = "general") =>
|
||||
routableTab({
|
||||
|
@ -258,181 +262,162 @@ export const RealmSettingsTabs = ({
|
|||
)}
|
||||
/>
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
<FormProvider {...form}>
|
||||
<RoutableTabs
|
||||
isBox
|
||||
mountOnEnter
|
||||
defaultLocation={toRealmSettings({
|
||||
realm: realmName,
|
||||
tab: "general",
|
||||
})}
|
||||
<RoutableTabs
|
||||
isBox
|
||||
mountOnEnter
|
||||
defaultLocation={toRealmSettings({
|
||||
realm: realmName,
|
||||
tab: "general",
|
||||
})}
|
||||
>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("general")}</TabTitleText>}
|
||||
data-testid="rs-general-tab"
|
||||
{...route()}
|
||||
>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("general")}</TabTitleText>}
|
||||
data-testid="rs-general-tab"
|
||||
{...route()}
|
||||
<RealmSettingsGeneralTab realm={realm} save={save} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("login")}</TabTitleText>}
|
||||
data-testid="rs-login-tab"
|
||||
{...route("login")}
|
||||
>
|
||||
<RealmSettingsLoginTab refresh={refresh} realm={realm} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("email")}</TabTitleText>}
|
||||
data-testid="rs-email-tab"
|
||||
{...route("email")}
|
||||
>
|
||||
<RealmSettingsEmailTab realm={realm} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("themes")}</TabTitleText>}
|
||||
data-testid="rs-themes-tab"
|
||||
{...route("themes")}
|
||||
>
|
||||
<RealmSettingsThemesTab realm={realm} save={save} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
|
||||
data-testid="rs-keys-tab"
|
||||
{...route("keys")}
|
||||
>
|
||||
<KeysTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("events")}</TabTitleText>}
|
||||
data-testid="rs-realm-events-tab"
|
||||
{...route("events")}
|
||||
>
|
||||
<EventsTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("localization")}</TabTitleText>}
|
||||
data-testid="rs-localization-tab"
|
||||
{...route("localization")}
|
||||
>
|
||||
<LocalizationTab
|
||||
key={key}
|
||||
refresh={refresh}
|
||||
save={save}
|
||||
realm={realm}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
|
||||
data-testid="rs-security-defenses-tab"
|
||||
{...route("securityDefences")}
|
||||
>
|
||||
<SecurityDefenses realm={realm} save={save} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>}
|
||||
data-testid="rs-sessions-tab"
|
||||
{...route("sessions")}
|
||||
>
|
||||
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
|
||||
data-testid="rs-tokens-tab"
|
||||
{...route("tokens")}
|
||||
>
|
||||
<RealmSettingsTokensTab save={save} realm={realm} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={
|
||||
<TabTitleText>{t("realm-settings:clientPolicies")}</TabTitleText>
|
||||
}
|
||||
data-testid="rs-clientPolicies-tab"
|
||||
{...route("clientPolicies")}
|
||||
>
|
||||
<RoutableTabs
|
||||
mountOnEnter
|
||||
defaultLocation={toClientPolicies({
|
||||
realm: realmName,
|
||||
tab: "profiles",
|
||||
})}
|
||||
>
|
||||
<RealmSettingsGeneralTab
|
||||
save={save}
|
||||
reset={() => resetForm(realm)}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("login")}</TabTitleText>}
|
||||
data-testid="rs-login-tab"
|
||||
{...route("login")}
|
||||
>
|
||||
<RealmSettingsLoginTab refresh={refresh} realm={realm} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("email")}</TabTitleText>}
|
||||
data-testid="rs-email-tab"
|
||||
{...route("email")}
|
||||
>
|
||||
<RealmSettingsEmailTab realm={realm} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("themes")}</TabTitleText>}
|
||||
data-testid="rs-themes-tab"
|
||||
{...route("themes")}
|
||||
>
|
||||
<RealmSettingsThemesTab
|
||||
save={save}
|
||||
reset={() => resetForm(realm)}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
|
||||
data-testid="rs-keys-tab"
|
||||
{...route("keys")}
|
||||
>
|
||||
<KeysTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("events")}</TabTitleText>}
|
||||
data-testid="rs-realm-events-tab"
|
||||
{...route("events")}
|
||||
>
|
||||
<EventsTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("localization")}</TabTitleText>}
|
||||
data-testid="rs-localization-tab"
|
||||
{...route("localization")}
|
||||
>
|
||||
<LocalizationTab
|
||||
key={key}
|
||||
refresh={refresh}
|
||||
save={save}
|
||||
reset={() => resetForm(realm)}
|
||||
realm={realm}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
|
||||
data-testid="rs-security-defenses-tab"
|
||||
{...route("securityDefences")}
|
||||
>
|
||||
<SecurityDefences save={save} reset={() => resetForm(realm)} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={
|
||||
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
|
||||
}
|
||||
data-testid="rs-sessions-tab"
|
||||
{...route("sessions")}
|
||||
>
|
||||
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
|
||||
data-testid="rs-tokens-tab"
|
||||
{...route("tokens")}
|
||||
>
|
||||
<RealmSettingsTokensTab
|
||||
save={save}
|
||||
realm={realm}
|
||||
reset={() => resetForm(realm)}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("realm-settings:clientPolicies")}
|
||||
</TabTitleText>
|
||||
}
|
||||
data-testid="rs-clientPolicies-tab"
|
||||
{...route("clientPolicies")}
|
||||
>
|
||||
<RoutableTabs
|
||||
mountOnEnter
|
||||
defaultLocation={toClientPolicies({
|
||||
realm: realmName,
|
||||
tab: "profiles",
|
||||
})}
|
||||
<Tab
|
||||
data-testid="rs-policies-clientProfiles-tab"
|
||||
aria-label={t("clientProfilesSubTab")}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("profiles")}
|
||||
<span className="kc-help-text">
|
||||
<HelpItem
|
||||
helpText="realm-settings:clientPoliciesProfilesHelpText"
|
||||
fieldLabelId="realm-settings:clientPoliciesProfiles"
|
||||
/>
|
||||
</span>
|
||||
</TabTitleText>
|
||||
}
|
||||
{...policiesRoute("profiles")}
|
||||
>
|
||||
<Tab
|
||||
data-testid="rs-policies-clientProfiles-tab"
|
||||
aria-label={t("clientProfilesSubTab")}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("profiles")}
|
||||
<span className="kc-help-text">
|
||||
<HelpItem
|
||||
helpText="realm-settings:clientPoliciesProfilesHelpText"
|
||||
fieldLabelId="realm-settings:clientPoliciesProfiles"
|
||||
/>
|
||||
</span>
|
||||
</TabTitleText>
|
||||
}
|
||||
{...policiesRoute("profiles")}
|
||||
>
|
||||
<ProfilesTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="policies"
|
||||
data-testid="rs-policies-clientPolicies-tab"
|
||||
aria-label={t("clientPoliciesSubTab")}
|
||||
{...policiesRoute("policies")}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("policies")}
|
||||
<span className="kc-help-text">
|
||||
<HelpItem
|
||||
helpText="realm-settings:clientPoliciesPoliciesHelpText"
|
||||
fieldLabelId="realm-settings:clientPoliciesPolicies"
|
||||
/>
|
||||
</span>
|
||||
</TabTitleText>
|
||||
}
|
||||
>
|
||||
<PoliciesTab />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
</Tab>
|
||||
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
|
||||
userProfileEnabled === "true" && (
|
||||
<Tab
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("realm-settings:userProfile")}
|
||||
</TabTitleText>
|
||||
}
|
||||
data-testid="rs-user-profile-tab"
|
||||
{...route("userProfile")}
|
||||
>
|
||||
<UserProfileTab />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
|
||||
data-testid="rs-userRegistration-tab"
|
||||
{...route("userRegistration")}
|
||||
>
|
||||
<UserRegistration />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
</FormProvider>
|
||||
<ProfilesTab />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="policies"
|
||||
data-testid="rs-policies-clientPolicies-tab"
|
||||
aria-label={t("clientPoliciesSubTab")}
|
||||
{...policiesRoute("policies")}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("policies")}
|
||||
<span className="kc-help-text">
|
||||
<HelpItem
|
||||
helpText="realm-settings:clientPoliciesPoliciesHelpText"
|
||||
fieldLabelId="realm-settings:clientPoliciesPolicies"
|
||||
/>
|
||||
</span>
|
||||
</TabTitleText>
|
||||
}
|
||||
>
|
||||
<PoliciesTab />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
</Tab>
|
||||
{isFeatureEnabled(Feature.DeclarativeUserProfile) &&
|
||||
realm.attributes?.userProfileEnabled === "true" && (
|
||||
<Tab
|
||||
title={
|
||||
<TabTitleText>{t("realm-settings:userProfile")}</TabTitleText>
|
||||
}
|
||||
data-testid="rs-user-profile-tab"
|
||||
{...route("userProfile")}
|
||||
>
|
||||
<UserProfileTab />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
|
||||
data-testid="rs-userRegistration-tab"
|
||||
{...route("userRegistration")}
|
||||
>
|
||||
<UserRegistration />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -14,6 +14,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
|
|||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||
import { TimeSelector } from "../components/time-selector/TimeSelector";
|
||||
import { convertToFormValues } from "../util";
|
||||
|
||||
import "./realm-settings-section.css";
|
||||
|
||||
|
@ -28,24 +29,22 @@ export const RealmSettingsSessionsTab = ({
|
|||
}: RealmSettingsSessionsTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset: resetForm,
|
||||
formState,
|
||||
} = useFormContext<RealmRepresentation>();
|
||||
const { setValue, control, handleSubmit, formState } =
|
||||
useForm<RealmRepresentation>({
|
||||
shouldUnregister: false,
|
||||
});
|
||||
|
||||
const offlineSessionMaxEnabled = useWatch({
|
||||
control,
|
||||
name: "offlineSessionMaxLifespanEnabled",
|
||||
});
|
||||
|
||||
const reset = () => {
|
||||
if (realm) {
|
||||
resetForm(realm);
|
||||
}
|
||||
const setupForm = () => {
|
||||
convertToFormValues(realm, setValue);
|
||||
};
|
||||
|
||||
useEffect(setupForm, []);
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<FormPanel
|
||||
|
@ -399,7 +398,7 @@ export const RealmSettingsSessionsTab = ({
|
|||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -15,15 +15,16 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
|
|||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { convertToFormValues } from "../util";
|
||||
|
||||
type RealmSettingsThemesTabProps = {
|
||||
realm: RealmRepresentation;
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const RealmSettingsThemesTab = ({
|
||||
realm,
|
||||
save,
|
||||
reset,
|
||||
}: RealmSettingsThemesTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
|
||||
|
@ -32,9 +33,14 @@ export const RealmSettingsThemesTab = ({
|
|||
const [adminConsoleThemeOpen, setAdminConsoleThemeOpen] = useState(false);
|
||||
const [emailThemeOpen, setEmailThemeOpen] = useState(false);
|
||||
|
||||
const { control, handleSubmit } = useFormContext();
|
||||
const { control, handleSubmit, setValue } = useForm<RealmRepresentation>();
|
||||
const themeTypes = useServerInfo().themes!;
|
||||
|
||||
const setupForm = () => {
|
||||
convertToFormValues(realm, setValue);
|
||||
};
|
||||
useEffect(setupForm, []);
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
|
@ -217,7 +223,7 @@ export const RealmSettingsThemesTab = ({
|
|||
<Button variant="primary" type="submit" data-testid="themes-tab-save">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -27,6 +27,7 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
|||
|
||||
import "./realm-settings-section.css";
|
||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
import { convertToFormValues } from "../util";
|
||||
|
||||
type RealmSettingsSessionsTabProps = {
|
||||
realm: RealmRepresentation;
|
||||
|
@ -60,8 +61,8 @@ export const RealmSettingsTokensTab = ({
|
|||
javaKeystoreAlgOptions!
|
||||
);
|
||||
|
||||
const form = useFormContext<RealmRepresentation>();
|
||||
const { control } = form;
|
||||
const form = useForm<RealmRepresentation>({ shouldUnregister: false });
|
||||
const { setValue, control } = form;
|
||||
|
||||
const offlineSessionMaxEnabled = useWatch({
|
||||
control,
|
||||
|
@ -81,6 +82,10 @@ export const RealmSettingsTokensTab = ({
|
|||
defaultValue: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
convertToFormValues(realm, setValue);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<FormPanel
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -13,22 +13,25 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
|
|||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { Time } from "./Time";
|
||||
import { convertToFormValues } from "../../util";
|
||||
|
||||
type BruteForceDetectionProps = {
|
||||
realm: RealmRepresentation;
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const BruteForceDetection = ({
|
||||
realm,
|
||||
save,
|
||||
reset,
|
||||
}: BruteForceDetectionProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const form = useForm({ shouldUnregister: false });
|
||||
const {
|
||||
setValue,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isDirty },
|
||||
} = useFormContext();
|
||||
} = form;
|
||||
|
||||
const enable = useWatch({
|
||||
control,
|
||||
|
@ -40,141 +43,143 @@ export const BruteForceDetection = ({
|
|||
name: "permanentLockout",
|
||||
});
|
||||
|
||||
const setupForm = () => convertToFormValues(realm, setValue);
|
||||
useEffect(setupForm, []);
|
||||
|
||||
return (
|
||||
<FormAccess role="manage-realm" isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<FormGroup
|
||||
label={t("common:enabled")}
|
||||
fieldId="bruteForceProtected"
|
||||
hasNoPaddingTop
|
||||
<FormProvider {...form}>
|
||||
<FormAccess
|
||||
role="manage-realm"
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<Controller
|
||||
name="bruteForceProtected"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="bruteForceProtected"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("failureFactor")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:failureFactor"
|
||||
fieldLabelId="realm-settings:failureFactor"
|
||||
/>
|
||||
}
|
||||
fieldId="failureFactor"
|
||||
style={enable ? {} : { display: "none" }}
|
||||
>
|
||||
<Controller
|
||||
name="failureFactor"
|
||||
defaultValue={0}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ onChange, value }) => (
|
||||
<NumberInput
|
||||
type="text"
|
||||
id="failureFactor"
|
||||
value={value}
|
||||
onPlus={() => onChange(value + 1)}
|
||||
onMinus={() => onChange(value - 1)}
|
||||
onChange={(event) =>
|
||||
onChange(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("permanentLockout")}
|
||||
fieldId="permanentLockout"
|
||||
hasNoPaddingTop
|
||||
style={enable ? {} : { display: "none" }}
|
||||
>
|
||||
<Controller
|
||||
name="permanentLockout"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="permanentLockout"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Time
|
||||
name="waitIncrementSeconds"
|
||||
style={enable && !permanentLockout ? {} : { display: "none" }}
|
||||
/>
|
||||
<Time
|
||||
name="maxFailureWaitSeconds"
|
||||
style={enable && !permanentLockout ? {} : { display: "none" }}
|
||||
/>
|
||||
<Time
|
||||
name="maxDeltaTimeSeconds"
|
||||
style={enable && !permanentLockout ? {} : { display: "none" }}
|
||||
/>
|
||||
|
||||
<FormGroup
|
||||
label={t("quickLoginCheckMilliSeconds")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:quickLoginCheckMilliSeconds"
|
||||
fieldLabelId="realm-settings:quickLoginCheckMilliSeconds"
|
||||
/>
|
||||
}
|
||||
fieldId="quickLoginCheckMilliSeconds"
|
||||
style={enable ? {} : { display: "none" }}
|
||||
>
|
||||
<Controller
|
||||
name="quickLoginCheckMilliSeconds"
|
||||
defaultValue={0}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<NumberInput
|
||||
type="text"
|
||||
id="quickLoginCheckMilliSeconds"
|
||||
value={value}
|
||||
onPlus={() => onChange(value + 1)}
|
||||
onMinus={() => onChange(value - 1)}
|
||||
onChange={(event) =>
|
||||
onChange(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Time
|
||||
name="minimumQuickLoginWaitSeconds"
|
||||
style={enable ? {} : { display: "none" }}
|
||||
/>
|
||||
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="brute-force-tab-save"
|
||||
isDisabled={!isDirty}
|
||||
<FormGroup
|
||||
label={t("common:enabled")}
|
||||
fieldId="bruteForceProtected"
|
||||
hasNoPaddingTop
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
<Controller
|
||||
name="bruteForceProtected"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="bruteForceProtected"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{enable && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("failureFactor")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:failureFactor"
|
||||
fieldLabelId="realm-settings:failureFactor"
|
||||
/>
|
||||
}
|
||||
fieldId="failureFactor"
|
||||
>
|
||||
<Controller
|
||||
name="failureFactor"
|
||||
defaultValue={0}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ onChange, value }) => (
|
||||
<NumberInput
|
||||
type="text"
|
||||
id="failureFactor"
|
||||
value={value}
|
||||
onPlus={() => onChange(value + 1)}
|
||||
onMinus={() => onChange(value - 1)}
|
||||
onChange={(event) =>
|
||||
onChange(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("permanentLockout")}
|
||||
fieldId="permanentLockout"
|
||||
hasNoPaddingTop
|
||||
>
|
||||
<Controller
|
||||
name="permanentLockout"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="permanentLockout"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{!permanentLockout && (
|
||||
<>
|
||||
<Time name="waitIncrementSeconds" />
|
||||
<Time name="maxFailureWaitSeconds" />
|
||||
<Time name="maxDeltaTimeSeconds" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormGroup
|
||||
label={t("quickLoginCheckMilliSeconds")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="realm-settings-help:quickLoginCheckMilliSeconds"
|
||||
fieldLabelId="realm-settings:quickLoginCheckMilliSeconds"
|
||||
/>
|
||||
}
|
||||
fieldId="quickLoginCheckMilliSeconds"
|
||||
>
|
||||
<Controller
|
||||
name="quickLoginCheckMilliSeconds"
|
||||
defaultValue={0}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<NumberInput
|
||||
type="text"
|
||||
id="quickLoginCheckMilliSeconds"
|
||||
value={value}
|
||||
onPlus={() => onChange(value + 1)}
|
||||
onMinus={() => onChange(value - 1)}
|
||||
onChange={(event) =>
|
||||
onChange(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Time name="minimumQuickLoginWaitSeconds" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="brute-force-tab-save"
|
||||
isDisabled={!isDirty}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,75 +1,83 @@
|
|||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { ActionGroup, Button } from "@patternfly/react-core";
|
||||
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpLinkTextInput } from "./HelpLinkTextInput";
|
||||
import { convertToFormValues } from "../../util";
|
||||
|
||||
import "./security-defences.css";
|
||||
|
||||
type HeadersFormProps = {
|
||||
realm: RealmRepresentation;
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const HeadersForm = ({ save, reset }: HeadersFormProps) => {
|
||||
export const HeadersForm = ({ realm, save }: HeadersFormProps) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm();
|
||||
const {
|
||||
setValue,
|
||||
formState: { isDirty },
|
||||
handleSubmit,
|
||||
} = useFormContext();
|
||||
} = form;
|
||||
|
||||
const setupForm = () => convertToFormValues(realm, setValue);
|
||||
useEffect(setupForm, []);
|
||||
|
||||
return (
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="manage-realm"
|
||||
className="keycloak__security-defences__form"
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xFrameOptions"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.contentSecurityPolicy"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.contentSecurityPolicyReportOnly"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xContentTypeOptions"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xRobotsTag"
|
||||
url="https://developers.google.com/search/docs/advanced/robots/robots_meta_tag"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xXSSProtection"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.strictTransportSecurity"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"
|
||||
/>
|
||||
<FormProvider {...form}>
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="manage-realm"
|
||||
className="keycloak__security-defences__form"
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xFrameOptions"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.contentSecurityPolicy"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.contentSecurityPolicyReportOnly"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xContentTypeOptions"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xRobotsTag"
|
||||
url="https://developers.google.com/search/docs/advanced/robots/robots_meta_tag"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.xXSSProtection"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"
|
||||
/>
|
||||
<HelpLinkTextInput
|
||||
fieldName="browserSecurityHeaders.strictTransportSecurity"
|
||||
url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"
|
||||
/>
|
||||
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="headers-form-tab-save"
|
||||
isDisabled={!isDirty}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
data-testid="headers-form-tab-save"
|
||||
isDisabled={!isDirty}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,12 +6,12 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
|
|||
import { HeadersForm } from "./HeadersForm";
|
||||
import { BruteForceDetection } from "./BruteForceDetection";
|
||||
|
||||
type SecurityDefencesProps = {
|
||||
type SecurityDefensesProps = {
|
||||
realm: RealmRepresentation;
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
|
||||
export const SecurityDefenses = ({ realm, save }: SecurityDefensesProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const [activeTab, setActiveTab] = useState(10);
|
||||
return (
|
||||
|
@ -25,7 +25,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
|
|||
title={<TabTitleText>{t("headers")}</TabTitleText>}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
<HeadersForm save={save} reset={reset} />
|
||||
<HeadersForm realm={realm} save={save} />
|
||||
</PageSection>
|
||||
</Tab>
|
||||
<Tab
|
||||
|
@ -34,7 +34,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
|
|||
title={<TabTitleText>{t("bruteForceDetection")}</TabTitleText>}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
<BruteForceDetection save={save} reset={reset} />
|
||||
<BruteForceDetection realm={realm} save={save} />
|
||||
</PageSection>
|
||||
</Tab>
|
||||
</Tabs>
|
Loading…
Reference in a new issue