added unlock user when brute force detect on (#1093)
* added unlock user when brute force detect on + some light refactor * fixed spelling * fixed merge error
This commit is contained in:
parent
0853e20ba1
commit
e58dfc7508
5 changed files with 170 additions and 148 deletions
|
@ -13,31 +13,39 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, UseFormMethods } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import { FormAccess } from "../components/form-access/FormAccess";
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
import moment from "moment";
|
|
||||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { emailRegexPattern } from "../util";
|
import { emailRegexPattern } from "../util";
|
||||||
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
export type BruteForced = {
|
||||||
|
isBruteForceProtected?: boolean;
|
||||||
|
isLocked?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type UserFormProps = {
|
export type UserFormProps = {
|
||||||
form: UseFormMethods<UserRepresentation>;
|
user?: UserRepresentation;
|
||||||
|
bruteForce?: BruteForced;
|
||||||
save: (user: UserRepresentation) => void;
|
save: (user: UserRepresentation) => void;
|
||||||
editMode: boolean;
|
|
||||||
timestamp?: number;
|
|
||||||
onGroupsUpdate: (groups: GroupRepresentation[]) => void;
|
onGroupsUpdate: (groups: GroupRepresentation[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserForm = ({
|
export const UserForm = ({
|
||||||
form: { handleSubmit, register, errors, watch, control, setValue, reset },
|
user,
|
||||||
|
bruteForce: { isBruteForceProtected, isLocked } = {
|
||||||
|
isBruteForceProtected: false,
|
||||||
|
isLocked: false,
|
||||||
|
},
|
||||||
save,
|
save,
|
||||||
editMode,
|
|
||||||
onGroupsUpdate,
|
onGroupsUpdate,
|
||||||
}: UserFormProps) => {
|
}: UserFormProps) => {
|
||||||
const { t } = useTranslation("users");
|
const { t } = useTranslation("users");
|
||||||
|
@ -49,36 +57,24 @@ export const UserForm = ({
|
||||||
] = useState(false);
|
] = useState(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { id } = useParams<{ id: string }>();
|
const { addAlert, addError } = useAlerts();
|
||||||
|
|
||||||
|
const { handleSubmit, register, errors, watch, control, reset } =
|
||||||
|
useFormContext();
|
||||||
const watchUsernameInput = watch("username");
|
const watchUsernameInput = watch("username");
|
||||||
const [user, setUser] = useState<UserRepresentation>();
|
|
||||||
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
|
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { addAlert, addError } = useAlerts();
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [locked, setLocked] = useState(isLocked);
|
||||||
|
|
||||||
useFetch(
|
const unLockUser = async () => {
|
||||||
async () => {
|
try {
|
||||||
if (editMode) return await adminClient.users.findOne({ id: id });
|
await adminClient.attackDetection.del({ id: user!.id! });
|
||||||
},
|
addAlert(t("unlockSuccess"), AlertVariant.success);
|
||||||
(user) => {
|
} catch (error) {
|
||||||
if (user) {
|
addError("users:unlockError", error);
|
||||||
setupForm(user);
|
}
|
||||||
setUser(user);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[selectedGroups]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setupForm = (user: UserRepresentation) => {
|
|
||||||
reset();
|
|
||||||
Object.entries(user).map((entry) => {
|
|
||||||
setValue(entry[0], entry[1]);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredUserActionsOptions = [
|
const requiredUserActionsOptions = [
|
||||||
|
@ -116,7 +112,7 @@ export const UserForm = ({
|
||||||
newGroups.forEach(async (group) => {
|
newGroups.forEach(async (group) => {
|
||||||
try {
|
try {
|
||||||
await adminClient.users.addToGroup({
|
await adminClient.users.addToGroup({
|
||||||
id,
|
id: user!.id!,
|
||||||
groupId: group.id!,
|
groupId: group.id!,
|
||||||
});
|
});
|
||||||
addAlert(t("users:addedGroupMembership"), AlertVariant.success);
|
addAlert(t("users:addedGroupMembership"), AlertVariant.success);
|
||||||
|
@ -145,17 +141,17 @@ export const UserForm = ({
|
||||||
ok: "users:join",
|
ok: "users:join",
|
||||||
}}
|
}}
|
||||||
onConfirm={(groups) => {
|
onConfirm={(groups) => {
|
||||||
editMode ? addGroups(groups) : addChips(groups);
|
user?.id ? addGroups(groups) : addChips(groups);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
filterGroups={selectedGroups}
|
filterGroups={selectedGroups}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{editMode && user ? (
|
{user?.id ? (
|
||||||
<>
|
<>
|
||||||
<FormGroup label={t("common:id")} fieldId="kc-id" isRequired>
|
<FormGroup label={t("common:id")} fieldId="kc-id" isRequired>
|
||||||
<TextInput id={user.id} value={user.id} type="text" isReadOnly />
|
<TextInput id={user?.id} value={user?.id} type="text" isReadOnly />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
|
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -182,7 +178,7 @@ export const UserForm = ({
|
||||||
type="text"
|
type="text"
|
||||||
id="kc-username"
|
id="kc-username"
|
||||||
name="username"
|
name="username"
|
||||||
isReadOnly={editMode}
|
isReadOnly={!!user?.id}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
@ -260,6 +256,32 @@ export const UserForm = ({
|
||||||
aria-label={t("lastName")}
|
aria-label={t("lastName")}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
{isBruteForceProtected && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("temporaryLocked")}
|
||||||
|
fieldId="temporaryLocked"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="users-help:temporaryLocked"
|
||||||
|
forLabel={t("temporaryLocked")}
|
||||||
|
forID={t(`common:helpLabel`, { label: t("temporaryLocked") })}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
data-testid="user-locked-switch"
|
||||||
|
id={"temporaryLocked"}
|
||||||
|
onChange={(value) => {
|
||||||
|
unLockUser();
|
||||||
|
setLocked(value);
|
||||||
|
}}
|
||||||
|
isChecked={locked}
|
||||||
|
isDisabled={!locked}
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("common:enabled")}
|
label={t("common:enabled")}
|
||||||
fieldId="kc-enabled"
|
fieldId="kc-enabled"
|
||||||
|
@ -333,7 +355,7 @@ export const UserForm = ({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{!editMode && (
|
{!user?.id && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("common:groups")}
|
label={t("common:groups")}
|
||||||
fieldId="kc-groups"
|
fieldId="kc-groups"
|
||||||
|
@ -380,21 +402,21 @@ export const UserForm = ({
|
||||||
|
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
data-testid={!editMode ? "create-user" : "save-user"}
|
data-testid={!user?.id ? "create-user" : "save-user"}
|
||||||
isDisabled={!editMode && !watchUsernameInput}
|
isDisabled={!user?.id && !watchUsernameInput}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{editMode ? t("common:save") : t("common:create")}
|
{user?.id ? t("common:save") : t("common:create")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
data-testid="cancel-create-user"
|
data-testid="cancel-create-user"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
editMode ? setupForm(user!) : history.push(`/${realm}/users`)
|
user?.id ? reset(user) : history.push(`/${realm}/users`)
|
||||||
}
|
}
|
||||||
variant="link"
|
variant="link"
|
||||||
>
|
>
|
||||||
{editMode ? t("common:revert") : t("common:cancel")}
|
{user?.id ? t("common:revert") : t("common:cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
|
|
|
@ -12,7 +12,6 @@ import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/us
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { GroupPath } from "../components/group/GroupPath";
|
import { GroupPath } from "../components/group/GroupPath";
|
||||||
|
@ -20,31 +19,21 @@ import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
||||||
import { useHelp } from "../components/help-enabler/HelpHeader";
|
import { useHelp } from "../components/help-enabler/HelpHeader";
|
||||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
import { emptyFormatter } from "../util";
|
import { emptyFormatter } from "../util";
|
||||||
|
|
||||||
export type UserFormProps = {
|
type UserGroupsProps = {
|
||||||
username?: string;
|
user: UserRepresentation;
|
||||||
loader?: (
|
|
||||||
first?: number,
|
|
||||||
max?: number,
|
|
||||||
search?: string
|
|
||||||
) => Promise<UserRepresentation[]>;
|
|
||||||
addGroup?: (newGroup: GroupRepresentation) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserGroups = () => {
|
export const UserGroups = ({ user }: UserGroupsProps) => {
|
||||||
const { t } = useTranslation("users");
|
const { t } = useTranslation("users");
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const refresh = () => setKey(new Date().getTime());
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState<GroupRepresentation>();
|
const [selectedGroup, setSelectedGroup] = useState<GroupRepresentation>();
|
||||||
const [list, setList] = useState(false);
|
|
||||||
const [listGroups, setListGroups] = useState(true);
|
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
|
|
||||||
const [isDirectMembership, setDirectMembership] = useState(true);
|
const [isDirectMembership, setDirectMembership] = useState(true);
|
||||||
const [directMembershipList, setDirectMembershipList] = useState<
|
const [directMembershipList, setDirectMembershipList] = useState<
|
||||||
|
@ -55,7 +44,6 @@ export const UserGroups = () => {
|
||||||
const { enabled } = useHelp();
|
const { enabled } = useHelp();
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { id } = useParams<{ id: string }>();
|
|
||||||
const alphabetize = (groupsList: GroupRepresentation[]) => {
|
const alphabetize = (groupsList: GroupRepresentation[]) => {
|
||||||
return _.sortBy(groupsList, (group) => group.path?.toUpperCase());
|
return _.sortBy(groupsList, (group) => group.path?.toUpperCase());
|
||||||
};
|
};
|
||||||
|
@ -66,22 +54,15 @@ export const UserGroups = () => {
|
||||||
max: max!,
|
max: max!,
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = await adminClient.users.findOne({ id });
|
|
||||||
setUsername(user.username!);
|
|
||||||
|
|
||||||
const searchParam = search || "";
|
const searchParam = search || "";
|
||||||
if (searchParam) {
|
if (searchParam) {
|
||||||
params.search = searchParam;
|
params.search = searchParam;
|
||||||
setSearch(searchParam);
|
setSearch(searchParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!searchParam && !listGroups && !list) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinedUserGroups = await adminClient.users.listGroups({
|
const joinedUserGroups = await adminClient.users.listGroups({
|
||||||
...params,
|
...params,
|
||||||
id,
|
id: user.id!,
|
||||||
});
|
});
|
||||||
|
|
||||||
const allCreatedGroups = await adminClient.groups.find();
|
const allCreatedGroups = await adminClient.groups.find();
|
||||||
|
@ -177,14 +158,6 @@ export const UserGroups = () => {
|
||||||
return alphabetize(directMembership);
|
return alphabetize(directMembership);
|
||||||
};
|
};
|
||||||
|
|
||||||
useFetch(
|
|
||||||
() => adminClient.users.listGroups({ id }),
|
|
||||||
(response) => {
|
|
||||||
setListGroups(!!(response && response.length > 0));
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
refresh();
|
||||||
}, [isDirectMembership]);
|
}, [isDirectMembership]);
|
||||||
|
@ -201,14 +174,14 @@ export const UserGroups = () => {
|
||||||
}),
|
}),
|
||||||
messageKey: t("leaveGroupConfirmDialog", {
|
messageKey: t("leaveGroupConfirmDialog", {
|
||||||
groupname: selectedGroup?.name,
|
groupname: selectedGroup?.name,
|
||||||
username: username,
|
username: user.username,
|
||||||
}),
|
}),
|
||||||
continueButtonLabel: "leave",
|
continueButtonLabel: "leave",
|
||||||
continueButtonVariant: ButtonVariant.danger,
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
try {
|
try {
|
||||||
await adminClient.users.delFromGroup({
|
await adminClient.users.delFromGroup({
|
||||||
id,
|
id: user.id!,
|
||||||
groupId: selectedGroup!.id!,
|
groupId: selectedGroup!.id!,
|
||||||
});
|
});
|
||||||
refresh();
|
refresh();
|
||||||
|
@ -248,10 +221,9 @@ export const UserGroups = () => {
|
||||||
newGroups.forEach(async (group) => {
|
newGroups.forEach(async (group) => {
|
||||||
try {
|
try {
|
||||||
await adminClient.users.addToGroup({
|
await adminClient.users.addToGroup({
|
||||||
id: id,
|
id: user.id!,
|
||||||
groupId: group.id!,
|
groupId: group.id!,
|
||||||
});
|
});
|
||||||
setList(true);
|
|
||||||
refresh();
|
refresh();
|
||||||
addAlert(t("addedGroupMembership"), AlertVariant.success);
|
addAlert(t("addedGroupMembership"), AlertVariant.success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -267,10 +239,10 @@ export const UserGroups = () => {
|
||||||
<DeleteConfirm />
|
<DeleteConfirm />
|
||||||
{open && (
|
{open && (
|
||||||
<GroupPickerDialog
|
<GroupPickerDialog
|
||||||
id={id}
|
id={user.id}
|
||||||
type="selectMany"
|
type="selectMany"
|
||||||
text={{
|
text={{
|
||||||
title: t("joinGroupsFor", { username }),
|
title: t("joinGroupsFor", { username: user.username }),
|
||||||
ok: "users:join",
|
ok: "users:join",
|
||||||
}}
|
}}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
PageSection,
|
PageSection,
|
||||||
|
@ -6,20 +6,21 @@ import {
|
||||||
TabTitleText,
|
TabTitleText,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import { UserForm } from "./UserForm";
|
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||||
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
|
import { BruteForced, UserForm } from "./UserForm";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { useAdminClient } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||||
import { UserGroups } from "./UserGroups";
|
import { UserGroups } from "./UserGroups";
|
||||||
import { UserConsents } from "./UserConsents";
|
import { UserConsents } from "./UserConsents";
|
||||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
||||||
|
import { toUser } from "./routes/User";
|
||||||
|
|
||||||
export const UsersTabs = () => {
|
export const UsersTabs = () => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
|
@ -30,18 +31,36 @@ export const UsersTabs = () => {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const [user, setUser] = useState("");
|
const [user, setUser] = useState<UserRepresentation>();
|
||||||
|
const [bruteForced, setBruteForced] = useState<BruteForced>();
|
||||||
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useFetch(
|
||||||
const update = async () => {
|
async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
const fetchedUser = await adminClient.users.findOne({ id });
|
const user = await adminClient.users.findOne({ id });
|
||||||
setUser(fetchedUser.username!);
|
const isBruteForceProtected = (
|
||||||
|
await adminClient.realms.findOne({ realm })
|
||||||
|
).bruteForceProtected;
|
||||||
|
const isLocked: boolean =
|
||||||
|
isBruteForceProtected &&
|
||||||
|
(await adminClient.attackDetection.findOne({ id: user.id! }))
|
||||||
|
?.disabled;
|
||||||
|
return { user, bruteForced: { isBruteForceProtected, isLocked } };
|
||||||
}
|
}
|
||||||
};
|
return { user: undefined };
|
||||||
setTimeout(update, 100);
|
},
|
||||||
}, []);
|
({ user, bruteForced }) => {
|
||||||
|
setUser(user);
|
||||||
|
setBruteForced(bruteForced);
|
||||||
|
user && setupForm(user);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setupForm = (user: UserRepresentation) => {
|
||||||
|
userForm.reset(user);
|
||||||
|
};
|
||||||
|
|
||||||
const updateGroups = (groups: GroupRepresentation[]) => {
|
const updateGroups = (groups: GroupRepresentation[]) => {
|
||||||
setAddedGroups(groups);
|
setAddedGroups(groups);
|
||||||
|
@ -63,7 +82,7 @@ export const UsersTabs = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
addAlert(t("users:userCreated"), AlertVariant.success);
|
addAlert(t("users:userCreated"), AlertVariant.success);
|
||||||
history.push(`/${realm}/users/${createdUser.id}/settings`);
|
history.push(toUser({ id: createdUser.id, realm, tab: "settings" }));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("users:userCreateError", error);
|
addError("users:userCreateError", error);
|
||||||
|
@ -72,59 +91,63 @@ export const UsersTabs = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeader titleKey={user! || t("users:createUser")} divider={!id} />
|
<ViewHeader
|
||||||
|
titleKey={user?.username || t("users:createUser")}
|
||||||
|
divider={!id}
|
||||||
|
/>
|
||||||
<PageSection variant="light" className="pf-u-p-0">
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
{id && (
|
<FormProvider {...userForm}>
|
||||||
<KeycloakTabs isBox>
|
{id && user && (
|
||||||
<Tab
|
<KeycloakTabs isBox>
|
||||||
eventKey="settings"
|
<Tab
|
||||||
data-testid="user-details-tab"
|
eventKey="settings"
|
||||||
title={<TabTitleText>{t("details")}</TabTitleText>}
|
data-testid="user-details-tab"
|
||||||
>
|
title={<TabTitleText>{t("details")}</TabTitleText>}
|
||||||
<PageSection variant="light">
|
>
|
||||||
<UserForm
|
<PageSection variant="light">
|
||||||
onGroupsUpdate={updateGroups}
|
{bruteForced && (
|
||||||
form={userForm}
|
<UserForm
|
||||||
save={save}
|
onGroupsUpdate={updateGroups}
|
||||||
editMode={true}
|
save={save}
|
||||||
/>
|
user={user}
|
||||||
</PageSection>
|
bruteForce={bruteForced}
|
||||||
</Tab>
|
/>
|
||||||
<Tab
|
)}
|
||||||
eventKey="groups"
|
</PageSection>
|
||||||
data-testid="user-groups-tab"
|
</Tab>
|
||||||
title={<TabTitleText>{t("groups")}</TabTitleText>}
|
<Tab
|
||||||
>
|
eventKey="groups"
|
||||||
<UserGroups />
|
data-testid="user-groups-tab"
|
||||||
</Tab>
|
title={<TabTitleText>{t("groups")}</TabTitleText>}
|
||||||
<Tab
|
>
|
||||||
eventKey="consents"
|
<UserGroups user={user} />
|
||||||
data-testid="user-consents-tab"
|
</Tab>
|
||||||
title={<TabTitleText>{t("users:consents")}</TabTitleText>}
|
<Tab
|
||||||
>
|
eventKey="consents"
|
||||||
<UserConsents />
|
data-testid="user-consents-tab"
|
||||||
</Tab>
|
title={<TabTitleText>{t("users:consents")}</TabTitleText>}
|
||||||
<Tab
|
>
|
||||||
eventKey="identity-provider-links"
|
<UserConsents />
|
||||||
data-testid="identity-provider-links-tab"
|
</Tab>
|
||||||
title={
|
<Tab
|
||||||
<TabTitleText>{t("users:identityProviderLinks")}</TabTitleText>
|
eventKey="identity-provider-links"
|
||||||
}
|
data-testid="identity-provider-links-tab"
|
||||||
>
|
title={
|
||||||
<UserIdentityProviderLinks />
|
<TabTitleText>
|
||||||
</Tab>
|
{t("users:identityProviderLinks")}
|
||||||
</KeycloakTabs>
|
</TabTitleText>
|
||||||
)}
|
}
|
||||||
{!id && (
|
>
|
||||||
<PageSection variant="light">
|
<UserIdentityProviderLinks />
|
||||||
<UserForm
|
</Tab>
|
||||||
onGroupsUpdate={updateGroups}
|
</KeycloakTabs>
|
||||||
form={userForm}
|
)}
|
||||||
save={save}
|
{!id && (
|
||||||
editMode={false}
|
<PageSection variant="light">
|
||||||
/>
|
<UserForm onGroupsUpdate={updateGroups} save={save} />
|
||||||
</PageSection>
|
</PageSection>
|
||||||
)}
|
)}
|
||||||
|
</FormProvider>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export default {
|
export default {
|
||||||
"users-help": {
|
"users-help": {
|
||||||
|
temporaryLocked:
|
||||||
|
"The user may be locked due to multiple failed attempts to log in.",
|
||||||
disabled: "A disabled user cannot log in.",
|
disabled: "A disabled user cannot log in.",
|
||||||
emailVerified: "Has the user's email been verified?",
|
emailVerified: "Has the user's email been verified?",
|
||||||
requiredUserActions:
|
requiredUserActions:
|
||||||
|
|
|
@ -38,6 +38,9 @@ export default {
|
||||||
firstName: "First name",
|
firstName: "First name",
|
||||||
status: "Status",
|
status: "Status",
|
||||||
disabled: "Disabled",
|
disabled: "Disabled",
|
||||||
|
temporaryLocked: "Temporarily locked",
|
||||||
|
unlockSuccess: "User successfully unlocked",
|
||||||
|
unlockError: "Could not unlock user due to {{error}}",
|
||||||
emailInvalid: "You must enter a valid email.",
|
emailInvalid: "You must enter a valid email.",
|
||||||
temporaryDisabled: "Temporarily disabled",
|
temporaryDisabled: "Temporarily disabled",
|
||||||
notVerified: "Not verified",
|
notVerified: "Not verified",
|
||||||
|
|
Loading…
Reference in a new issue