diff --git a/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx b/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx deleted file mode 100644 index 4476ea1dd1..0000000000 --- a/packages/javascript/bh-shared-ui/src/hooks/useUsers.tsx +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2023 Specter Ops, Inc. -// -// Licensed under the Apache License, Version 2.0 -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -import { PutUserAuthSecretRequest, RequestOptions } from 'js-client-library'; -import { useMutation, useQuery, useQueryClient } from 'react-query'; -import { apiClient } from '../utils'; -import { addNotification } from '../providers/NotificationProvider/actions'; - -export type User = { - id: string; - sso_provider_id: number | null; - AuthSecret: any; - roles: Role[]; - first_name: string | null; - last_name: string | null; - email_address: string | null; - principal_name: string; - last_login: string; -}; - -export type Permission = { - id: number; - name: string; - authority: string; -}; - -export type Role = { - name: string; - description: string; - permissions: Permission[]; -}; - -export type CreateUserRequest = { - firstName: string; - lastName: string; - emailAddress: string; - principal: string; - roles: number[]; - SAMLProviderId?: string; - password?: string; - needsPasswordReset?: boolean; -}; - -export type UpdateUserRequest = { - firstName: string; - lastName: string; - emailAddress: string; - principal: string; - roles: number[]; -}; - -export const userKeys = { - all: ['users'] as const, - detail: (userId: number) => [...userKeys.all, userId] as const, -}; - -export const getUsers = (options?: RequestOptions): Promise => - apiClient.listUsers(options).then((res) => res.data.data.users); - -export const createUser = ({ newUser }: { newUser: CreateUserRequest }, options?: RequestOptions) => - apiClient.createUser(newUser, options).then((res) => res.data.data); - -export const updateUser = ( - { userId, updatedUser }: { userId: string; updatedUser: UpdateUserRequest }, - options?: RequestOptions -) => apiClient.updateUser(userId, updatedUser, options).then((res) => res.data.data); - -export const deleteUser = ({ userId }: { userId: string }, options?: RequestOptions) => - apiClient.deleteUser(userId, options).then((res) => res.data); - -export const expireUserPassword = ({ userId }: { userId: string }, options?: RequestOptions) => - apiClient.expireUserAuthSecret(userId, options).then((res) => res.data); - -export const updateUserPassword = ( - { - userId, - payload, - }: { - userId: string; - payload: PutUserAuthSecretRequest; - }, - options?: RequestOptions -) => apiClient.putUserAuthSecret(userId, payload, options).then((res) => res.data); - -export const useGetUsers = () => useQuery(userKeys.all, ({ signal }) => getUsers({ signal })); - -export const useCreateUser = () => { - const queryClient = useQueryClient(); - - return useMutation(createUser, { - onSuccess: () => { - addNotification('User created successfully!', 'createUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useUpdateUser = () => { - const queryClient = useQueryClient(); - - return useMutation(updateUser, { - onSuccess: () => { - addNotification('User updated successfully!', 'updateUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useDeleteUser = () => { - const queryClient = useQueryClient(); - - return useMutation(deleteUser, { - onSuccess: () => { - addNotification('User deleted successfully!', 'deleteUserSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useExpireUserPassword = () => { - const queryClient = useQueryClient(); - - return useMutation(expireUserPassword, { - onSuccess: () => { - addNotification('User password expired successfully!', 'expireUserPasswordSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; - -export const useUpdateUserPassword = () => { - const queryClient = useQueryClient(); - - return useMutation(updateUserPassword, { - onSuccess: () => { - addNotification('User password updated successfully!', 'updateUserPasswordSuccess'); - queryClient.invalidateQueries(userKeys.all); - }, - }); -}; diff --git a/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx b/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx index 31caf71d62..12809920b0 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/Users.tsx @@ -32,10 +32,9 @@ import { CreateUserDialog, } from '../../components'; import { apiClient, LuxonFormat } from '../../utils'; -import { CreateUserRequest, PutUserAuthSecretRequest, UpdateUserRequest } from 'js-client-library'; +import { CreateUserRequest, PutUserAuthSecretRequest, UpdateUserRequest, User } from 'js-client-library'; import find from 'lodash/find'; import { useToggle } from '../../hooks'; -import { User } from '../../hooks/useUsers'; import UserActionsMenu from '../../components/UserActionsMenu'; import { useNotifications } from '../../providers'; @@ -93,51 +92,31 @@ const Users = () => { } ); - const disableUserMutation = useMutation( - async (userId: string) => { - const user = listUsersQuery.data.find((user: User) => { + const disableEnableUserMutation = useMutation( + async ({ userId, disable }: { userId: string; disable: boolean }) => { + const user = listUsersQuery.data?.find((user: User) => { return user.id === userId; }); - const updatedUser = { - emailAddress: user.email_address || '', - principal: user.principal_name || '', - firstName: user.first_name || '', - lastName: user.last_name || '', - SSOProviderId: user.sso_provider_id?.toString() || '', - roles: user.roles?.map((role: any) => role.id) || [], - is_disabled: true, - }; - return apiClient.updateUser(selectedUserId!, updatedUser); - }, - { - onSuccess: () => { - addNotification('User disabled successfully!', 'disableUserSuccess'); - listUsersQuery.refetch(); - }, - } - ); + if (!user) { + return; + } - const enableUserMutation = useMutation( - async (userId: string) => { - const user = listUsersQuery.data.find((user: User) => { - return user.id === userId; - }); - - const updatedUser = { + const updatedUser: UpdateUserRequest = { emailAddress: user.email_address || '', principal: user.principal_name || '', firstName: user.first_name || '', lastName: user.last_name || '', - SSOProviderId: user.sso_provider_id?.toString() || '', + ...(user.sso_provider_id && { SSOProviderId: user.sso_provider_id }), roles: user.roles?.map((role: any) => role.id) || [], - is_disabled: false, + is_disabled: disable, }; + return apiClient.updateUser(selectedUserId!, updatedUser); }, { - onSuccess: () => { - addNotification('User enabled successfully!', 'enableUserSuccess'); + onSuccess: (_, { disable }) => { + addNotification(`User ${disable ? 'disabled' : 'enabled'} successfully!`, 'disableEnableUserSuccess'); listUsersQuery.refetch(); }, } @@ -301,7 +280,7 @@ const Users = () => { title={'Enable User'} onClose={(response) => { if (response) { - enableUserMutation.mutate(selectedUserId!); + disableEnableUserMutation.mutate({ userId: selectedUserId!, disable: false }); } toggleEnableUserDialog(); }} @@ -312,7 +291,7 @@ const Users = () => { title={'Disable User'} onClose={(response) => { if (response) { - disableUserMutation.mutate(selectedUserId!); + disableEnableUserMutation.mutate({ userId: selectedUserId!, disable: true }); } toggleDisableUserDialog(); }} diff --git a/packages/javascript/js-client-library/src/client.ts b/packages/javascript/js-client-library/src/client.ts index df53d08192..6548478cf3 100644 --- a/packages/javascript/js-client-library/src/client.ts +++ b/packages/javascript/js-client-library/src/client.ts @@ -740,7 +740,8 @@ class BHEAPIClient { deleteUserToken = (tokenId: string, options?: types.RequestOptions) => this.baseClient.delete(`/api/v2/tokens/${tokenId}`, options); - listUsers = (options?: types.RequestOptions) => this.baseClient.get('/api/v2/bloodhound-users', options); + listUsers = (options?: types.RequestOptions) => + this.baseClient.get('/api/v2/bloodhound-users', options); getUser = (userId: string, options?: types.RequestOptions) => this.baseClient.get(`/api/v2/bloodhound-users/${userId}`, options); diff --git a/packages/javascript/js-client-library/src/types.ts b/packages/javascript/js-client-library/src/types.ts index 9e47d0910d..1e15874460 100644 --- a/packages/javascript/js-client-library/src/types.ts +++ b/packages/javascript/js-client-library/src/types.ts @@ -185,6 +185,36 @@ export interface ListSSOProvidersResponse { data: SSOProvider[]; } +export interface User { + id: string; + sso_provider_id: number | null; + AuthSecret: any; + roles: Role[]; + first_name: string | null; + last_name: string | null; + email_address: string | null; + principal_name: string; + last_login: string; +} + +interface Permission { + id: number; + name: string; + authority: string; +} + +interface Role { + name: string; + description: string; + permissions: Permission[]; +} + +export interface ListUsersResponse { + data: { + users: User[]; + }; +} + export interface LoginRequest { login_method: string; secret: string;