diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 279f957ae..6099645ed 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -532,7 +532,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { }); if (machineMembership) throw BadRequestError({ - message: "Service account already exists in workspace" + message: "Machine identity already exists in workspace" }); const machineIdentity = await MachineIdentity.findById(machineId); @@ -542,7 +542,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { if (!workspace) throw ResourceNotFoundError(); if (!machineIdentity.organization.equals(workspace.organization)) throw BadRequestError({ - message: "Failed to add service account to workspace in another organization" + message: "Failed to add machine identity to workspace in another organization" }); let customRole; @@ -572,7 +572,83 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { } /** - * Add service account with id [machineId] to workspace + * Update role of machine identity with id [machineId] in workspace + * with id [workspaceId] to [role] + * @param req + * @param res + */ + export const updateMachineWorkspaceRole = async (req: Request, res: Response) => { + const { + params: { workspaceId, machineId }, + body: { + role + } + } = await validateRequest(reqValidator.AddWorkspaceServiceMemberV2, req); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Create, + ProjectPermissionSub.ServiceTokens + ); + + let machineMembership = await MachineMembership.findOne({ + machineIdentity: new Types.ObjectId(machineId), + workspace: new Types.ObjectId(workspaceId) + }); + + if (!machineMembership) throw BadRequestError({ + message: "Machine identity does not exist in workspace" + }); + + const machineIdentity = await MachineIdentity.findById(machineId); + if (!machineIdentity) throw ResourceNotFoundError(); + + const workspace = await Workspace.findById(workspaceId); + if (!workspace) throw ResourceNotFoundError(); + + if (!machineIdentity.organization.equals(workspace.organization)) throw BadRequestError({ + message: "Failed to add machine identity to workspace in another organization" + }); + + let customRole; + if (role) { + const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role); + if (isCustomRole) { + customRole = await Role.findOne({ + slug: role, + isOrgRole: false, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!customRole) throw BadRequestError({ message: "Role not found" }); + } + } + + machineMembership = await MachineMembership.findOneAndUpdate( + { + machineIdentity: machineIdentity._id, + workspace: new Types.ObjectId(workspaceId), + }, + { + role, + customRole + }, + { + new: true + } + ); + + return res.status(200).send({ + machineMembership + }); +} + +/** + * Delete machine identity with id [machineId] to workspace * with id [workspaceId] * @param req * @param res diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index 24d31f96e..d4dc87c37 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -101,6 +101,14 @@ router.post( workspaceController.addMachineToWorkspace ); +router.patch( + "/:workspaceId/machine-memberships/:machineId", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY] + }), + workspaceController.updateMachineWorkspaceRole +); + router.delete( "/:workspaceId/machine-memberships/:machineId", requireAuth({ diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index 40c8c0587..494cee176 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -20,6 +20,6 @@ export { useRenameWorkspace, useReorderWsEnvironment, useToggleAutoCapitalization, + useUpdateMachineWorkspaceRole, useUpdateUserWorkspaceRole, - useUpdateWsEnvironment, -} from "./queries"; + useUpdateWsEnvironment} from "./queries"; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 8fe84c895..e6217fbab 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -31,7 +31,7 @@ export const workspaceKeys = { getAllUserWorkspace: ["workspaces"] as const, getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const, - getWorkspaceServiceMemberships: (workspaceId: string) => [{ workspaceId }, "organization-service-memberships"] as const + getWorkspaceMachineMemberships: (workspaceId: string) => [{ workspaceId }, "workspace-machine-memberships"] as const }; const fetchWorkspaceById = async (workspaceId: string) => { @@ -378,7 +378,34 @@ export const useAddMachineToWorkspace = () => { return serviceMembership; }, onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries(workspaceKeys.getWorkspaceServiceMemberships(workspaceId)); + queryClient.invalidateQueries(workspaceKeys.getWorkspaceMachineMemberships(workspaceId)); + } + }); +}; + +export const useUpdateMachineWorkspaceRole = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + machineId, + workspaceId, + role + }: { + machineId: string; + workspaceId: string; + role?: string; + }) => { + + const { + data: { serviceMembership } + } = await apiRequest.patch(`/api/v2/workspace/${workspaceId}/machine-memberships/${machineId}`, { + role + }); + + return serviceMembership; + }, + onSuccess: (_, { workspaceId }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceMachineMemberships(workspaceId)); } }); }; @@ -401,7 +428,7 @@ export const useDeleteMachineFromWorkspace = () => { return serviceMembership; }, onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries(workspaceKeys.getWorkspaceServiceMemberships(workspaceId)); + queryClient.invalidateQueries(workspaceKeys.getWorkspaceMachineMemberships(workspaceId)); } }); }; @@ -409,7 +436,7 @@ export const useDeleteMachineFromWorkspace = () => { export const useGetWorkspaceMachineMemberships = (workspaceId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceServiceMemberships(workspaceId), + queryKey: workspaceKeys.getWorkspaceMachineMemberships(workspaceId), queryFn: async () => { const { diff --git a/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx b/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx index 2096411d0..a473dcd1e 100644 --- a/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx +++ b/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx @@ -27,7 +27,8 @@ import { } from "@app/context"; import { useGetRoles, - useGetWorkspaceMachineMemberships + useGetWorkspaceMachineMemberships, + useUpdateMachineWorkspaceRole } from "@app/hooks/api"; import { MachineTrustedIp} from "@app/hooks/api/machineIdentities/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -66,6 +67,8 @@ export const MachineIdentityTable = ({ workspaceId }); + const { mutateAsync: updateMutateAsync } = useUpdateMachineWorkspaceRole(); + const handleChangeRole = async ({ machineId, role @@ -73,21 +76,14 @@ export const MachineIdentityTable = ({ machineId: string; role: string; }) => { - try { - - console.log("handle project-level role change vals: ", { + + await updateMutateAsync({ machineId, + workspaceId, role }); - // TODO: change role - - // await updateMutateAsync({ - // serviceTokenDataId, - // role - // }); - createNotification({ text: "Successfully updated machine identity role", type: "success"