From fd2b067e14795284c8704bcf77e24377218eb5c4 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 12 Sep 2025 16:04:17 +0530 Subject: [PATCH] feat: completed all workspace prefix routes --- .../ee/services/audit-log/audit-log-types.ts | 40 +- .../depreciated-project-membership-router.ts | 4 +- .../routes/{v2 => v1}/group-project-router.ts | 0 .../{v2 => v1}/identity-project-router.ts | 0 backend/src/server/routes/v1/index.ts | 8 +- .../routes/v1/project-membership-router.ts | 145 ++++-- .../src/server/routes/v1/project-router.ts | 234 +++++++++- .../v2/depreciated-group-project-router.ts | 363 +++++++++++++++ .../v2/depreciated-identity-project-router.ts | 418 ++++++++++++++++++ ... depreciated-project-membership-router.ts} | 6 +- ...outer.ts => depreciated-project-router.ts} | 70 +-- backend/src/server/routes/v2/index.ts | 18 +- backend/src/server/routes/v3/index.ts | 2 - .../routes/v3/secret-blind-index-router.ts | 109 ----- frontend/src/hooks/api/auditLogs/queries.tsx | 14 +- frontend/src/hooks/api/groups/queries.tsx | 2 +- frontend/src/hooks/api/tags/queries.tsx | 38 +- frontend/src/hooks/api/tags/types.ts | 2 +- .../api/workflowIntegrations/mutation.tsx | 6 +- .../hooks/api/workflowIntegrations/types.ts | 4 +- frontend/src/hooks/api/workspace/index.tsx | 2 - frontend/src/hooks/api/workspace/queries.tsx | 34 -- .../MicrosoftTeamsIntegrationForm.tsx | 2 +- .../components/SlackIntegrationForm.tsx | 2 +- 24 files changed, 1207 insertions(+), 316 deletions(-) rename backend/src/server/routes/{v2 => v1}/group-project-router.ts (100%) rename backend/src/server/routes/{v2 => v1}/identity-project-router.ts (100%) create mode 100644 backend/src/server/routes/v2/depreciated-group-project-router.ts create mode 100644 backend/src/server/routes/v2/depreciated-identity-project-router.ts rename backend/src/server/routes/v2/{project-membership-router.ts => depreciated-project-membership-router.ts} (95%) rename backend/src/server/routes/v2/{project-router.ts => depreciated-project-router.ts} (92%) delete mode 100644 backend/src/server/routes/v3/secret-blind-index-router.ts diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 10c84cc4a..0aa9ce8d8 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -146,7 +146,7 @@ export enum EventType { MOVE_SECRETS = "move-secrets", DELETE_SECRET = "delete-secret", DELETE_SECRETS = "delete-secrets", - GET_WORKSPACE_KEY = "get-workspace-key", + GET_PROJECT_KEY = "get-project-key", AUTHORIZE_INTEGRATION = "authorize-integration", UPDATE_INTEGRATION_AUTH = "update-integration-auth", UNAUTHORIZE_INTEGRATION = "unauthorize-integration", @@ -249,9 +249,9 @@ export enum EventType { UPDATE_ENVIRONMENT = "update-environment", DELETE_ENVIRONMENT = "delete-environment", GET_ENVIRONMENT = "get-environment", - ADD_WORKSPACE_MEMBER = "add-workspace-member", - ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members", - REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", + ADD_PROJECT_MEMBER = "add-project-member", + ADD_BATCH_PROJECT_MEMBER = "add-project-members", + REMOVE_PROJECT_MEMBER = "remove-project-member", CREATE_FOLDER = "create-folder", UPDATE_FOLDER = "update-folder", DELETE_FOLDER = "delete-folder", @@ -264,8 +264,8 @@ export enum EventType { CREATE_SECRET_IMPORT = "create-secret-import", UPDATE_SECRET_IMPORT = "update-secret-import", DELETE_SECRET_IMPORT = "delete-secret-import", - UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role", - UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions", + UPDATE_USER_PROJECT_ROLE = "update-user-project-role", + UPDATE_USER_PROJECT_DENIED_PERMISSIONS = "update-user-project-denied-permissions", SECRET_APPROVAL_MERGED = "secret-approval-merged", SECRET_APPROVAL_REQUEST = "secret-approval-request", SECRET_APPROVAL_CLOSED = "secret-approval-closed", @@ -664,8 +664,8 @@ interface DeleteSecretBatchEvent { }; } -interface GetWorkspaceKeyEvent { - type: EventType.GET_WORKSPACE_KEY; +interface GetProjectKeyEvent { + type: EventType.GET_PROJECT_KEY; metadata: { keyId: string; }; @@ -1557,24 +1557,24 @@ interface DeleteEnvironmentEvent { }; } -interface AddWorkspaceMemberEvent { - type: EventType.ADD_WORKSPACE_MEMBER; +interface AddProjectMemberEvent { + type: EventType.ADD_PROJECT_MEMBER; metadata: { userId: string; email: string; }; } -interface AddBatchWorkspaceMemberEvent { - type: EventType.ADD_BATCH_WORKSPACE_MEMBER; +interface AddBatchProjectMemberEvent { + type: EventType.ADD_BATCH_PROJECT_MEMBER; metadata: Array<{ userId: string; email: string; }>; } -interface RemoveWorkspaceMemberEvent { - type: EventType.REMOVE_WORKSPACE_MEMBER; +interface RemoveProjectMemberEvent { + type: EventType.REMOVE_PROJECT_MEMBER; metadata: { userId: string; email: string; @@ -1713,7 +1713,7 @@ interface DeleteSecretImportEvent { } interface UpdateUserRole { - type: EventType.UPDATE_USER_WORKSPACE_ROLE; + type: EventType.UPDATE_USER_PROJECT_ROLE; metadata: { userId: string; email: string; @@ -1723,7 +1723,7 @@ interface UpdateUserRole { } interface UpdateUserDeniedPermissions { - type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS; + type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS; metadata: { userId: string; email: string; @@ -3477,7 +3477,7 @@ export type Event = | MoveSecretsEvent | DeleteSecretEvent | DeleteSecretBatchEvent - | GetWorkspaceKeyEvent + | GetProjectKeyEvent | AuthorizeIntegrationEvent | UpdateIntegrationAuthEvent | UnauthorizeIntegrationEvent @@ -3566,9 +3566,9 @@ export type Event = | GetEnvironmentEvent | UpdateEnvironmentEvent | DeleteEnvironmentEvent - | AddWorkspaceMemberEvent - | AddBatchWorkspaceMemberEvent - | RemoveWorkspaceMemberEvent + | AddProjectMemberEvent + | AddBatchProjectMemberEvent + | RemoveProjectMemberEvent | CreateFolderEvent | UpdateFolderEvent | DeleteFolderEvent diff --git a/backend/src/server/routes/v1/depreciated-project-membership-router.ts b/backend/src/server/routes/v1/depreciated-project-membership-router.ts index 63d4d6dcd..ea7e51313 100644 --- a/backend/src/server/routes/v1/depreciated-project-membership-router.ts +++ b/backend/src/server/routes/v1/depreciated-project-membership-router.ts @@ -240,7 +240,7 @@ export const registerDepreciatedProjectMembershipRouter = async (server: Fastify projectId: req.params.workspaceId, ...req.auditLogInfo, event: { - type: EventType.ADD_BATCH_WORKSPACE_MEMBER, + type: EventType.ADD_BATCH_PROJECT_MEMBER, metadata: data.map(({ userId }) => ({ userId: userId || "", email: "" @@ -365,7 +365,7 @@ export const registerDepreciatedProjectMembershipRouter = async (server: Fastify ...req.auditLogInfo, projectId: req.params.workspaceId, event: { - type: EventType.REMOVE_WORKSPACE_MEMBER, + type: EventType.REMOVE_PROJECT_MEMBER, metadata: { userId: membership.userId, email: "" diff --git a/backend/src/server/routes/v2/group-project-router.ts b/backend/src/server/routes/v1/group-project-router.ts similarity index 100% rename from backend/src/server/routes/v2/group-project-router.ts rename to backend/src/server/routes/v1/group-project-router.ts diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v1/identity-project-router.ts similarity index 100% rename from backend/src/server/routes/v2/identity-project-router.ts rename to backend/src/server/routes/v1/identity-project-router.ts diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index caca4b6f6..4aaa6083b 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -15,9 +15,11 @@ import { registerCertRouter } from "./certificate-router"; import { registerCertificateTemplateRouter } from "./certificate-template-router"; import { registerDepreciatedProjectEnvRouter } from "./depreciated-project-env-router"; import { registerDepreciatedProjectMembershipRouter } from "./depreciated-project-membership-router"; +import { registerDepreciatedProjectRouter } from "./depreciated-project-router"; import { registerDepreciatedSecretTagRouter } from "./depreciated-secret-tag-router"; import { registerEventRouter } from "./event-router"; import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router"; +import { registerGroupProjectRouter } from "./group-project-router"; import { registerIdentityAccessTokenRouter } from "./identity-access-token-router"; import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router"; import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router"; @@ -46,10 +48,10 @@ import { registerPkiSubscriberRouter } from "./pki-subscriber-router"; import { registerProjectEnvRouter } from "./project-env-router"; import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectMembershipRouter } from "./project-membership-router"; -import { registerDepreciatedProjectRouter } from "./depreciated-project-router"; import { registerProjectRouter } from "./project-router"; import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers"; import { registerSecretFolderRouter } from "./secret-folder-router"; +import { registerSecretImportRouter } from "./secret-import-router"; import { registerSecretRequestsRouter } from "./secret-requests-router"; import { registerSecretSharingRouter } from "./secret-sharing-router"; import { registerSecretTagRouter } from "./secret-tag-router"; @@ -60,7 +62,7 @@ import { registerUserEngagementRouter } from "./user-engagement-router"; import { registerUserRouter } from "./user-router"; import { registerWebhookRouter } from "./webhook-router"; import { registerWorkflowIntegrationRouter } from "./workflow-integration-router"; -import { registerSecretImportRouter } from "./secret-import-router"; +import { registerIdentityProjectRouter } from "./identity-project-router"; export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerSsoRouter, { prefix: "/sso" }); @@ -121,6 +123,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await projectRouter.register(registerProjectMembershipRouter); await projectRouter.register(registerProjectEnvRouter); await projectRouter.register(registerSecretTagRouter); + await projectRouter.register(registerGroupProjectRouter); + await projectRouter.register(registerIdentityProjectRouter); }, { prefix: "/projects" } ); diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 95048b048..ef973de46 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -1,7 +1,8 @@ import { z } from "zod"; import { - OrgMembershipsSchema, + OrgMembershipRole, + ProjectMembershipRole, ProjectMembershipsSchema, ProjectUserMembershipRolesSchema, UserEncryptionKeysSchema, @@ -206,50 +207,72 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider rateLimit: writeLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Invite members to project", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - projectId: z.string().trim() + projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId) }), body: z.object({ - members: z - .object({ - orgMembershipId: z.string().trim(), - workspaceEncryptedKey: z.string().trim(), - workspaceEncryptedNonce: z.string().trim() - }) + emails: z + .string() + .email() .array() - .min(1) + .default([]) + .describe(PROJECT_USERS.INVITE_MEMBER.emails) + .refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"), + usernames: z + .string() + .array() + .default([]) + .describe(PROJECT_USERS.INVITE_MEMBER.usernames) + .refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase"), + roleSlugs: z.string().array().min(1).optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs) }), response: { 200: z.object({ - success: z.boolean(), - data: OrgMembershipsSchema.array() + memberships: ProjectMembershipsSchema.array() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const data = await server.services.projectMembership.addUsersToProject({ - actorId: req.permission.id, - actor: req.permission.type, + const usernamesAndEmails = [...req.body.emails, ...req.body.usernames]; + const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({ actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, actorOrgId: req.permission.orgId, - projectId: req.params.projectId, - members: req.body.members + actor: req.permission.type, + inviteeEmails: usernamesAndEmails, + orgId: req.permission.orgId, + organizationRoleSlug: OrgMembershipRole.NoAccess, + projects: [ + { + id: req.params.projectId, + projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member] + } + ] }); await server.services.auditLog.createAuditLog({ projectId: req.params.projectId, ...req.auditLogInfo, event: { - type: EventType.ADD_BATCH_WORKSPACE_MEMBER, - metadata: data.map(({ userId }) => ({ + type: EventType.ADD_BATCH_PROJECT_MEMBER, + metadata: memberships.map(({ userId, id }) => ({ userId: userId || "", + membershipId: id, email: "" })) } }); - return { data, success: true }; + return { memberships }; } }); @@ -311,23 +334,79 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider roles: req.body.roles }); - // await server.services.auditLog.createAuditLog({ - // ...req.auditLogInfo, - // projectId: req.params.projectId, - // event: { - // type: EventType.UPDATE_USER_WORKSPACE_ROLE, - // metadata: { - // userId: membership.userId, - // newRole: req.body.role, - // oldRole: membership.role, - // email: "" - // } - // } - // }); return { roles }; } }); + server.route({ + method: "DELETE", + url: "/:projectId/memberships", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Remove members from project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId) + }), + body: z.object({ + emails: z + .string() + .email() + .array() + .default([]) + .describe(PROJECT_USERS.REMOVE_MEMBER.emails) + .refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"), + usernames: z + .string() + .array() + .default([]) + .describe(PROJECT_USERS.REMOVE_MEMBER.usernames) + .refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase") + }), + response: { + 200: z.object({ + memberships: ProjectMembershipsSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const memberships = await server.services.projectMembership.deleteProjectMemberships({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + emails: req.body.emails, + usernames: req.body.usernames + }); + + for (const membership of memberships) { + // eslint-disable-next-line no-await-in-loop + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.params.projectId, + event: { + type: EventType.REMOVE_PROJECT_MEMBER, + metadata: { + userId: membership.userId, + email: "" + } + } + }); + } + return { memberships }; + } + }); + server.route({ method: "DELETE", url: "/:projectId/memberships/:membershipId", @@ -366,7 +445,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider ...req.auditLogInfo, projectId: req.params.projectId, event: { - type: EventType.REMOVE_WORKSPACE_MEMBER, + type: EventType.REMOVE_PROJECT_MEMBER, metadata: { userId: membership.userId, email: "" diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index da5d07f70..13295974a 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -29,6 +29,7 @@ import { WorkflowIntegration } from "@app/services/workflow-integration/workflow import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas"; import { sanitizedServiceTokenSchema } from "../v2/service-token-router"; +import { slugSchema } from "@app/server/lib/schemas"; const projectWithEnv = SanitizedProjectSchema.merge( z.object({ @@ -117,6 +118,83 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Create a new project", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectName: z.string().trim().describe(PROJECTS.CREATE.projectName), + projectDescription: z.string().trim().optional().describe(PROJECTS.CREATE.projectDescription), + slug: slugSchema({ min: 5, max: 36 }).optional().describe(PROJECTS.CREATE.slug), + kmsKeyId: z.string().optional(), + template: slugSchema({ field: "Template Name", max: 64 }) + .optional() + .default(InfisicalProjectTemplate.Default) + .describe(PROJECTS.CREATE.template), + type: z.nativeEnum(ProjectType).default(ProjectType.SecretManager), + shouldCreateDefaultEnvs: z.boolean().optional().default(true) + }), + response: { + 200: z.object({ + project: projectWithEnv + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.createProject({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + workspaceName: req.body.projectName, + workspaceDescription: req.body.projectDescription, + slug: req.body.slug, + kmsKeyId: req.body.kmsKeyId, + template: req.body.template, + type: req.body.type, + createDefaultEnvs: req.body.shouldCreateDefaultEnvs + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.ProjectCreated, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + orgId: project.orgId, + name: project.name, + ...req.auditLogInfo + } + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: project.id, + event: { + type: EventType.CREATE_PROJECT, + metadata: { + ...req.body, + name: req.body.projectName + } + } + }); + + return { project }; + } + }); + server.route({ method: "GET", url: "/", @@ -195,6 +273,47 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + // TODO(depri): confirm with the team + server.route({ + method: "GET", + url: "/slug/:slug", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Get project details by slug", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + slug: slugSchema({ max: 36 }).describe("The slug of the project to get.") + }), + response: { + 200: projectWithEnv + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.getAProject({ + filter: { + slug: req.params.slug, + orgId: req.permission.orgId, + type: ProjectFilterType.SLUG + }, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type + }); + + return project; + } + }); + server.route({ method: "DELETE", url: "/:projectId", @@ -246,6 +365,58 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + /* Delete a project by slug */ + server.route({ + method: "DELETE", + url: "/slug/:slug", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Delete project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to delete.") + }), + response: { + 200: SanitizedProjectSchema + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + + handler: async (req) => { + const project = await server.services.project.deleteProject({ + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.slug, + orgId: req.permission.orgId + }, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + actor: req.permission.type + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: project.id, + event: { + type: EventType.DELETE_PROJECT, + metadata: project + } + }); + + return project; + } + }); + server.route({ method: "PATCH", url: "/:projectId", @@ -862,7 +1033,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { params: z.object({ projectId: z.string().trim() }), - body: z.discriminatedUnion("integration", [ z.object({ integration: z.literal(WorkflowIntegration.SLACK), @@ -1071,4 +1241,66 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { message: "Project access request has been send to project admins" }; } }); + + /* Start upgrade of a project */ + server.route({ + method: "POST", + url: "/:projectId/upgrade", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim() + }), + body: z.object({ + userPrivateKey: z.string().trim() + }), + response: { + 200: z.void() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + await server.services.project.upgradeProject({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + projectId: req.params.projectId, + userPrivateKey: req.body.userPrivateKey + }); + } + }); + + /* Get upgrade status of project */ + server.route({ + url: "/:projectId/upgrade/status", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + status: z.string().nullable() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const status = await server.services.project.getProjectUpgradeStatus({ + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + actor: req.permission.type, + actorId: req.permission.id + }); + + return { status }; + } + }); }; diff --git a/backend/src/server/routes/v2/depreciated-group-project-router.ts b/backend/src/server/routes/v2/depreciated-group-project-router.ts new file mode 100644 index 000000000..e9f2eb2b6 --- /dev/null +++ b/backend/src/server/routes/v2/depreciated-group-project-router.ts @@ -0,0 +1,363 @@ +import { z } from "zod"; + +import { + GroupProjectMembershipsSchema, + GroupsSchema, + ProjectMembershipRole, + ProjectUserMembershipRolesSchema, + UsersSchema +} from "@app/db/schemas"; +import { EFilterReturnedUsers } from "@app/ee/services/group/group-types"; +import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs"; +import { ms } from "@app/lib/ms"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types"; + +export const registerDepreciatedGroupProjectRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectId/groups/:groupIdOrName", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Add group to project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectId), + groupIdOrName: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupIdOrName) + }), + body: z + .object({ + role: z + .string() + .trim() + .min(1) + .default(ProjectMembershipRole.NoAccess) + .describe(PROJECTS.ADD_GROUP_TO_PROJECT.role), + roles: z + .array( + z.union([ + z.object({ + role: z.string(), + isTemporary: z.literal(false).default(false) + }), + z.object({ + role: z.string(), + isTemporary: z.literal(true), + temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode), + temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), + temporaryAccessStartTime: z.string().datetime() + }) + ]) + ) + .optional() + }) + .refine((data) => data.role || data.roles, { + message: "Either role or roles must be present", + path: ["role", "roles"] + }), + response: { + 200: z.object({ + groupMembership: GroupProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const groupMembership = await server.services.groupProject.addGroupToProject({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + roles: req.body.roles || [{ role: req.body.role }], + projectId: req.params.projectId, + groupIdOrName: req.params.groupIdOrName + }); + + return { groupMembership }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/groups/:groupId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Update group in project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.projectId), + groupId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.groupId) + }), + body: z.object({ + roles: z + .array( + z.union([ + z.object({ + role: z.string(), + isTemporary: z.literal(false).default(false) + }), + z.object({ + role: z.string(), + isTemporary: z.literal(true), + temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode), + temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), + temporaryAccessStartTime: z.string().datetime() + }) + ]) + ) + .min(1) + .describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.roles) + }), + response: { + 200: z.object({ + roles: ProjectUserMembershipRolesSchema.array() + }) + } + }, + handler: async (req) => { + const roles = await server.services.groupProject.updateGroupInProject({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + groupId: req.params.groupId, + roles: req.body.roles + }); + + return { roles }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/groups/:groupId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Remove group from project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.projectId), + groupId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.groupId) + }), + response: { + 200: z.object({ + groupMembership: GroupProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const groupMembership = await server.services.groupProject.removeGroupFromProject({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + groupId: req.params.groupId, + projectId: req.params.projectId + }); + + return { groupMembership }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/groups", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Return list of groups in project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_GROUPS_IN_PROJECT.projectId) + }), + response: { + 200: z.object({ + groupMemberships: z + .object({ + id: z.string(), + groupId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + group: GroupsSchema.pick({ name: true, id: true, slug: true }) + }) + .array() + }) + } + }, + handler: async (req) => { + const groupMemberships = await server.services.groupProject.listGroupsInProject({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId + }); + + return { groupMemberships }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/groups/:groupId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Return project group", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim(), + groupId: z.string().trim() + }), + response: { + 200: z.object({ + groupMembership: z.object({ + id: z.string(), + groupId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + group: GroupsSchema.pick({ name: true, id: true, slug: true }) + }) + }) + } + }, + handler: async (req) => { + const groupMembership = await server.services.groupProject.getGroupInProject({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.params + }); + + return { groupMembership }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/groups/:groupId/users", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Return project group users", + params: z.object({ + projectId: z.string().trim().describe(GROUPS.LIST_USERS.projectId), + groupId: z.string().trim().describe(GROUPS.LIST_USERS.id) + }), + querystring: z.object({ + offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset), + limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit), + username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username), + search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search), + filter: z.nativeEnum(EFilterReturnedUsers).optional().describe(GROUPS.LIST_USERS.filterUsers) + }), + response: { + 200: z.object({ + users: UsersSchema.pick({ + email: true, + username: true, + firstName: true, + lastName: true, + id: true + }) + .merge( + z.object({ + isPartOfGroup: z.boolean(), + joinedGroupAt: z.date().nullable() + }) + ) + .array(), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { users, totalCount } = await server.services.groupProject.listProjectGroupUsers({ + id: req.params.groupId, + projectId: req.params.projectId, + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query + }); + + return { users, totalCount }; + } + }); +}; diff --git a/backend/src/server/routes/v2/depreciated-identity-project-router.ts b/backend/src/server/routes/v2/depreciated-identity-project-router.ts new file mode 100644 index 000000000..4182b9a80 --- /dev/null +++ b/backend/src/server/routes/v2/depreciated-identity-project-router.ts @@ -0,0 +1,418 @@ +import { z } from "zod"; + +import { + IdentitiesSchema, + IdentityProjectMembershipsSchema, + ProjectMembershipRole, + ProjectUserMembershipRolesSchema +} from "@app/db/schemas"; +import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs"; +import { BadRequestError } from "@app/lib/errors"; +import { ms } from "@app/lib/ms"; +import { OrderByDirection } from "@app/lib/types"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types"; +import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types"; + +import { SanitizedProjectSchema } from "../sanitizedSchemas"; + +export const registerDepreciatedIdentityProjectRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Create project identity membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim(), + identityId: z.string().trim() + }), + body: z.object({ + // @depreciated + role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess), + roles: z + .array( + z.union([ + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z + .literal(false) + .default(false) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role) + }), + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryMode: z + .nativeEnum(ProjectUserMembershipTemporaryMode) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role) + }) + ]) + ) + .describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description) + .optional() + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const { role, roles } = req.body; + if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" }); + + const identityMembership = await server.services.identityProject.createProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityId: req.params.identityId, + projectId: req.params.projectId, + roles: roles || [{ role }] + }); + return { identityMembership }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Update project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId) + }), + body: z.object({ + roles: z + .array( + z.union([ + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z + .literal(false) + .default(false) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary) + }), + z.object({ + role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role), + isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary), + temporaryMode: z + .nativeEnum(ProjectUserMembershipTemporaryMode) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode), + temporaryRange: z + .string() + .refine((val) => ms(val) > 0, "Temporary range must be a positive number") + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange), + temporaryAccessStartTime: z + .string() + .datetime() + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime) + }) + ]) + ) + .min(1) + .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description) + }), + response: { + 200: z.object({ + roles: ProjectUserMembershipRolesSchema.array() + }) + } + }, + handler: async (req) => { + const roles = await server.services.identityProject.updateProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityId: req.params.identityId, + projectId: req.params.projectId, + roles: req.body.roles + }); + return { roles }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Delete project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId) + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.deleteProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityId: req.params.identityId, + projectId: req.params.projectId + }); + return { identityMembership }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identity-memberships", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Return project identity memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId) + }), + querystring: z.object({ + offset: z.coerce + .number() + .min(0) + .default(0) + .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset) + .optional(), + limit: z.coerce + .number() + .min(1) + .max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added + .default(100) + .describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit) + .optional(), + orderBy: z + .nativeEnum(ProjectIdentityOrderBy) + .default(ProjectIdentityOrderBy.Name) + .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy) + .optional(), + orderDirection: z + .nativeEnum(OrderByDirection) + .default(OrderByDirection.ASC) + .describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection) + .optional(), + search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional() + }), + response: { + 200: z.object({ + identityMemberships: z + .object({ + id: z.string(), + identityId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + .array(), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + limit: req.query.limit, + offset: req.query.offset, + orderBy: req.query.orderBy, + orderDirection: req.query.orderDirection, + search: req.query.search + }); + + return { identityMemberships, totalCount }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identity-memberships/:identityId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + description: "Return project identity membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId), + identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId) + }), + response: { + 200: z.object({ + identityMembership: z.object({ + id: z.string(), + identityId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + identityId: req.params.identityId + }); + return { identityMembership }; + } + }); + + server.route({ + method: "GET", + url: "/identity-memberships/:identityMembershipId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.ProjectIdentities], + params: z.object({ + identityMembershipId: z.string().trim() + }), + response: { + 200: z.object({ + identityMembership: z.object({ + id: z.string(), + identityId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + roles: z.array( + z.object({ + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() + }) + ), + identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ + authMethods: z.array(z.string()) + }), + project: SanitizedProjectSchema.pick({ name: true, id: true }) + }) + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.getProjectIdentityByMembershipId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityMembershipId: req.params.identityMembershipId + }); + return { identityMembership }; + } + }); +}; diff --git a/backend/src/server/routes/v2/project-membership-router.ts b/backend/src/server/routes/v2/depreciated-project-membership-router.ts similarity index 95% rename from backend/src/server/routes/v2/project-membership-router.ts rename to backend/src/server/routes/v2/depreciated-project-membership-router.ts index 76f1e9c5e..11ebc44f2 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/depreciated-project-membership-router.ts @@ -7,7 +7,7 @@ import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => { +export const registerDepreciatedProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:projectId/memberships", @@ -71,7 +71,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider projectId: req.params.projectId, ...req.auditLogInfo, event: { - type: EventType.ADD_BATCH_WORKSPACE_MEMBER, + type: EventType.ADD_BATCH_PROJECT_MEMBER, metadata: memberships.map(({ userId, id }) => ({ userId: userId || "", membershipId: id, @@ -141,7 +141,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider ...req.auditLogInfo, projectId: req.params.projectId, event: { - type: EventType.REMOVE_WORKSPACE_MEMBER, + type: EventType.REMOVE_PROJECT_MEMBER, metadata: { userId: membership.userId, email: "" diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/depreciated-project-router.ts similarity index 92% rename from backend/src/server/routes/v2/project-router.ts rename to backend/src/server/routes/v2/depreciated-project-router.ts index 8b9091364..a8abab54c 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/depreciated-project-router.ts @@ -35,7 +35,8 @@ const projectWithEnv = SanitizedProjectSchema.extend({ kmsSecretManagerKeyId: z.string().nullable().optional() }); -export const registerProjectRouter = async (server: FastifyZodProvider) => { +export const registerDepreciatedProjectRouter = async (server: FastifyZodProvider) => { + // depreciated /* Get project key */ server.route({ method: "GET", @@ -72,7 +73,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { ...req.auditLogInfo, projectId: req.params.workspaceId, event: { - type: EventType.GET_WORKSPACE_KEY, + type: EventType.GET_PROJECT_KEY, metadata: { keyId: key?.id as string } @@ -83,68 +84,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); - /* Start upgrade of a project */ - server.route({ - method: "POST", - url: "/:projectId/upgrade", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - body: z.object({ - userPrivateKey: z.string().trim() - }), - response: { - 200: z.void() - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - await server.services.project.upgradeProject({ - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - projectId: req.params.projectId, - userPrivateKey: req.body.userPrivateKey - }); - } - }); - - /* Get upgrade status of project */ - server.route({ - url: "/:projectId/upgrade/status", - method: "GET", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - response: { - 200: z.object({ - status: z.string().nullable() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const status = await server.services.project.getProjectUpgradeStatus({ - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.projectId, - actor: req.permission.type, - actorId: req.permission.id - }); - - return { status }; - } - }); - /* Create new project */ server.route({ method: "POST", @@ -224,6 +163,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); /* Delete a project by slug */ + // moved to DELETE /v1/projects/slug/:slug server.route({ method: "DELETE", url: "/:slug", @@ -276,6 +216,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); /* Get a project by slug */ + // moved to GET /v1/projects/slug/:slug server.route({ method: "GET", url: "/:slug", @@ -337,7 +278,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 200: SanitizedProjectSchema } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const project = await server.services.project.updateProject({ diff --git a/backend/src/server/routes/v2/index.ts b/backend/src/server/routes/v2/index.ts index 93c422d15..2735dfcc9 100644 --- a/backend/src/server/routes/v2/index.ts +++ b/backend/src/server/routes/v2/index.ts @@ -1,13 +1,13 @@ import { registerCaRouter } from "./certificate-authority-router"; -import { registerGroupProjectRouter } from "./group-project-router"; +import { registerDepreciatedGroupProjectRouter } from "./depreciated-group-project-router"; import { registerIdentityOrgRouter } from "./identity-org-router"; -import { registerIdentityProjectRouter } from "./identity-project-router"; +import { registerDepreciatedIdentityProjectRouter } from "./depreciated-identity-project-router"; import { registerMfaRouter } from "./mfa-router"; import { registerOrgRouter } from "./organization-router"; import { registerPasswordRouter } from "./password-router"; import { registerPkiTemplatesRouter } from "./pki-templates-router"; -import { registerProjectMembershipRouter } from "./project-membership-router"; -import { registerProjectRouter } from "./project-router"; +import { registerDepreciatedProjectMembershipRouter } from "./depreciated-project-membership-router"; +import { registerDepreciatedProjectRouter } from "./depreciated-project-router"; import { registerServiceTokenRouter } from "./service-token-router"; import { registerUserRouter } from "./user-router"; @@ -32,12 +32,14 @@ export const registerV2Routes = async (server: FastifyZodProvider) => { }, { prefix: "/organizations" } ); + + // moved to v1/projects await server.register( async (projectServer) => { - await projectServer.register(registerProjectRouter); - await projectServer.register(registerIdentityProjectRouter); - await projectServer.register(registerGroupProjectRouter); - await projectServer.register(registerProjectMembershipRouter); + await projectServer.register(registerDepreciatedProjectRouter); + await projectServer.register(registerDepreciatedIdentityProjectRouter); + await projectServer.register(registerDepreciatedGroupProjectRouter); + await projectServer.register(registerDepreciatedProjectMembershipRouter); }, { prefix: "/workspace" } ); diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index 1c31741a1..16880e840 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -1,6 +1,5 @@ import { registerExternalMigrationRouter } from "./external-migration-router"; import { registerLoginRouter } from "./login-router"; -import { registerSecretBlindIndexRouter } from "./secret-blind-index-router"; import { registerSecretRouter } from "./secret-router"; import { registerSignupRouter } from "./signup-router"; import { registerUserRouter } from "./user-router"; @@ -10,6 +9,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerLoginRouter, { prefix: "/auth" }); await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerSecretRouter, { prefix: "/secrets" }); - await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" }); }; diff --git a/backend/src/server/routes/v3/secret-blind-index-router.ts b/backend/src/server/routes/v3/secret-blind-index-router.ts deleted file mode 100644 index cfb27a58e..000000000 --- a/backend/src/server/routes/v3/secret-blind-index-router.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { z } from "zod"; - -import { SecretsSchema } from "@app/db/schemas"; -import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; - -export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => { - server.route({ - method: "GET", - url: "/:projectId/secrets/blind-index-status", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - response: { - 200: z.boolean() - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({ - projectId: req.params.projectId, - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId - }); - return count === 0; - } - }); - - server.route({ - method: "GET", - url: "/:projectId/secrets", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - response: { - 200: z.object({ - secrets: SecretsSchema.omit({ secretBlindIndex: true }) - .merge( - z.object({ - environment: z.string(), - workspace: z.string() - }) - ) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const secrets = await server.services.secretBlindIndex.getProjectSecrets({ - projectId: req.params.projectId, - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId - }); - return { secrets }; - } - }); - - server.route({ - method: "POST", - url: "/:projectId/secrets/names", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - body: z.object({ - secretsToUpdate: z - .object({ - secretName: z.string().trim(), - secretId: z.string().trim() - }) - .array() - }), - response: { - 200: z.object({ - message: z.string() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - await server.services.secretBlindIndex.updateProjectSecretName({ - projectId: req.params.projectId, - secretsToUpdate: req.body.secretsToUpdate, - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId - }); - return { message: "Successfully named workspace secrets" }; - } - }); -}; diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 8cb438c92..5e25d8104 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -7,10 +7,10 @@ import { TReactQueryOptions } from "@app/types/reactQuery"; import { Actor, AuditLog, TGetAuditLogsFilter } from "./types"; export const auditLogKeys = { - getAuditLogs: (workspaceId: string | null, filters: TGetAuditLogsFilter) => - [{ workspaceId, filters }, "audit-logs"] as const, - getAuditLogActorFilterOpts: (workspaceId: string) => - [{ workspaceId }, "audit-log-actor-filters"] as const + getAuditLogs: (projectId: string | null, filters: TGetAuditLogsFilter) => + [{ projectId, filters }, "audit-logs"] as const, + getAuditLogActorFilterOpts: (projectId: string) => + [{ projectId }, "audit-log-actor-filters"] as const }; export const useGetAuditLogs = ( @@ -56,12 +56,12 @@ export const useGetAuditLogs = ( }); }; -export const useGetAuditLogActorFilterOpts = (workspaceId: string) => { +export const useGetAuditLogActorFilterOpts = (projectId: string) => { return useQuery({ - queryKey: auditLogKeys.getAuditLogActorFilterOpts(workspaceId), + queryKey: auditLogKeys.getAuditLogActorFilterOpts(projectId), queryFn: async () => { const { data } = await apiRequest.get<{ actors: Actor[] }>( - `/api/v1/workspace/${workspaceId}/audit-logs/filters/actors` + `/api/v1/projects/${projectId}/audit-logs/filters/actors` ); return data.actors; } diff --git a/frontend/src/hooks/api/groups/queries.tsx b/frontend/src/hooks/api/groups/queries.tsx index 6fbe0ec13..ca524066f 100644 --- a/frontend/src/hooks/api/groups/queries.tsx +++ b/frontend/src/hooks/api/groups/queries.tsx @@ -138,7 +138,7 @@ export const useListProjectGroupUsers = ({ }); const { data } = await apiRequest.get<{ users: TGroupUser[]; totalCount: number }>( - `/api/v2/workspace/${projectId}/groups/${id}/users`, + `/api/v1/projects/${projectId}/groups/${id}/users`, { params } diff --git a/frontend/src/hooks/api/tags/queries.tsx b/frontend/src/hooks/api/tags/queries.tsx index bdbb9659d..0bd4b09a9 100644 --- a/frontend/src/hooks/api/tags/queries.tsx +++ b/frontend/src/hooks/api/tags/queries.tsx @@ -4,23 +4,23 @@ import { apiRequest } from "@app/config/request"; import { CreateTagDTO, DeleteTagDTO, UserWsTags, WsTag } from "./types"; -const workspaceTags = { - getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const +const projectTags = { + getWsTags: (projectID: string) => ["project-tags", { projectID }] as const }; -const fetchWsTag = async (workspaceID: string) => { - const { data } = await apiRequest.get<{ workspaceTags: UserWsTags }>( - `/api/v1/workspace/${workspaceID}/tags` +const fetchWsTag = async (projectID: string) => { + const { data } = await apiRequest.get<{ projectTags: UserWsTags }>( + `/api/v1/projects/${projectID}/tags` ); - return data.workspaceTags; + return data.projectTags; }; -export const useGetWsTags = (workspaceID: string) => { +export const useGetWsTags = (projectID: string) => { return useQuery({ - queryKey: workspaceTags.getWsTags(workspaceID), - queryFn: () => fetchWsTag(workspaceID), - enabled: Boolean(workspaceID) + queryKey: projectTags.getWsTags(projectID), + queryFn: () => fetchWsTag(projectID), + enabled: Boolean(projectID) }); }; @@ -28,18 +28,18 @@ export const useCreateWsTag = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ workspaceID, tagColor, tagSlug }) => { - const { data } = await apiRequest.post<{ workspaceTag: WsTag }>( - `/api/v1/workspace/${workspaceID}/tags`, + mutationFn: async ({ projectID, tagColor, tagSlug }) => { + const { data } = await apiRequest.post<{ projectTag: WsTag }>( + `/api/v1/projects/${projectID}/tags`, { color: tagColor || "", slug: tagSlug } ); - return data.workspaceTag; + return data.projectTag; }, onSuccess: (tagData) => { - queryClient.invalidateQueries({ queryKey: workspaceTags.getWsTags(tagData?.projectId) }); + queryClient.invalidateQueries({ queryKey: projectTags.getWsTags(tagData?.projectId) }); } }); }; @@ -49,13 +49,13 @@ export const useDeleteWsTag = () => { return useMutation({ mutationFn: async ({ tagID, projectId }) => { - const { data } = await apiRequest.delete<{ workspaceTag: WsTag }>( - `/api/v1/workspace/${projectId}/tags/${tagID}` + const { data } = await apiRequest.delete<{ projectTag: WsTag }>( + `/api/v1/projects/${projectId}/tags/${tagID}` ); - return data.workspaceTag; + return data.projectTag; }, onSuccess: (tagData) => { - queryClient.invalidateQueries({ queryKey: workspaceTags.getWsTags(tagData?.projectId) }); + queryClient.invalidateQueries({ queryKey: projectTags.getWsTags(tagData?.projectId) }); } }); }; diff --git a/frontend/src/hooks/api/tags/types.ts b/frontend/src/hooks/api/tags/types.ts index 72d710cfa..c0994b92c 100644 --- a/frontend/src/hooks/api/tags/types.ts +++ b/frontend/src/hooks/api/tags/types.ts @@ -13,7 +13,7 @@ export type WsTag = { export type WorkspaceTag = { id: string; name: string; slug: string }; export type CreateTagDTO = { - workspaceID: string; + projectID: string; tagSlug: string; tagColor: string; }; diff --git a/frontend/src/hooks/api/workflowIntegrations/mutation.tsx b/frontend/src/hooks/api/workflowIntegrations/mutation.tsx index 49aaab815..1a3927eff 100644 --- a/frontend/src/hooks/api/workflowIntegrations/mutation.tsx +++ b/frontend/src/hooks/api/workflowIntegrations/mutation.tsx @@ -118,13 +118,13 @@ export const useUpdateProjectWorkflowIntegrationConfig = () => { return useMutation({ mutationFn: async (dto: TUpdateProjectWorkflowIntegrationConfigDTO) => { const { data } = await apiRequest.put( - `/api/v1/workspace/${dto.workspaceId}/workflow-integration`, + `/api/v1/projects/${dto.projectId}/workflow-integration`, dto ); return data; }, - onSuccess: (_, { workspaceId, integration }) => { + onSuccess: (_, { projectId: workspaceId, integration }) => { queryClient.invalidateQueries({ queryKey: projectKeys.getWorkspaceWorkflowIntegrationConfig(workspaceId, integration) }); @@ -138,7 +138,7 @@ export const useDeleteProjectWorkflowIntegration = () => { return useMutation({ mutationFn: async (dto: TDeleteProjectWorkflowIntegrationDTO) => { const { data } = await apiRequest.delete( - `/api/v1/workspace/${dto.projectId}/workflow-integration/${dto.integration}/${dto.integrationId}` + `/api/v1/projects/${dto.projectId}/workflow-integration/${dto.integration}/${dto.integrationId}` ); return data; diff --git a/frontend/src/hooks/api/workflowIntegrations/types.ts b/frontend/src/hooks/api/workflowIntegrations/types.ts index 4d2baf4bf..668850124 100644 --- a/frontend/src/hooks/api/workflowIntegrations/types.ts +++ b/frontend/src/hooks/api/workflowIntegrations/types.ts @@ -106,7 +106,7 @@ export type ProjectWorkflowIntegrationConfig = export type TUpdateProjectWorkflowIntegrationConfigDTO = | { integration: WorkflowIntegrationPlatform.SLACK; - workspaceId: string; + projectId: string; integrationId: string; isAccessRequestNotificationEnabled: boolean; accessRequestChannels: string; @@ -115,7 +115,7 @@ export type TUpdateProjectWorkflowIntegrationConfigDTO = } | { integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS; - workspaceId: string; + projectId: string; integrationId: string; isAccessRequestNotificationEnabled: boolean; isSecretRequestNotificationEnabled: boolean; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index 02d17f14c..a75789da5 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -25,7 +25,6 @@ export { useGetWorkspaceIdentityMemberships, useGetWorkspaceIndexStatus, useGetWorkspaceIntegrations, - useGetWorkspaceSecrets, useGetWorkspaceUserDetails, useGetWorkspaceUsers, useGetWorkspaceWorkflowIntegrationConfig, @@ -41,7 +40,6 @@ export { useListWorkspaceSshCertificateTemplates, useListWorkspaceSshHostGroups, useListWorkspaceSshHosts, - useNameWorkspaceSecrets, useSearchProjects, useToggleAutoCapitalization, useUpdateIdentityWorkspaceRole, diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index cdda24ee4..0e312967f 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -15,7 +15,6 @@ import { TIntegration } from "../integrations/types"; import { TPkiAlert } from "../pkiAlerts/types"; import { TPkiCollection } from "../pkiCollections/types"; import { TPkiSubscriber } from "../pkiSubscriber/types"; -import { EncryptedSecret } from "../secrets/types"; import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types"; import { TSshCertificateTemplate } from "../sshCertificateTemplates/types"; import { TSshHost } from "../sshHost/types"; @@ -32,7 +31,6 @@ import { CreateWorkspaceDTO, DeleteEnvironmentDTO, DeleteWorkspaceDTO, - NameWorkspaceSecretsDTO, ProjectIdentityOrderBy, ProjectType, TGetUpgradeProjectStatusDTO, @@ -73,14 +71,6 @@ const fetchProjectUpgradeStatus = async (projectId: string) => { return data; }; -export const fetchWorkspaceSecrets = async (projectId: string) => { - const { - data: { secrets } - } = await apiRequest.get<{ secrets: EncryptedSecret[] }>(`/api/v3/projects/${projectId}/secrets`); - - return secrets; -}; - export const useUpgradeProject = () => { const queryClient = useQueryClient(); @@ -129,14 +119,6 @@ export const useGetWorkspaceIndexStatus = (projectId: string) => { }); }; -export const useGetWorkspaceSecrets = (projectId: string) => { - return useQuery({ - queryKey: projectKeys.getWorkspaceSecrets(projectId), - queryFn: () => fetchWorkspaceSecrets(projectId), - enabled: true - }); -}; - export const useGetWorkspaceById = ( projectId: string, dto?: { refetchInterval?: number | false } @@ -192,22 +174,6 @@ export const useGetUserWorkspaceMemberships = (orgId: string) => enabled: Boolean(orgId) }); -export const useNameWorkspaceSecrets = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ projectId, secretsToUpdate }) => - apiRequest.post(`/api/v3/projects/${projectId}/secrets/names`, { - secretsToUpdate - }), - onSuccess: (_, variables) => { - queryClient.invalidateQueries({ - queryKey: projectKeys.getWorkspaceIndexStatus(variables.projectId) - }); - } - }); -}; - const fetchWorkspaceAuthorization = async (projectId: string) => { const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>( `/api/v1/projects/${projectId}/authorizations` diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx index aab932b8d..2bf4bb7db 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx @@ -136,7 +136,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => { } await updateProjectMicrosoftTeamsConfig({ - workspaceId: currentWorkspace.id, + projectId: currentWorkspace.id, isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled, isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled, ...(data.isAccessRequestNotificationEnabled && { diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx index 6bcaa6ac1..74c9ce55e 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx @@ -83,7 +83,7 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { await updateProjectSlackConfig({ ...data, - workspaceId: currentWorkspace.id, + projectId: currentWorkspace.id, integration: WorkflowIntegrationPlatform.SLACK, integrationId: data.slackIntegrationId, accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "),