import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import { AlertVariant, ButtonVariant, DropdownItem, PageSection, Tab, TabTitleText, } from "@patternfly/react-core"; import { useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { RoutableTabs, useRoutableTab, } from "../components/routable-tabs/RoutableTabs"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useAccess } from "../context/access/Access"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext"; import { useParams } from "../utils/useParams"; import { toUser, UserParams, UserTab } from "./routes/User"; import { toUsers } from "./routes/Users"; import { UserAttributes } from "./UserAttributes"; import { UserConsents } from "./UserConsents"; import { UserCredentials } from "./UserCredentials"; import { BruteForced, UserForm } from "./UserForm"; import { UserGroups } from "./UserGroups"; import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks"; import { isUserProfileError, userProfileErrorToString, } from "./UserProfileFields"; import { UserRoleMapping } from "./UserRoleMapping"; import { UserSessions } from "./UserSessions"; import "./user-section.css"; export default function EditUser() { const { adminClient } = useAdminClient(); const { realm } = useRealm(); const { id } = useParams(); const { t } = useTranslation("users"); const [user, setUser] = useState(); const [bruteForced, setBruteForced] = useState(); const [refreshCount, setRefreshCount] = useState(0); const refresh = () => setRefreshCount((count) => count + 1); useFetch( async () => { const [user, currentRealm, attackDetection] = await Promise.all([ adminClient.users.findOne({ id: id! }), adminClient.realms.findOne({ realm }), adminClient.attackDetection.findOne({ id: id! }), ]); if (!user || !currentRealm || !attackDetection) { throw new Error(t("common:notFound")); } const isBruteForceProtected = currentRealm.bruteForceProtected; const isLocked = isBruteForceProtected && attackDetection.disabled; return { user, bruteForced: { isBruteForceProtected, isLocked } }; }, ({ user, bruteForced }) => { setUser(user); setBruteForced(bruteForced); }, [refreshCount] ); if (!user || !bruteForced) { return ; } return ( ); } type EditUserFormProps = { user: UserRepresentation; bruteForced: BruteForced; refresh: () => void; }; const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => { const { t } = useTranslation("users"); const { realm } = useRealm(); const { adminClient } = useAdminClient(); const { addAlert, addError } = useAlerts(); const navigate = useNavigate(); const { hasAccess } = useAccess(); const userForm = useForm({ mode: "onChange", defaultValues: user, }); const toTab = (tab: UserTab) => toUser({ realm, id: user.id!, tab, }); const useTab = (tab: UserTab) => useRoutableTab(toTab(tab)); const settingsTab = useTab("settings"); const attributesTab = useTab("attributes"); const credentialsTab = useTab("credentials"); const roleMappingTab = useTab("role-mapping"); const groupsTab = useTab("groups"); const consentsTab = useTab("consents"); const identityProviderLinksTab = useTab("identity-provider-links"); const sessionsTab = useTab("sessions"); const save = async (formUser: UserRepresentation) => { try { await adminClient.users.update( { id: user.id! }, { ...formUser, username: formUser.username?.trim(), attributes: { ...user.attributes, ...formUser.attributes }, } ); addAlert(t("userSaved"), AlertVariant.success); refresh(); } catch (error) { if (isUserProfileError(error)) { addError(userProfileErrorToString(error), error); } else { addError("users:userCreateError", error); } } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "users:deleteConfirm", messageKey: "users:deleteConfirmCurrentUser", continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.users.del({ id: user.id! }); addAlert(t("userDeletedSuccess"), AlertVariant.success); navigate(toUsers({ realm })); } catch (error) { addError("users:userDeletedError", error); } }, }); const [toggleImpersonateDialog, ImpersonateConfirm] = useConfirmDialog({ titleKey: "users:impersonateConfirm", messageKey: "users:impersonateConfirmDialog", continueButtonLabel: "users:impersonate", onConfirm: async () => { try { const data = await adminClient.users.impersonation( { id: user.id! }, { user: user.id!, realm } ); if (data.sameRealm) { window.location = data.redirect; } else { window.open(data.redirect, "_blank"); } } catch (error) { addError("users:impersonateError", error); } }, }); return ( <> toggleImpersonateDialog()} > {t("impersonate")} , toggleDeleteDialog()} > {t("common:delete")} , ]} onToggle={(value) => save({ ...user, enabled: value })} isEnabled={user.enabled} /> {t("common:details")}} {...settingsTab} > {t("common:attributes")}} {...attributesTab} > {t("common:credentials")}} {...credentialsTab} > {t("roleMapping")}} {...roleMappingTab} > {hasAccess("query-groups") && ( {t("common:groups")}} {...groupsTab} > )} {t("consents")}} {...consentsTab} > {hasAccess("view-identity-providers") && ( {t("identityProviderLinks")} } {...identityProviderLinksTab} > )} {t("sessions")}} {...sessionsTab} > ); };