Only show activated required actions in credentials reset dialog (#19048)
This commit is contained in:
parent
e40fa5fcb4
commit
fc0a9be79f
4 changed files with 34 additions and 83 deletions
|
@ -45,6 +45,7 @@
|
||||||
"emailInvalid": "You must enter a valid email.",
|
"emailInvalid": "You must enter a valid email.",
|
||||||
"notVerified": "Not verified",
|
"notVerified": "Not verified",
|
||||||
"requiredUserActions": "Required user actions",
|
"requiredUserActions": "Required user actions",
|
||||||
|
"requiredActionPlaceholder": "Select action",
|
||||||
"federationLink": "Federation link",
|
"federationLink": "Federation link",
|
||||||
"addUser": "Add user",
|
"addUser": "Add user",
|
||||||
"impersonate": "Impersonate",
|
"impersonate": "Impersonate",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
|
@ -10,8 +9,6 @@ import {
|
||||||
ChipGroup,
|
ChipGroup,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
Select,
|
|
||||||
SelectOption,
|
|
||||||
Switch,
|
Switch,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -32,6 +29,7 @@ import useFormatDate from "../utils/useFormatDate";
|
||||||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||||
import { FederatedUserLink } from "./FederatedUserLink";
|
import { FederatedUserLink } from "./FederatedUserLink";
|
||||||
import { UserProfileFields } from "./UserProfileFields";
|
import { UserProfileFields } from "./UserProfileFields";
|
||||||
|
import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect";
|
||||||
|
|
||||||
export type BruteForced = {
|
export type BruteForced = {
|
||||||
isBruteForceProtected?: boolean;
|
isBruteForceProtected?: boolean;
|
||||||
|
@ -93,10 +91,6 @@ export const UserForm = ({
|
||||||
const formatDate = useFormatDate();
|
const formatDate = useFormatDate();
|
||||||
const isFeatureEnabled = useIsFeatureEnabled();
|
const isFeatureEnabled = useIsFeatureEnabled();
|
||||||
|
|
||||||
const [
|
|
||||||
isRequiredUserActionsDropdownOpen,
|
|
||||||
setRequiredUserActionsDropdownOpen,
|
|
||||||
] = useState(false);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
|
@ -118,22 +112,14 @@ export const UserForm = ({
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [locked, setLocked] = useState(isLocked);
|
const [locked, setLocked] = useState(isLocked);
|
||||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||||
const [requiredActions, setRequiredActions] = useState<
|
|
||||||
RequiredActionProviderRepresentation[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() =>
|
() => adminClient.realms.findOne({ realm: realmName }),
|
||||||
Promise.all([
|
(realm) => {
|
||||||
adminClient.realms.findOne({ realm: realmName }),
|
|
||||||
adminClient.authenticationManagement.getRequiredActions(),
|
|
||||||
]),
|
|
||||||
([realm, actions]) => {
|
|
||||||
if (!realm) {
|
if (!realm) {
|
||||||
throw new Error(t("common:notFound"));
|
throw new Error(t("common:notFound"));
|
||||||
}
|
}
|
||||||
setRealm(realm);
|
setRealm(realm);
|
||||||
setRequiredActions(actions);
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
@ -147,10 +133,6 @@ export const UserForm = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSelection = () => {
|
|
||||||
setRequiredUserActionsDropdownOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteItem = (id: string) => {
|
const deleteItem = (id: string) => {
|
||||||
setSelectedGroups(selectedGroups.filter((item) => item.name !== id));
|
setSelectedGroups(selectedGroups.filter((item) => item.name !== id));
|
||||||
onGroupsUpdate?.(selectedGroups);
|
onGroupsUpdate?.(selectedGroups);
|
||||||
|
@ -233,56 +215,11 @@ export const UserForm = ({
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<FormGroup
|
<RequiredActionMultiSelect
|
||||||
label={t("requiredUserActions")}
|
name="requiredActions"
|
||||||
fieldId="kc-required-user-actions"
|
label="requiredUserActions"
|
||||||
validated={errors.requiredActions ? "error" : "default"}
|
help="users-help:requiredUserActions"
|
||||||
helperTextInvalid={t("common:required")}
|
/>
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={t("users-help:requiredUserActions")}
|
|
||||||
fieldLabelId="users:requiredUserActions"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="requiredActions"
|
|
||||||
defaultValue={[]}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Select
|
|
||||||
data-testid="required-actions-select"
|
|
||||||
placeholderText="Select action"
|
|
||||||
toggleId="kc-required-user-actions"
|
|
||||||
onToggle={() =>
|
|
||||||
setRequiredUserActionsDropdownOpen(
|
|
||||||
!isRequiredUserActionsDropdownOpen
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isOpen={isRequiredUserActionsDropdownOpen}
|
|
||||||
selections={field.value}
|
|
||||||
onSelect={(_, v) => {
|
|
||||||
const option = v as string;
|
|
||||||
if (field.value.includes(option)) {
|
|
||||||
field.onChange(
|
|
||||||
field.value.filter((item: string) => item !== option)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
field.onChange([...field.value, option]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onClear={clearSelection}
|
|
||||||
variant="typeaheadmulti"
|
|
||||||
>
|
|
||||||
{requiredActions.map(({ alias, name }) => (
|
|
||||||
<SelectOption key={alias} value={alias}>
|
|
||||||
{name}
|
|
||||||
</SelectOption>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
{(user?.federationLink || user?.origin) && (
|
{(user?.federationLink || user?.origin) && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("federationLink")}
|
label={t("federationLink")}
|
||||||
|
|
|
@ -12,7 +12,17 @@ import { useTranslation } from "react-i18next";
|
||||||
import { HelpItem } from "ui-shared";
|
import { HelpItem } from "ui-shared";
|
||||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||||
|
|
||||||
export const CredentialsResetActionMultiSelect = () => {
|
type RequiredActionMultiSelectProps = {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
help: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RequiredActionMultiSelect = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
help,
|
||||||
|
}: RequiredActionMultiSelectProps) => {
|
||||||
const { t } = useTranslation("users");
|
const { t } = useTranslation("users");
|
||||||
const { adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
@ -24,24 +34,22 @@ export const CredentialsResetActionMultiSelect = () => {
|
||||||
useFetch(
|
useFetch(
|
||||||
() => adminClient.authenticationManagement.getRequiredActions(),
|
() => adminClient.authenticationManagement.getRequiredActions(),
|
||||||
(actions) => {
|
(actions) => {
|
||||||
setRequiredActions(actions);
|
const enabledUserActions = actions.filter((action) => {
|
||||||
|
return action.enabled;
|
||||||
|
});
|
||||||
|
setRequiredActions(enabledUserActions);
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("resetActions")}
|
label={t(label)}
|
||||||
labelIcon={
|
labelIcon={<HelpItem helpText={t(help)} fieldLabelId="resetActions" />}
|
||||||
<HelpItem
|
|
||||||
helpText={t("clients-help:resetActions")}
|
|
||||||
fieldLabelId="resetActions"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="actions"
|
fieldId="actions"
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="actions"
|
name={name}
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
|
@ -52,6 +60,7 @@ export const CredentialsResetActionMultiSelect = () => {
|
||||||
chipGroupProps={{
|
chipGroupProps={{
|
||||||
numChips: 3,
|
numChips: 3,
|
||||||
}}
|
}}
|
||||||
|
placeholderText={t("requiredActionPlaceholder")}
|
||||||
menuAppendTo="parent"
|
menuAppendTo="parent"
|
||||||
onToggle={(open) => setOpen(open)}
|
onToggle={(open) => setOpen(open)}
|
||||||
isOpen={open}
|
isOpen={open}
|
|
@ -3,7 +3,7 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||||
import { ModalVariant, Form, AlertVariant } from "@patternfly/react-core";
|
import { ModalVariant, Form, AlertVariant } from "@patternfly/react-core";
|
||||||
|
|
||||||
import type { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
import type { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
||||||
import { CredentialsResetActionMultiSelect } from "./CredentialsResetActionMultiSelect";
|
import { RequiredActionMultiSelect } from "./RequiredActionMultiSelect";
|
||||||
import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog";
|
import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog";
|
||||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
@ -84,7 +84,11 @@ export const ResetCredentialDialog = ({
|
||||||
data-testid="credential-reset-modal"
|
data-testid="credential-reset-modal"
|
||||||
>
|
>
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<CredentialsResetActionMultiSelect />
|
<RequiredActionMultiSelect
|
||||||
|
name="actions"
|
||||||
|
label="resetActions"
|
||||||
|
help="clients-help:resetActions"
|
||||||
|
/>
|
||||||
<LifespanField />
|
<LifespanField />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
Loading…
Reference in a new issue