From e445970f3613344694c4844c6ec40ea3a44e725f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:27:53 +0400 Subject: [PATCH] More gohst user --- .../src/ee/services/license/licence-fns.ts | 8 +- backend/src/lib/project/index.ts | 21 ++ .../src/server/routes/v1/project-router.ts | 1 + .../src/server/routes/v3/project-router.ts | 196 ++---------------- .../project-membership-service.ts | 1 + 5 files changed, 49 insertions(+), 178 deletions(-) diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 9b1d4c203..b8b02ec54 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -9,11 +9,11 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ _id: null, slug: null, tier: -1, - workspaceLimit: null, + workspaceLimit: 5000, workspacesUsed: 0, - memberLimit: null, + memberLimit: 5000, membersUsed: 0, - environmentLimit: null, + environmentLimit: 5000, environmentsUsed: 0, secretVersioning: true, pitRecovery: false, @@ -28,7 +28,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ status: null, trial_end: null, has_used_trial: true, - secretApproval: false, + secretApproval: true, secretRotation: true }); diff --git a/backend/src/lib/project/index.ts b/backend/src/lib/project/index.ts index f680dc7eb..d4e97ba17 100644 --- a/backend/src/lib/project/index.ts +++ b/backend/src/lib/project/index.ts @@ -1,3 +1,5 @@ +import crypto from "crypto"; + import { ProjectMembershipRole, TProjectKeys } from "@app/db/schemas"; import { decryptAsymmetric, encryptAsymmetric } from "../crypto"; @@ -37,3 +39,22 @@ export const createWsMembers = ({ members, decryptKey, userPrivateKey }: AddUser return newWsMembers; }; + +type TCreateWorkspaceKeyDTO = { + publicKey: string; + privateKey: string; +}; + +export const createWorkspaceKey = ({ publicKey, privateKey }: TCreateWorkspaceKeyDTO) => { + // 3. Create a random key that we'll use as the project key. + const randomBytes = crypto.randomBytes(16).toString("hex"); + + // 4. Encrypt the project key with the users key pair. + const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = encryptAsymmetric( + randomBytes, + publicKey, + privateKey + ); + + return { key: encryptedProjectKey, iv: encryptedProjectKeyIv }; +}; diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 0a5f8d85e..96452a549 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -282,6 +282,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + // Is this actually used..? server.route({ url: "/:workspaceId/integrations", method: "GET", diff --git a/backend/src/server/routes/v3/project-router.ts b/backend/src/server/routes/v3/project-router.ts index db39c5677..c1759f718 100644 --- a/backend/src/server/routes/v3/project-router.ts +++ b/backend/src/server/routes/v3/project-router.ts @@ -1,15 +1,8 @@ -import { ForbiddenError } from "@casl/ability"; -import crypto from "crypto"; import { z } from "zod"; -import { ProjectMembershipRole, ProjectsSchema } from "@app/db/schemas"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; -import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { encryptAsymmetric } from "@app/lib/crypto"; -import { BadRequestError } from "@app/lib/errors"; -import { createWsMembers } from "@app/lib/project"; +import { ProjectMembershipsSchema, ProjectsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { authRateLimit } from "@app/server/config/rateLimiter"; -import { ActorType } from "@app/services/auth/auth-type"; const projectWithEnv = ProjectsSchema.merge( z.object({ @@ -29,7 +22,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ projectName: z.string().trim(), - inviteMemberEmails: z.array(z.string().email()).optional(), organizationId: z.string().trim() }), response: { @@ -39,110 +31,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { permission } = await server.services.permission.getOrgPermission( - req.permission.type, - req.permission.id, - req.body.organizationId - ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); - - // 2. Create a new project (will set the e2ee db field to false). - const { project, ghostUser } = await server.services.project.createProject({ + const project = await server.services.project.createProject({ actorId: req.permission.id, actor: req.permission.type, orgId: req.body.organizationId, workspaceName: req.body.projectName }); - // 3. Create a random key that we'll use as the project key. - const randomBytes = crypto.randomBytes(16).toString("hex"); - - // 4. Encrypt the project key with the users key pair. - const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = encryptAsymmetric( - randomBytes, - ghostUser.keys.publicKey, - ghostUser.keys.plainPrivateKey - ); - - // 4. Save the project key for the ghost user. - await server.services.projectKey.uploadProjectKeys({ - projectId: project.id, - actor: req.permission.type, - actorId: ghostUser.user.id, - nonce: encryptedProjectKeyIv, - receiverId: ghostUser.user.id, - encryptedKey: encryptedProjectKey - }); - - // 5. Create a bot for the project. - const bot = await server.services.projectBot.findBotByProjectId({ - actorId: ghostUser.user.id, - actor: req.permission.type, - projectId: project.id, - - // We set the publicKey and privateKey of the bot to the same as the ghost user. - // We do this because we'll need to access the private key again later, when adding new members to the project. - publicKey: ghostUser.keys.publicKey, - privateKey: ghostUser.keys.plainPrivateKey - }); - - // 6. Activate the bot. - await server.services.projectBot.setBotActiveState({ - botKey: { - encryptedKey: encryptedProjectKey, - nonce: encryptedProjectKeyIv - }, - actorId: ghostUser.user.id, - isActive: true, - actor: req.permission.type, - botId: bot.id - }); - - // 7. get the current user & org membership - const user = await server.services.user.getMe(req.permission.id); - const userOrgMembership = await server.services.permission.getUserOrgPermission(user.id, req.body.organizationId); - - // 7. Get the latest key from the ghost! - const latestKey = await server.services.projectKey.getLatestProjectKey({ - actorId: ghostUser.user.id, - actor: req.permission.type, - projectId: project.id - }); - - if (!latestKey) throw new Error("Failed to get latest key"); - - // If the project is being created by a user, add the user to the project as an admin - if (req.permission.type === ActorType.USER) { - const projectAdmin = createWsMembers({ - decryptKey: latestKey, - members: [ - { - userPublicKey: user.publicKey, - orgMembershipId: userOrgMembership.membership.id, - projectMembershipRole: ProjectMembershipRole.Admin // <-- Make the first user an admin - } - ], - userPrivateKey: ghostUser.keys.plainPrivateKey - }); - - await server.services.projectMembership.addUsersToProject({ - projectId: project.id, - actorId: ghostUser.user.id, - actor: req.permission.type, - members: projectAdmin - }); - } - // If the project is being created by an identity, add the identity to the project as an admin - else if (req.permission.type === ActorType.IDENTITY) { - await server.services.identityProject.createProjectIdentity({ - actor: ActorType.IDENTITY, - actorId: ghostUser.user.id, - identityId: req.permission.id, - projectId: project.id, - role: ProjectMembershipRole.Admin - }); - } - return { project }; } }); @@ -159,82 +54,35 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), body: z.object({ emails: z.string().email().array() - }) + }), + response: { + 200: z.object({ + memberships: ProjectMembershipsSchema.array() + }) + } }, handler: async (req) => { - const { permission } = await server.services.permission.getProjectPermission( - req.permission.type, - req.permission.id, - req.params.projectId - ); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); - - const project = await server.services.project.getAProject({ + const memberships = await server.services.projectMembership.addUsersToProjectNonE2EE({ + projectId: req.params.projectId, actorId: req.permission.id, actor: req.permission.type, - projectId: req.params.projectId - }); - - const ghostUser = await server.services.project.findProjectGhostUser(req.params.projectId); - - if (!ghostUser) { - throw new BadRequestError({ - message: "Failed" // TODO: Add a message - }); - } - - const latestKey = await server.services.projectKey.getLatestProjectKey({ - actorId: ghostUser.id, - actor: ActorType.USER, - projectId: req.params.projectId - }); - - if (!latestKey) { - throw new BadRequestError({ - message: "Failed to find project key" - }); - } - - const bot = await server.services.projectBot.findBotByProjectId({ - actor: req.permission.type, - actorId: req.permission.id, - projectId: req.params.projectId - }); - - // We get the bot private key, because the bot private key is the same as the ghost user's private key. - const botPrivateKey = server.services.projectBot.getBotPrivateKey({ bot }); - - const members = await server.services.org.findOrgMembersByEmail({ - actor: req.permission.type, - actorId: req.permission.id, - orgId: project.orgId, emails: req.body.emails }); - if (members.length !== req.body.emails.length) { - throw new BadRequestError({ - message: "Some users are not part of the organization" - }); - } - - const wsMembers = createWsMembers({ - members: members.map((membership) => ({ - orgMembershipId: membership.id, - projectMembershipRole: ProjectMembershipRole.Member, - userPublicKey: membership.user.publicKey - })), - decryptKey: latestKey, - userPrivateKey: botPrivateKey - }); - - await server.services.projectMembership.addUsersToProject({ + await server.services.auditLog.createAuditLog({ projectId: req.params.projectId, - actorId: ghostUser.id, // We set the actor ID to the ghost user, because this is used as senderId in the project key sharing - actor: ActorType.USER, - members: wsMembers + ...req.auditLogInfo, + event: { + type: EventType.ADD_BATCH_WORKSPACE_MEMBER, + metadata: memberships.map(({ userId, id }) => ({ + userId: userId || "", + membershipId: id, + email: "" + })) + } }); - return {}; + return { memberships }; } }); }; diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index e96247c97..0450450d8 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -17,6 +17,7 @@ import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; +import { createWsMembers } from "@app/lib/project"; import { ActorType } from "../auth/auth-type"; import { TOrgDALFactory } from "../org/org-dal";