diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index 75f2fe604..e4410bbe4 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -26,7 +26,7 @@ declare module "jsonwebtoken" { refreshVersion?: number; } export interface MachineRefreshTokenJwtPayload extends jwt.JwtPayload { - serviceTokenDataId: string; + _id: string; authTokenType: string; tokenVersion: number; } diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index 6db82a201..350cddc5f 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -4,7 +4,7 @@ import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../mo import { EventType, Role } from "../../ee/models"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { sendMail } from "../../helpers/nodemailer"; -import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables"; +import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables"; import { getSiteURL } from "../../config"; import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { validateRequest } from "../../helpers/validation"; @@ -129,7 +129,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => { ProjectPermissionSub.Member ); - const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role); + const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role); if (isCustomRole) { const wsRole = await Role.findOne({ slug: role, diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 1e27ec356..fc825411e 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -516,7 +516,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { body: { role } - } = await validateRequest(reqValidator.AddWorkspaceServiceMemberV2, req); + } = await validateRequest(reqValidator.AddMachineToWorkspaceV2, req); const { permission } = await getAuthDataProjectPermissions({ authData: req.authData, @@ -574,7 +574,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { role: customRole ? CUSTOM : role, customRole }).save(); - + return res.status(200).send({ machineMembership }); @@ -592,7 +592,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { body: { role } - } = await validateRequest(reqValidator.AddWorkspaceServiceMemberV2, req); + } = await validateRequest(reqValidator.UpdateMachineWorkspaceRoleV2, req); const { permission } = await getAuthDataProjectPermissions({ authData: req.authData, @@ -650,7 +650,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { workspace: new Types.ObjectId(workspaceId), }, { - role, + role: customRole ? CUSTOM : role, customRole }, { @@ -672,7 +672,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { export const deleteMachineFromWorkspace = async (req: Request, res: Response) => { const { params: { workspaceId, machineId } - } = await validateRequest(reqValidator.DeleteWorkspaceServiceMemberV2, req); + } = await validateRequest(reqValidator.DeleteMachineFromWorkspaceV2, req); const { permission } = await getAuthDataProjectPermissions({ authData: req.authData, @@ -705,7 +705,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { export const getWorkspaceMachineMemberships = async (req: Request, res: Response) => { const { params: { workspaceId } - } = await validateRequest(reqValidator.GetWorkspaceServiceMembersV2, req); + } = await validateRequest(reqValidator.GetWorkspaceMachineMembersV2, req); const { permission } = await getAuthDataProjectPermissions({ authData: req.authData, diff --git a/backend/src/ee/controllers/v1/roleController.ts b/backend/src/ee/controllers/v1/roleController.ts index 47707ae60..11ac29a63 100644 --- a/backend/src/ee/controllers/v1/roleController.ts +++ b/backend/src/ee/controllers/v1/roleController.ts @@ -82,7 +82,7 @@ export const updateRole = async (req: Request, res: Response) => { body: { name, description, slug, permissions, workspaceId, orgId } } = await validateRequest(UpdateRoleSchema, req); const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule - + if (isOrgRole) { const { permission } = await getUserOrgPermissions(req.user.id, orgId); if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) { diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 2b11c3674..2ef25f3ae 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -757,19 +757,19 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res } })); - const serviceV3Actors: MachineActor[] = ( + const machineActors: MachineActor[] = ( await MachineIdentity.find({ workspace: new Types.ObjectId(workspaceId) }) ).map((machineIdentity) => ({ type: ActorType.MACHINE, metadata: { - serviceId: machineIdentity._id.toString(), + machineId: machineIdentity._id.toString(), name: machineIdentity.name } })); - const actors = [...userActors, ...serviceActors, ...serviceV3Actors]; + const actors = [...userActors, ...serviceActors, ...machineActors]; return res.status(200).send({ actors diff --git a/backend/src/ee/controllers/v3/machineIdentityController.ts b/backend/src/ee/controllers/v3/machineIdentityController.ts index a422aa9c3..129884d32 100644 --- a/backend/src/ee/controllers/v3/machineIdentityController.ts +++ b/backend/src/ee/controllers/v3/machineIdentityController.ts @@ -43,7 +43,7 @@ import { ForbiddenError } from "@casl/ability"; refreshToken } } = await validateRequest(reqValidator.RefreshTokenV3, req); - + const decodedToken = ( jwt.verify(refreshToken, await getAuthSecret()) ); @@ -51,7 +51,7 @@ import { ForbiddenError } from "@casl/ability"; if (decodedToken.authTokenType !== AuthTokenType.MACHINE_REFRESH_TOKEN) throw UnauthorizedRequestError(); let machineIdentity = await MachineIdentity.findOne({ - _id: new Types.ObjectId(decodedToken.serviceTokenDataId), + _id: new Types.ObjectId(decodedToken._id), isActive: true }); @@ -101,7 +101,7 @@ import { ForbiddenError } from "@casl/ability"; response.accessToken = createToken({ payload: { - _id: machineIdentity._id.toString(), // TODO: fix this + _id: machineIdentity._id.toString(), authTokenType: AuthTokenType.MACHINE_ACCESS_TOKEN, tokenVersion: machineIdentity.tokenVersion }, diff --git a/backend/src/ee/services/ProjectRoleService.ts b/backend/src/ee/services/ProjectRoleService.ts index 0b368c9a0..904301d7e 100644 --- a/backend/src/ee/services/ProjectRoleService.ts +++ b/backend/src/ee/services/ProjectRoleService.ts @@ -332,6 +332,7 @@ export const getAuthDataProjectPermissions = async ({ role = machineMembership.role; customRole = machineMembership.customRole; + break; } default: diff --git a/backend/src/validation/workspace.ts b/backend/src/validation/workspace.ts index 3eaffd187..4f1d017ae 100644 --- a/backend/src/validation/workspace.ts +++ b/backend/src/validation/workspace.ts @@ -280,7 +280,7 @@ export const ToggleAutoCapitalizationV2 = z.object({ }) }); -export const AddWorkspaceServiceMemberV2 = z.object({ +export const AddMachineToWorkspaceV2 = z.object({ params: z.object({ workspaceId: z.string().trim(), machineId: z.string().trim() @@ -290,14 +290,24 @@ export const AddWorkspaceServiceMemberV2 = z.object({ }) }); -export const DeleteWorkspaceServiceMemberV2 = z.object({ +export const UpdateMachineWorkspaceRoleV2 = z.object({ + params: z.object({ + workspaceId: z.string().trim(), + machineId: z.string().trim() + }), + body: z.object({ + role: z.string().trim().min(1).default(NO_ACCESS), + }) +}); + +export const DeleteMachineFromWorkspaceV2 = z.object({ params: z.object({ workspaceId: z.string().trim(), machineId: z.string().trim() }) }); -export const GetWorkspaceServiceMembersV2 = z.object({ +export const GetWorkspaceMachineMembersV2 = z.object({ params: z.object({ workspaceId: z.string().trim() }), diff --git a/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentitySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentitySection.tsx index 7237d7746..ef2ec7f7c 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentitySection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentitySection.tsx @@ -31,7 +31,7 @@ export const MachineIdentitySection = withPermission( machineId }); createNotification({ - text: "Successfully deleted service token v3", + text: "Successfully deleted machine identity", type: "success" }); @@ -39,7 +39,7 @@ export const MachineIdentitySection = withPermission( } catch (err) { console.error(err); createNotification({ - text: "Failed to delete service token v3", + text: "Failed to delete machine identity", type: "error" }); } diff --git a/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx index dcf255695..aeb3ed686 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMachineIdentityTab/components/MachineIdentitySection/MachineIdentityTable.tsx @@ -103,13 +103,13 @@ export const MachineIdentityTable = ({ // }); // createNotification({ - // text: `Successfully ${isActive ? "enabled" : "disabled"} service token v3`, + // text: `Successfully ${isActive ? "enabled" : "disabled"} machine identity`, // type: "success" // }); // } catch (err) { // console.log(err); // createNotification({ - // text: `Failed to ${isActive ? "enable" : "disable"} service token v3`, + // text: `Failed to ${isActive ? "enable" : "disable"} machine identity`, // type: "error" // }); // } diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx index 174ab8d42..3fcb054d2 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx @@ -84,7 +84,7 @@ const SIMPLE_PERMISSION_OPTIONS = [ ] as const; export const OrgRoleModifySection = ({ role, onGoBack }: Props) => { - const isNonEditable = ["owner", "admin", "member"].includes(role?.slug || ""); + const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || ""); const isNewRole = !role?.slug; const { createNotification } = useNotificationContext(); diff --git a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx index 57440f2a5..b858869aa 100644 --- a/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsTableRow.tsx @@ -33,7 +33,7 @@ export const LogsTableRow = ({ return (

{`${actor.metadata.name}`}

-

Service token V3

+

Machine identity

); default: diff --git a/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/AddMachineIdentityModal.tsx b/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/AddMachineIdentityModal.tsx index a0b186071..1c9837804 100644 --- a/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/AddMachineIdentityModal.tsx +++ b/frontend/src/views/Project/MembersPage/components/MachineIdentityTab/components/MachineIdentitySection/AddMachineIdentityModal.tsx @@ -27,7 +27,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = yup.object({ machineId: yup.string().required("Machine identity id is required"), - role: yup.string().required("Machine identity role is required") + role: yup.string() }).required(); export type FormData = yup.InferType; @@ -85,10 +85,11 @@ export const AddMachineIdentityModal = ({ role }: FormData) => { try { + await addMachineToWorkspaceMutateAsync({ workspaceId, machineId, - role + role: role || undefined }); createNotification({ diff --git a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.tsx b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.tsx index 2254bdbcb..74eeec69b 100644 --- a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.tsx +++ b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.tsx @@ -9,6 +9,7 @@ import { faLock, faNetworkWired, faPuzzlePiece, + faServer, faShield, faTags, faUser, @@ -59,6 +60,12 @@ const SINGLE_PERMISSION_LIST = [ icon: faUser, formName: "member" }, + { + title: "Machine identity management", + subtitle: "Add, view, update and remove machine identities from the project", + icon: faServer, + formName: "machine-identity" + }, { title: "Webhooks", subtitle: "Webhook management control", @@ -109,7 +116,7 @@ type Props = { }; export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => { - const isNonEditable = ["admin", "member", "viewer"].includes(role?.slug || ""); + const isNonEditable = ["admin", "member", "viewer", "no-access"].includes(role?.slug || ""); const isNewRole = !role?.slug; const { createNotification } = useNotificationContext(); @@ -133,7 +140,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => { const handleRoleUpdate = async (el: TFormSchema) => { if (!role?._id) return; - + try { await updateRole({ orgId, diff --git a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils.ts b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils.ts index fec02934c..ef6a41c70 100644 --- a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils.ts +++ b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils.ts @@ -33,6 +33,7 @@ export const formSchema = z.object({ .object({ secrets: z.record(multiEnvPermissionSchema).optional(), member: generalPermissionSchema, + "machine-identity": generalPermissionSchema, role: generalPermissionSchema, integrations: generalPermissionSchema, webhooks: generalPermissionSchema,