Realm settings(login): adds login tab and tests (#531)
* realm settings tabs wip * realm settings wip * switches * updates to login tab watch * remove controller and call save in onChange * add cypress tests * remove log stmt * fix lint Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * Update src/realm-settings/LoginTab.tsx Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * Remove form * format * lint Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
50f4ca3a46
commit
ba4e576a2d
9 changed files with 619 additions and 184 deletions
40
cypress/integration/realm_settings_test.spec.ts
Normal file
40
cypress/integration/realm_settings_test.spec.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import SidebarPage from "../support/pages/admin_console/SidebarPage";
|
||||||
|
import LoginPage from "../support/pages/LoginPage";
|
||||||
|
import RealmSettingsPage from "../support/pages/admin_console/manage/realm_settings/RealmSettingsPage";
|
||||||
|
import { keycloakBefore } from "../support/util/keycloak_before";
|
||||||
|
|
||||||
|
describe("Realm settings test", () => {
|
||||||
|
const loginPage = new LoginPage();
|
||||||
|
const sidebarPage = new SidebarPage();
|
||||||
|
const realmSettingsPage = new RealmSettingsPage();
|
||||||
|
|
||||||
|
const managedAccessSwitch = "user-managed-access-switch";
|
||||||
|
const userRegSwitch = "user-reg-switch";
|
||||||
|
const forgotPwdSwitch = "forgot-pw-switch";
|
||||||
|
const rememberMeSwitch = "remember-me-switch";
|
||||||
|
const verifyEmailSwitch = "verify-email-switch";
|
||||||
|
|
||||||
|
describe("Realm settings", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
keycloakBefore();
|
||||||
|
loginPage.logIn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Go to general tab", function () {
|
||||||
|
sidebarPage.goToRealmSettings();
|
||||||
|
realmSettingsPage.toggleSwitch(managedAccessSwitch);
|
||||||
|
realmSettingsPage.save();
|
||||||
|
realmSettingsPage.toggleSwitch(managedAccessSwitch);
|
||||||
|
realmSettingsPage.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Go to login tab", function () {
|
||||||
|
sidebarPage.goToRealmSettings();
|
||||||
|
cy.getId("rs-login-tab").click();
|
||||||
|
realmSettingsPage.toggleSwitch(userRegSwitch);
|
||||||
|
realmSettingsPage.toggleSwitch(forgotPwdSwitch);
|
||||||
|
realmSettingsPage.toggleSwitch(rememberMeSwitch);
|
||||||
|
realmSettingsPage.toggleSwitch(verifyEmailSwitch);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,40 @@
|
||||||
|
export default class RealmSettingsPage {
|
||||||
|
saveBtn: string;
|
||||||
|
loginTab: string;
|
||||||
|
managedAccessSwitch: string;
|
||||||
|
userRegSwitch: string;
|
||||||
|
forgotPwdSwitch: string;
|
||||||
|
rememberMeSwitch: string;
|
||||||
|
emailAsUsernameSwitch: string;
|
||||||
|
loginWithEmailSwitch: string;
|
||||||
|
duplicateEmailsSwitch: string;
|
||||||
|
verifyEmailSwitch: string;
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.saveBtn = "general-tab-save";
|
||||||
|
this.loginTab = "rs-login-tab";
|
||||||
|
this.managedAccessSwitch = "user-managed-access-switch";
|
||||||
|
this.userRegSwitch = "user-reg-switch"
|
||||||
|
this.forgotPwdSwitch = "forgot-password-switch"
|
||||||
|
this.rememberMeSwitch = "remember-me-switch"
|
||||||
|
this.emailAsUsernameSwitch = "email-as-username-switch"
|
||||||
|
this.loginWithEmailSwitch = "login-with-email-switch"
|
||||||
|
this.duplicateEmailsSwitch = "duplicate-emails-switch"
|
||||||
|
this.verifyEmailSwitch = "verify-email-switch"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSwitch(switchName: string) {
|
||||||
|
cy.getId(switchName).next().click();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
cy.getId(this.saveBtn).click();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import "./form-panel.css";
|
||||||
|
|
||||||
type FormPanelProps = {
|
type FormPanelProps = {
|
||||||
title: string;
|
title: string;
|
||||||
scrollId: string;
|
scrollId?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
230
src/realm-settings/GeneralTab.tsx
Normal file
230
src/realm-settings/GeneralTab.tsx
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ClipboardCopy,
|
||||||
|
FormGroup,
|
||||||
|
PageSection,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
|
Switch,
|
||||||
|
TextInput,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
|
import { getBaseUrl } from "../util";
|
||||||
|
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
import { FormattedLink } from "../components/external-link/FormattedLink";
|
||||||
|
|
||||||
|
export const RealmSettingsGeneralTab = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const handleError = useErrorHandler();
|
||||||
|
const { realm: realmName } = useRealm();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
const { register, control, setValue, handleSubmit } = useForm();
|
||||||
|
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const baseUrl = getBaseUrl(adminClient);
|
||||||
|
|
||||||
|
const requireSslTypes = ["all", "external", "none"];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return asyncStateFetch(
|
||||||
|
() => adminClient.realms.findOne({ realm: realmName }),
|
||||||
|
(realm) => {
|
||||||
|
setRealm(realm);
|
||||||
|
setupForm(realm);
|
||||||
|
},
|
||||||
|
handleError
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setupForm = (realm: RealmRepresentation) => {
|
||||||
|
Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (realm: RealmRepresentation) => {
|
||||||
|
try {
|
||||||
|
await adminClient.realms.update({ realm: realmName }, realm);
|
||||||
|
setRealm(realm);
|
||||||
|
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("saveError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageSection variant="light">
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
onSubmit={handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup label={t("realmId")} fieldId="kc-realm-id" isRequired>
|
||||||
|
<ClipboardCopy isReadOnly>{realmName}</ClipboardCopy>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup label={t("displayName")} fieldId="kc-display-name">
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="kc-display-name"
|
||||||
|
name="displayName"
|
||||||
|
ref={register}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("htmlDisplayName")}
|
||||||
|
fieldId="kc-html-display-name"
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="kc-html-display-name"
|
||||||
|
name="displayNameHtml"
|
||||||
|
ref={register}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("frontendUrl")}
|
||||||
|
fieldId="kc-frontend-url"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:frontendUrl"
|
||||||
|
forLabel={t("frontendUrl")}
|
||||||
|
forID="kc-frontend-url"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="kc-frontend-url"
|
||||||
|
name="attributes.frontendUrl"
|
||||||
|
ref={register}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("requireSsl")}
|
||||||
|
fieldId="kc-require-ssl"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:requireSsl"
|
||||||
|
forLabel={t("requireSsl")}
|
||||||
|
forID="kc-require-ssl"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="sslRequired"
|
||||||
|
defaultValue="none"
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId="kc-require-ssl"
|
||||||
|
onToggle={() => setOpen(!open)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value as string);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
selections={value}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("requireSsl")}
|
||||||
|
isOpen={open}
|
||||||
|
>
|
||||||
|
{requireSslTypes.map((sslType) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={sslType === value}
|
||||||
|
key={sslType}
|
||||||
|
value={sslType}
|
||||||
|
>
|
||||||
|
{t(`sslType.${sslType}`)}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
hasNoPaddingTop
|
||||||
|
label={t("userManagedAccess")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:userManagedAccess"
|
||||||
|
forLabel={t("userManagedAccess")}
|
||||||
|
forID="kc-user-manged-access"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-user-manged-access"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="userManagedAccessAllowed"
|
||||||
|
control={control}
|
||||||
|
defaultValue={false}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="kc-user-managed-access"
|
||||||
|
data-testid="user-managed-access-switch"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("endpoints")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:endpoints"
|
||||||
|
forLabel={t("endpoints")}
|
||||||
|
forID="kc-endpoints"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-endpoints"
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<StackItem>
|
||||||
|
<FormattedLink
|
||||||
|
href={`${baseUrl}realms/${realmName}/.well-known/openid-configuration`}
|
||||||
|
title={t("openEndpointConfiguration")}
|
||||||
|
/>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<FormattedLink
|
||||||
|
href={`${baseUrl}realms/${realmName}/protocol/saml/descriptor`}
|
||||||
|
title={t("samlIdentityProviderMetadata")}
|
||||||
|
/>
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<ActionGroup>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
data-testid="general-tab-save"
|
||||||
|
>
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={() => setupForm(realm!)}>
|
||||||
|
{t("common:revert")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
240
src/realm-settings/LoginTab.tsx
Normal file
240
src/realm-settings/LoginTab.tsx
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
FormGroup,
|
||||||
|
PageSection,
|
||||||
|
Switch,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||||
|
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient";
|
||||||
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
|
||||||
|
export const RealmSettingsLoginTab = () => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||||
|
const handleError = useErrorHandler();
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm: realmName } = useRealm();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return asyncStateFetch(
|
||||||
|
() => adminClient.realms.findOne({ realm: realmName }),
|
||||||
|
(realm) => {
|
||||||
|
setRealm(realm);
|
||||||
|
},
|
||||||
|
handleError
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const save = async (realm: RealmRepresentation) => {
|
||||||
|
try {
|
||||||
|
await adminClient.realms.update({ realm: realmName }, realm);
|
||||||
|
setRealm(realm);
|
||||||
|
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("saveError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageSection variant="light">
|
||||||
|
<FormPanel title="Login screen customization">
|
||||||
|
{
|
||||||
|
<FormAccess isHorizontal role="manage-realm">
|
||||||
|
<FormGroup
|
||||||
|
label={t("userRegistration")}
|
||||||
|
fieldId="kc-user-reg"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("userRegistrationHelpText")}
|
||||||
|
forLabel={t("userRegistration")}
|
||||||
|
forID="kc-user-reg"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-user-reg"
|
||||||
|
data-testid="user-reg-switch"
|
||||||
|
name="registrationAllowed"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.registrationAllowed}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, registrationAllowed: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("forgotPassword")}
|
||||||
|
fieldId="kc-forgot-pw"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("forgotPasswordHelpText")}
|
||||||
|
forLabel={t("forgotPassword")}
|
||||||
|
forID="kc-forgot-pw"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-forgot-pw"
|
||||||
|
data-testid="forgot-pw-switch"
|
||||||
|
name="resetPasswordAllowed"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.resetPasswordAllowed}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, resetPasswordAllowed: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("rememberMe")}
|
||||||
|
fieldId="kc-remember-me"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("rememberMeHelpText")}
|
||||||
|
forLabel={t("rememberMe")}
|
||||||
|
forID="kc-remember-me"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-remember-me"
|
||||||
|
data-testid="remember-me-switch"
|
||||||
|
name="rememberMe"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.rememberMe}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, rememberMe: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormAccess>
|
||||||
|
}
|
||||||
|
</FormPanel>
|
||||||
|
<FormPanel title="Email settings">
|
||||||
|
{
|
||||||
|
<FormAccess isHorizontal role="manage-realm">
|
||||||
|
<FormGroup
|
||||||
|
label={t("emailAsUsername")}
|
||||||
|
fieldId="kc-email-as-username"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("emailAsUsernameHelpText")}
|
||||||
|
forLabel={t("emailAsUsername")}
|
||||||
|
forID="kc-email-as-username"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-email-as-username"
|
||||||
|
data-testid="email-as-username-switch"
|
||||||
|
name="registrationEmailAsUsername"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.registrationEmailAsUsername}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, registrationEmailAsUsername: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("loginWithEmail")}
|
||||||
|
fieldId="kc-login-with-email"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("loginWithEmailHelpText")}
|
||||||
|
forLabel={t("loginWithEmail")}
|
||||||
|
forID="kc-login-with-email"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-login-with-email"
|
||||||
|
data-testid="login-with-email-switch"
|
||||||
|
name="loginWithEmailAllowed"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.loginWithEmailAllowed}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, loginWithEmailAllowed: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("duplicateEmails")}
|
||||||
|
fieldId="kc-duplicate-emails"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("duplicateEmailsHelpText")}
|
||||||
|
forLabel={t("duplicateEmails")}
|
||||||
|
forID="kc-duplicate-emails"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-duplicate-emails"
|
||||||
|
data-testid="duplicate-emails-switch"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
name="duplicateEmailsAllowed"
|
||||||
|
isChecked={
|
||||||
|
realm?.duplicateEmailsAllowed &&
|
||||||
|
!realm?.loginWithEmailAllowed &&
|
||||||
|
!realm?.registrationEmailAsUsername
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, duplicateEmailsAllowed: value });
|
||||||
|
}}
|
||||||
|
isDisabled={
|
||||||
|
realm?.loginWithEmailAllowed ||
|
||||||
|
realm?.registrationEmailAsUsername
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("verifyEmail")}
|
||||||
|
fieldId="kc-verify-email"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("verifyEmailHelpText")}
|
||||||
|
forLabel={t("verifyEmail")}
|
||||||
|
forID="kc-verify-email"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hasNoPaddingTop
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="kc-verify-email"
|
||||||
|
data-testid="verify-email-switch"
|
||||||
|
name="verifyEmail"
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
isChecked={realm?.verifyEmail}
|
||||||
|
onChange={(value) => {
|
||||||
|
save({ ...realm, verifyEmail: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormAccess>
|
||||||
|
}
|
||||||
|
</FormPanel>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -4,37 +4,25 @@ import { useTranslation } from "react-i18next";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { useErrorHandler } from "react-error-boundary";
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
Button,
|
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
ClipboardCopy,
|
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownSeparator,
|
DropdownSeparator,
|
||||||
FormGroup,
|
|
||||||
PageSection,
|
PageSection,
|
||||||
Select,
|
|
||||||
SelectOption,
|
|
||||||
SelectVariant,
|
|
||||||
Stack,
|
|
||||||
StackItem,
|
|
||||||
Switch,
|
|
||||||
Tab,
|
Tab,
|
||||||
TabTitleText,
|
TabTitleText,
|
||||||
TextInput,
|
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
import { getBaseUrl, toUpperCase } from "../util";
|
import { toUpperCase } from "../util";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { FormAccess } from "../components/form-access/FormAccess";
|
|
||||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
|
||||||
import { FormattedLink } from "../components/external-link/FormattedLink";
|
|
||||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||||
|
import { RealmSettingsLoginTab } from "./LoginTab";
|
||||||
|
import { RealmSettingsGeneralTab } from "./GeneralTab";
|
||||||
import { PartialImportDialog } from "./PartialImport";
|
import { PartialImportDialog } from "./PartialImport";
|
||||||
|
|
||||||
type RealmSettingsHeaderProps = {
|
type RealmSettingsHeaderProps = {
|
||||||
|
@ -128,25 +116,18 @@ const RealmSettingsHeader = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const requireSslTypes = ["all", "external", "none"];
|
|
||||||
|
|
||||||
export const RealmSettingsSection = () => {
|
export const RealmSettingsSection = () => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
const { realm: realmName } = useRealm();
|
const { realm: realmName } = useRealm();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
const { register, control, getValues, setValue, handleSubmit } = useForm();
|
const { control, getValues, setValue } = useForm();
|
||||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const baseUrl = getBaseUrl(adminClient);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return asyncStateFetch(
|
return asyncStateFetch(
|
||||||
() => adminClient.realms.findOne({ realm: realmName }),
|
() => adminClient.realms.findOne({ realm: realmName }),
|
||||||
(realm) => {
|
(realm) => {
|
||||||
setRealm(realm);
|
|
||||||
setupForm(realm);
|
setupForm(realm);
|
||||||
},
|
},
|
||||||
handleError
|
handleError
|
||||||
|
@ -160,7 +141,6 @@ export const RealmSettingsSection = () => {
|
||||||
const save = async (realm: RealmRepresentation) => {
|
const save = async (realm: RealmRepresentation) => {
|
||||||
try {
|
try {
|
||||||
await adminClient.realms.update({ realm: realmName }, realm);
|
await adminClient.realms.update({ realm: realmName }, realm);
|
||||||
setRealm(realm);
|
|
||||||
addAlert(t("saveSuccess"), AlertVariant.success);
|
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(t("saveError", { error }), AlertVariant.danger);
|
addAlert(t("saveError", { error }), AlertVariant.danger);
|
||||||
|
@ -182,169 +162,21 @@ export const RealmSettingsSection = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageSection variant="light" className="pf-u-p-0">
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
<KeycloakTabs isBox>
|
<KeycloakTabs isBox>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="general"
|
eventKey="general"
|
||||||
title={<TabTitleText>{t("general")}</TabTitleText>}
|
title={<TabTitleText>{t("realm-settings:general")}</TabTitleText>}
|
||||||
|
data-testid="rs-general-tab"
|
||||||
>
|
>
|
||||||
<PageSection variant="light">
|
<RealmSettingsGeneralTab />
|
||||||
<FormAccess
|
</Tab>
|
||||||
isHorizontal
|
<Tab
|
||||||
role="manage-realm"
|
eventKey="login"
|
||||||
className="pf-u-mt-lg"
|
title={<TabTitleText>{t("realm-settings:login")}</TabTitleText>}
|
||||||
onSubmit={handleSubmit(save)}
|
data-testid="rs-login-tab"
|
||||||
>
|
>
|
||||||
<FormGroup
|
<RealmSettingsLoginTab />
|
||||||
label={t("realmId")}
|
|
||||||
fieldId="kc-realm-id"
|
|
||||||
isRequired
|
|
||||||
>
|
|
||||||
<ClipboardCopy isReadOnly>{realmName}</ClipboardCopy>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup label={t("displayName")} fieldId="kc-display-name">
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
id="kc-display-name"
|
|
||||||
name="displayName"
|
|
||||||
ref={register}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("htmlDisplayName")}
|
|
||||||
fieldId="kc-html-display-name"
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
id="kc-html-display-name"
|
|
||||||
name="displayNameHtml"
|
|
||||||
ref={register}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("frontendUrl")}
|
|
||||||
fieldId="kc-frontend-url"
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText="realm-settings-help:frontendUrl"
|
|
||||||
forLabel={t("frontendUrl")}
|
|
||||||
forID="kc-frontend-url"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
id="kc-frontend-url"
|
|
||||||
name="attributes.frontendUrl"
|
|
||||||
ref={register}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("requireSsl")}
|
|
||||||
fieldId="kc-require-ssl"
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText="realm-settings-help:requireSsl"
|
|
||||||
forLabel={t("requireSsl")}
|
|
||||||
forID="kc-require-ssl"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="sslRequired"
|
|
||||||
defaultValue="none"
|
|
||||||
control={control}
|
|
||||||
render={({ onChange, value }) => (
|
|
||||||
<Select
|
|
||||||
toggleId="kc-require-ssl"
|
|
||||||
onToggle={() => setOpen(!open)}
|
|
||||||
onSelect={(_, value) => {
|
|
||||||
onChange(value as string);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
selections={value}
|
|
||||||
variant={SelectVariant.single}
|
|
||||||
aria-label={t("requireSsl")}
|
|
||||||
isOpen={open}
|
|
||||||
>
|
|
||||||
{requireSslTypes.map((sslType) => (
|
|
||||||
<SelectOption
|
|
||||||
selected={sslType === value}
|
|
||||||
key={sslType}
|
|
||||||
value={sslType}
|
|
||||||
>
|
|
||||||
{t(`sslType.${sslType}`)}
|
|
||||||
</SelectOption>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
hasNoPaddingTop
|
|
||||||
label={t("userManagedAccess")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText="realm-settings-help:userManagedAccess"
|
|
||||||
forLabel={t("userManagedAccess")}
|
|
||||||
forID="kc-user-manged-access"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-user-manged-access"
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="userManagedAccessAllowed"
|
|
||||||
control={control}
|
|
||||||
defaultValue={false}
|
|
||||||
render={({ onChange, value }) => (
|
|
||||||
<Switch
|
|
||||||
id="kc-user-manged-access"
|
|
||||||
label={t("common:on")}
|
|
||||||
labelOff={t("common:off")}
|
|
||||||
isChecked={value}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("endpoints")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText="realm-settings-help:endpoints"
|
|
||||||
forLabel={t("endpoints")}
|
|
||||||
forID="kc-endpoints"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-endpoints"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<StackItem>
|
|
||||||
<FormattedLink
|
|
||||||
href={`${baseUrl}realms/${realmName}/.well-known/openid-configuration`}
|
|
||||||
title={t("openEndpointConfiguration")}
|
|
||||||
/>
|
|
||||||
</StackItem>
|
|
||||||
<StackItem>
|
|
||||||
<FormattedLink
|
|
||||||
href={`${baseUrl}realms/${realmName}/protocol/saml/descriptor`}
|
|
||||||
title={t("samlIdentityProviderMetadata")}
|
|
||||||
/>
|
|
||||||
</StackItem>
|
|
||||||
</Stack>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<ActionGroup>
|
|
||||||
<Button variant="primary" type="submit">
|
|
||||||
{t("common:save")}
|
|
||||||
</Button>
|
|
||||||
<Button variant="link" onClick={() => setupForm(realm!)}>
|
|
||||||
{t("common:revert")}
|
|
||||||
</Button>
|
|
||||||
</ActionGroup>
|
|
||||||
</FormAccess>
|
|
||||||
</PageSection>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</KeycloakTabs>
|
</KeycloakTabs>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|
31
src/realm-settings/RealmSettingsTabs.tsx
Normal file
31
src/realm-settings/RealmSettingsTabs.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
import { PageSection, Tab, TabTitleText } from "@patternfly/react-core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||||
|
import { RealmSettingsLoginTab } from "./LoginTab";
|
||||||
|
import { RealmSettingsGeneralTab } from "./GeneralTab";
|
||||||
|
|
||||||
|
export const RealmSettingsTabs = () => {
|
||||||
|
const { t } = useTranslation("roles");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
|
<KeycloakTabs isBox>
|
||||||
|
<Tab
|
||||||
|
eventKey="general"
|
||||||
|
title={<TabTitleText>{t("realm-settings:general")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<RealmSettingsGeneralTab />
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="login"
|
||||||
|
title={<TabTitleText>{t("realm-settings:login")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<RealmSettingsLoginTab />
|
||||||
|
</Tab>
|
||||||
|
</KeycloakTabs>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -12,6 +12,21 @@
|
||||||
"saveSuccess": "Realm successfully updated",
|
"saveSuccess": "Realm successfully updated",
|
||||||
"saveError": "Realm could not be updated: {error}",
|
"saveError": "Realm could not be updated: {error}",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
|
"login": "Login",
|
||||||
|
"userRegistration": "User registration",
|
||||||
|
"userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.",
|
||||||
|
"forgotPassword": "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.",
|
||||||
|
"emailAsUsername": "Email as username",
|
||||||
|
"emailAsUsernameHelpText": "Allow users to set email as username.",
|
||||||
|
"loginWithEmail": "Login with email",
|
||||||
|
"loginWithEmailHelpText": "Allow users to log in with their email address.",
|
||||||
|
"duplicateEmails": "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.",
|
||||||
|
"verifyEmail": "Verify email",
|
||||||
|
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
|
||||||
"realmId": "Realm ID",
|
"realmId": "Realm ID",
|
||||||
"displayName": "Display name",
|
"displayName": "Display name",
|
||||||
"htmlDisplayName": "HTML Display name",
|
"htmlDisplayName": "HTML Display name",
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs";
|
||||||
import { SearchGroups } from "./groups/SearchGroups";
|
import { SearchGroups } from "./groups/SearchGroups";
|
||||||
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
|
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
|
||||||
import { LdapMappingDetails } from "./user-federation/ldap/mappers/LdapMappingDetails";
|
import { LdapMappingDetails } from "./user-federation/ldap/mappers/LdapMappingDetails";
|
||||||
|
import { RealmSettingsTabs } from "./realm-settings/RealmSettingsTabs";
|
||||||
|
|
||||||
export type RouteDef = BreadcrumbsRoute & {
|
export type RouteDef = BreadcrumbsRoute & {
|
||||||
access: AccessType;
|
access: AccessType;
|
||||||
|
@ -172,11 +173,17 @@ export const routes: RoutesFn = (t: TFunction) => [
|
||||||
access: "view-events",
|
access: "view-events",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/realm-settings",
|
path: "/:realm/realm-settings/:tab?",
|
||||||
component: RealmSettingsSection,
|
component: RealmSettingsSection,
|
||||||
breadcrumb: t("realmSettings"),
|
breadcrumb: t("realmSettings"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:realm/realm-settings/general",
|
||||||
|
component: RealmSettingsTabs,
|
||||||
|
breadcrumb: t("realmSettings"),
|
||||||
|
access: "view-realm",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/authentication",
|
path: "/:realm/authentication",
|
||||||
component: AuthenticationSection,
|
component: AuthenticationSection,
|
||||||
|
|
Loading…
Reference in a new issue