diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index 6a061a635..21d57d359 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -1,4 +1,5 @@ import { registerLoginRouter } from "./login-router"; +import { registerProjectRouter } from "./project-router"; import { registerSecretBlindIndexRouter } from "./secret-blind-index-router"; import { registerSecretRouter } from "./secret-router"; import { registerSignupRouter } from "./signup-router"; @@ -10,4 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerSecretRouter, { prefix: "/secrets" }); await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); + await server.register(registerProjectRouter, { prefix: "/projects" }); }; diff --git a/backend/src/server/routes/v3/project-router.ts b/backend/src/server/routes/v3/project-router.ts new file mode 100644 index 000000000..5f83167a7 --- /dev/null +++ b/backend/src/server/routes/v3/project-router.ts @@ -0,0 +1,126 @@ +import crypto from "crypto"; +import { z } from "zod"; + +import { ProjectMembershipRole, ProjectsSchema } from "@app/db/schemas"; +import { encryptAsymmetric } from "@app/lib/crypto"; +import { createWsMembers } from "@app/lib/project"; +import { authRateLimit } from "@app/server/config/rateLimiter"; + +const projectWithEnv = ProjectsSchema.merge( + z.object({ + _id: z.string(), + environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array() + }) +); + +export const registerProjectRouter = async (server: FastifyZodProvider) => { + /* Create new project */ + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: authRateLimit + }, + schema: { + body: z.object({ + projectName: z.string().trim(), + inviteAllOrgMembers: z.boolean(), + organizationId: z.string().trim() + }), + response: { + 200: z.object({ + workspace: projectWithEnv + }) + } + }, + handler: async (req) => { + // 1. create the ghost user and add it to the org as admin + const ghost = await server.services.org.addGhostUser(req.body.organizationId); + + // 2. create the workspace + const workspace = await server.services.project.createProject({ + actorId: ghost.user.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"); + + const ghostPrivateKey = ghost.keys.plainPrivateKey; + + const { ciphertext: encryptedProjectKey, nonce: encryptedProjectKeyIv } = encryptAsymmetric( + randomBytes, + ghost.keys.publicKey, + ghostPrivateKey + ); + + // 3. create workspace keys for the ghost user + await server.services.projectKey.uploadProjectKeys({ + projectId: workspace.id, + actor: req.permission.type, + actorId: ghost.user.id, + nonce: encryptedProjectKeyIv, + receiverId: ghost.user.id, + encryptedKey: encryptedProjectKey + }); + + // 4. create a project bot + const bot = await server.services.projectBot.findBotByProjectId({ + actorId: ghost.user.id, + actor: req.permission.type, + projectId: workspace.id + }); + + // 5. activate the bot + await server.services.projectBot.setBotActiveState({ + botKey: { + encryptedKey: encryptedProjectKey, + nonce: encryptedProjectKeyIv + }, + actorId: ghost.user.id, + isActive: true, + actor: req.permission.type, + botId: bot.id + }); + + // 6. 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: ghost.user.id, + actor: req.permission.type, + projectId: workspace.id + }); + + if (!latestKey) throw new Error("Failed to get latest key"); + + // 8. Create workspace members for the current user + + const projectAdmin = await createWsMembers({ + decryptKey: latestKey, + members: [ + { + userPublicKey: user.publicKey, + orgMembershipId: userOrgMembership.membership.id, + projectMembershipRole: ProjectMembershipRole.Admin // <-- Make the first user an admin + } + ], + userPrivateKey: ghostPrivateKey + }); + + // 9. Add the current user to the workspace + await server.services.projectMembership.addUsersToProject({ + projectId: workspace.id, + actorId: ghost.user.id, + actor: req.permission.type, + members: projectAdmin + }); + + return { workspace }; + } + }); +}; diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 6e4d60bba..231a99e19 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -275,7 +275,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); if (!user) { - user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod] }); + user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod], ghost: false }); } const isLinkingRequired = !user?.authMethods?.includes(authMethod); const isUserCompleted = user.isAccepted; diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index bb3cbbd95..e708cefdd 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -76,7 +76,8 @@ export const orgDALFactory = (db: TDbClient) => { db.ref("lastName").withSchema(TableName.Users), db.ref("id").withSchema(TableName.Users).as("userId"), db.ref("publicKey").withSchema(TableName.UserEncryptionKey) - ); + ) + .where({ ghost: false }); // MAKE SURE USER IS NOT A GHOST USER return members.map(({ email, firstName, lastName, userId, publicKey, ...data }) => ({ ...data, user: { email, firstName, lastName, id: userId, publicKey } @@ -86,6 +87,43 @@ export const orgDALFactory = (db: TDbClient) => { } }; + const findOrgGhostUser = async (orgId: string) => { + try { + const [member] = await db(TableName.OrgMembership) + .where({ orgId }) + .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) + .leftJoin(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`) + .select( + db.ref("id").withSchema(TableName.OrgMembership), + db.ref("orgId").withSchema(TableName.OrgMembership), + db.ref("role").withSchema(TableName.OrgMembership), + db.ref("roleId").withSchema(TableName.OrgMembership), + db.ref("status").withSchema(TableName.OrgMembership), + db.ref("email").withSchema(TableName.Users), + db.ref("id").withSchema(TableName.Users).as("userId"), + db.ref("publicKey").withSchema(TableName.UserEncryptionKey) + ) + .where({ ghost: true }); + return member; + } catch (error) { + return null; + } + }; + + const ghostUserExists = async (orgId: string) => { + try { + const [member] = await db(TableName.OrgMembership) + .where({ orgId }) + .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) + .leftJoin(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`) + .select(db.ref("id").withSchema(TableName.Users).as("userId")) + .where({ ghost: true }); + return !!member; + } catch (error) { + return false; + } + }; + const create = async (dto: TOrganizationsInsert, tx?: Knex) => { try { const [organization] = await (tx || db)(TableName.Organization).insert(dto).returning("*"); @@ -191,6 +229,8 @@ export const orgDALFactory = (db: TDbClient) => { findAllOrgMembers, findOrgById, findAllOrgsByUserId, + ghostUserExists, + findOrgGhostUser, create, updateById, deleteById, diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 1d78ef0b9..3b20b9a14 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -1,5 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; +import crypto from "crypto"; import jwt from "jsonwebtoken"; import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas"; @@ -11,6 +12,7 @@ import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config- import { getConfig } from "@app/lib/config/env"; import { generateAsymmetricKeyPair } from "@app/lib/crypto"; import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { isDisposableEmail } from "@app/lib/validator"; @@ -118,6 +120,49 @@ export const orgServiceFactory = ({ return workspaces.filter((workspace) => organizationWorkspaceIds.has(workspace.id)); }; + const addGhostUser = async (orgId: string) => { + const email = `ghost@${orgId}.com`; + const password = crypto.randomBytes(128).toString("hex"); + + const user = await userDAL.create({ + ghost: true, + authMethods: [AuthMethod.EMAIL], + email: `ghost@${orgId}.com`, + isAccepted: true + }); + + const encKeys = await generateUserSrpKeys(email, password); + + await userDAL.upsertUserEncryptionKey(user.id, { + encryptionVersion: 2, + protectedKey: encKeys.protectedKey, + protectedKeyIV: encKeys.protectedKeyIV, + protectedKeyTag: encKeys.protectedKeyTag, + publicKey: encKeys.publicKey, + encryptedPrivateKey: encKeys.encryptedPrivateKey, + iv: encKeys.encryptedPrivateKeyIV, + tag: encKeys.encryptedPrivateKeyTag, + salt: encKeys.salt, + verifier: encKeys.verifier + }); + + const createMembershipData = { + orgId, + userId: user.id, + role: OrgMembershipRole.Admin, + status: OrgMembershipStatus.Accepted + }; + + console.log("createMembershipData", createMembershipData); + + await orgDAL.createMembership(createMembershipData); + + return { + user, + keys: encKeys + }; + }; + /* * Update organization details * */ @@ -338,7 +383,8 @@ export const orgServiceFactory = ({ { email: inviteeEmail, isAccepted: false, - authMethods: [AuthMethod.EMAIL] + authMethods: [AuthMethod.EMAIL], + ghost: false }, tx ); @@ -491,6 +537,7 @@ export const orgServiceFactory = ({ deleteOrganizationById, deleteOrgMembership, findAllWorkspaces, + addGhostUser, updateOrgMembership, // incident contacts findIncidentContacts, diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 5ead160f2..a9514c1e3 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -1,4 +1,5 @@ import { ForbiddenError } from "@casl/ability"; +import { Knex } from "knex"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; @@ -6,17 +7,16 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services import { getConfig } from "@app/lib/config/env"; import { decryptAsymmetric, - decryptSymmetric, - decryptSymmetric128BitHexKeyUTF8, encryptSymmetric, encryptSymmetric128BitHexKeyUTF8, generateAsymmetricKeyPair } from "@app/lib/crypto"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; import { TProjectBotDALFactory } from "./project-bot-dal"; -import { TSetActiveStateDTO } from "./project-bot-types"; +import { TGetPrivateKeyDTO, TSetActiveStateDTO } from "./project-bot-types"; type TProjectBotServiceFactoryDep = { permissionService: Pick; @@ -26,48 +26,35 @@ type TProjectBotServiceFactoryDep = { export type TProjectBotServiceFactory = ReturnType; export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: TProjectBotServiceFactoryDep) => { - const getBotKey = async (projectId: string) => { - const appCfg = getConfig(); - const encryptionKey = appCfg.ENCRYPTION_KEY; - const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; + const getBotPrivateKey = async ({ encoding, nonce, tag, encryptedPrivateKey }: TGetPrivateKeyDTO) => + infisicalSymmetricDecrypt({ + keyEncoding: encoding, + iv: nonce, + tag, + ciphertext: encryptedPrivateKey + }); + const getBotKey = async (projectId: string) => { const bot = await projectBotDAL.findOne({ projectId }); if (!bot) throw new BadRequestError({ message: "failed to find bot key" }); if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" }); if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey) throw new BadRequestError({ message: "Encryption key missing" }); - if (rootEncryptionKey && (bot.keyEncoding as SecretKeyEncoding) === SecretKeyEncoding.BASE64) { - const privateKeyBot = decryptSymmetric({ - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey, - key: rootEncryptionKey - }); - return decryptAsymmetric({ - ciphertext: bot.encryptedProjectKey, - privateKey: privateKeyBot, - nonce: bot.encryptedProjectKeyNonce, - publicKey: bot.sender.publicKey - }); - } - if (encryptionKey && (bot.keyEncoding as SecretKeyEncoding) === SecretKeyEncoding.UTF8) { - const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({ - iv: bot.iv, - tag: bot.tag, - ciphertext: bot.encryptedPrivateKey, - key: encryptionKey - }); - return decryptAsymmetric({ - ciphertext: bot.encryptedProjectKey, - privateKey: privateKeyBot, - nonce: bot.encryptedProjectKeyNonce, - publicKey: bot.sender.publicKey - }); - } + const privateKeyBot = await getBotPrivateKey({ + nonce: bot.iv, + tag: bot.tag, + encryptedPrivateKey: bot.encryptedPrivateKey, + encoding: bot.keyEncoding as SecretKeyEncoding + }); - throw new BadRequestError({ - message: "Failed to obtain bot copy of workspace key needed for operation" + console.log("privateKeyBot", privateKeyBot); + + return decryptAsymmetric({ + ciphertext: bot.encryptedProjectKey, + privateKey: privateKeyBot, + nonce: bot.encryptedProjectKeyNonce, + publicKey: bot.sender.publicKey }); }; @@ -131,12 +118,16 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T if (!botKey?.nonce || !botKey?.encryptedKey) { throw new BadRequestError({ message: "Failed to set bot active - missing bot key" }); } - const doc = await projectBotDAL.updateById(botId, { - isActive: true, - encryptedProjectKey: botKey.encryptedKey, - encryptedProjectKeyNonce: botKey.nonce, - senderId: actorId - }); + const doc = await projectBotDAL.updateById( + botId, + { + isActive: true, + encryptedProjectKey: botKey.encryptedKey, + encryptedProjectKeyNonce: botKey.nonce, + senderId: actorId + }, + tx + ); if (!doc) throw new BadRequestError({ message: "Failed to update bot active state" }); return doc; } @@ -153,6 +144,7 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T return { findBotByProjectId, setBotActiveState, + getBotPrivateKey, getBotKey }; }; diff --git a/backend/src/services/project-bot/project-bot-types.ts b/backend/src/services/project-bot/project-bot-types.ts index 94a2943eb..3d4f400de 100644 --- a/backend/src/services/project-bot/project-bot-types.ts +++ b/backend/src/services/project-bot/project-bot-types.ts @@ -1,3 +1,4 @@ +import { SecretKeyEncoding } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; export type TSetActiveStateDTO = { @@ -8,3 +9,10 @@ export type TSetActiveStateDTO = { }; botId: string; } & Omit; + +export type TGetPrivateKeyDTO = { + encoding: SecretKeyEncoding; + nonce: string; + tag: string; + encryptedPrivateKey: string; +}; diff --git a/backend/src/services/project-key/project-key-service.ts b/backend/src/services/project-key/project-key-service.ts index fa77760a4..81b589bd0 100644 --- a/backend/src/services/project-key/project-key-service.ts +++ b/backend/src/services/project-key/project-key-service.ts @@ -1,4 +1,5 @@ import { ForbiddenError } from "@casl/ability"; +import { Knex } from "knex"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -43,7 +44,7 @@ export const projectKeyServiceFactory = ({ name: "Upload project keys" }); - await projectKeyDAL.create({ projectId, receiverId, encryptedKey, nonce, senderId: actorId }); + await projectKeyDAL.create({ projectId, receiverId, encryptedKey, nonce, senderId: actorId }, tx); }; const getLatestProjectKey = async ({ actorId, projectId, actor, actorOrgId }: TGetLatestProjectKeyDTO) => { diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index 22b9937a9..c6d8249e2 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -24,15 +24,16 @@ export const projectMembershipDALFactory = (db: TDbClient) => { db.ref("projectId").withSchema(TableName.ProjectMembership), db.ref("role").withSchema(TableName.ProjectMembership), db.ref("roleId").withSchema(TableName.ProjectMembership), + db.ref("ghost").withSchema(TableName.Users), db.ref("email").withSchema(TableName.Users), db.ref("publicKey").withSchema(TableName.UserEncryptionKey), db.ref("firstName").withSchema(TableName.Users), db.ref("lastName").withSchema(TableName.Users), db.ref("id").withSchema(TableName.Users).as("userId") ); - return members.map(({ email, firstName, lastName, publicKey, ...data }) => ({ + return members.map(({ email, firstName, lastName, publicKey, ghost, ...data }) => ({ ...data, - user: { email, firstName, lastName, id: data.userId, publicKey } + user: { email, firstName, lastName, id: data.userId, publicKey, ghost } })); } catch (error) { throw new DatabaseError({ error, name: "Find all project members" }); diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 6b2123e5e..477891bf8 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -134,11 +134,16 @@ export const projectMembershipServiceFactory = ({ await projectMembershipDAL.transaction(async (tx) => { await projectMembershipDAL.insertMany( - orgMembers.map(({ userId }) => ({ - projectId, - userId: userId as string, - role: ProjectMembershipRole.Member - })), + orgMembers.map(({ userId, id: membershipId }) => { + const role = + members.find((i) => i.orgMembershipId === membershipId)?.projectRole || ProjectMembershipRole.Member; + + return { + projectId, + userId: userId as string, + role + }; + }), tx ); const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId); diff --git a/backend/src/services/project-membership/project-membership-types.ts b/backend/src/services/project-membership/project-membership-types.ts index 6ee1d2965..6fe2977ee 100644 --- a/backend/src/services/project-membership/project-membership-types.ts +++ b/backend/src/services/project-membership/project-membership-types.ts @@ -1,3 +1,4 @@ +import { ProjectMembershipRole } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; export type TGetProjectMembershipDTO = TProjectPermission; @@ -20,5 +21,6 @@ export type TAddUsersToWorkspaceDTO = { orgMembershipId: string; workspaceEncryptedKey: string; workspaceEncryptedNonce: string; + projectRole: ProjectMembershipRole; }[]; } & TProjectPermission; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 387c9bd78..f56c40fa2 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -66,10 +66,15 @@ export const projectServiceFactory = ({ const newProject = projectDAL.transaction(async (tx) => { const project = await projectDAL.create( - { name: workspaceName, orgId, slug: slugify(`${workspaceName}-${alphaNumericNanoId(4)}`) }, + { + name: workspaceName, + orgId, + slug: slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), + e2ee: false + }, tx ); - // set user as admin member for proeject + // set user as admin member for project await projectMembershipDAL.create( { userId: actorId, @@ -78,6 +83,7 @@ export const projectServiceFactory = ({ }, tx ); + // generate the blind index for project await secretBlindIndexDAL.create( { diff --git a/frontend/src/components/utilities/cryptography/crypto.ts b/frontend/src/components/utilities/cryptography/crypto.ts index 4e2d981a7..0f0e4af22 100644 --- a/frontend/src/components/utilities/cryptography/crypto.ts +++ b/frontend/src/components/utilities/cryptography/crypto.ts @@ -13,12 +13,12 @@ nacl.util = require("tweetnacl-util"); */ const generateKeyPair = () => { const pair = nacl.box.keyPair(); - - return ({ - publicKey: nacl.util.encodeBase64(pair.publicKey), - privateKey: nacl.util.encodeBase64(pair.secretKey) - }); -} + + return { + publicKey: nacl.util.encodeBase64(pair.publicKey), + privateKey: nacl.util.encodeBase64(pair.secretKey) + }; +}; type EncryptAsymmetricProps = { plaintext: string; @@ -29,27 +29,19 @@ type EncryptAsymmetricProps = { /** * Verify that private key [privateKey] is the one that corresponds to * the public key [publicKey] - * @param {Object} + * @param {Object} * @param {String} - base64-encoded Nacl private key * @param {String} - base64-encoded Nacl public key */ -const verifyPrivateKey = ({ - privateKey, - publicKey -}: { - privateKey: string; - publicKey: string; -}) => { +const verifyPrivateKey = ({ privateKey, publicKey }: { privateKey: string; publicKey: string }) => { const derivedPublicKey = nacl.util.encodeBase64( - nacl.box.keyPair.fromSecretKey( - nacl.util.decodeBase64(privateKey) - ).publicKey + nacl.box.keyPair.fromSecretKey(nacl.util.decodeBase64(privateKey)).publicKey ); - + if (derivedPublicKey !== publicKey) { throw new Error("Failed to verify private key"); } -} +}; /** * Derive a key from password [password] and salt [salt] using Argon2id @@ -229,7 +221,8 @@ export { decryptAssymmetric, decryptSymmetric, deriveArgonKey, - encryptAssymmetric, + encryptAssymmetric, encryptSymmetric, generateKeyPair, - verifyPrivateKey}; + verifyPrivateKey +}; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index f47a77537..89a436df2 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -158,19 +158,21 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) => export const createWorkspace = ({ organizationId, - workspaceName + projectName, + inviteAllOrgMembers }: CreateWorkspaceDTO): Promise<{ data: { workspace: Workspace } }> => { - return apiRequest.post("/api/v1/workspace", { workspaceName, organizationId }); + return apiRequest.post("/api/v3/projects", { projectName, inviteAllOrgMembers, organizationId }); }; export const useCreateWorkspace = () => { const queryClient = useQueryClient(); return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({ - mutationFn: async ({ organizationId, workspaceName }) => + mutationFn: async ({ organizationId, projectName, inviteAllOrgMembers }) => createWorkspace({ organizationId, - workspaceName + projectName, + inviteAllOrgMembers }), onSuccess: () => { queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index 10d222a2a..3538e761b 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -25,7 +25,8 @@ export type NameWorkspaceSecretsDTO = { // mutation dto export type CreateWorkspaceDTO = { - workspaceName: string; + projectName: string; + inviteAllOrgMembers: boolean; organizationId: string; }; diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 43d36d06b..ae1bf4b3d 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -1,7 +1,5 @@ // REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex -import crypto from "crypto"; - import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -23,7 +21,7 @@ import { faNetworkWired, faPlug, faPlus, - faUserPlus, + faUserPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -33,7 +31,6 @@ import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { OrgPermissionCan } from "@app/components/permissions"; import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck"; -import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; import { Button, Checkbox, @@ -53,13 +50,12 @@ import { } from "@app/context"; import { withPermission } from "@app/hoc"; import { - fetchOrgUsers, - useAddUserToWs, + // fetchOrgUsers, + // useAddUserToWs, useCreateWorkspace, useRegisterUserAction, - useUploadWsKey } from "@app/hooks/api"; -import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; +// import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -475,7 +471,7 @@ const OrganizationPage = withPermission( const currentOrg = String(router.query.id); const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === currentOrg) || []; const { createNotification } = useNotificationContext(); - const addWsUser = useAddUserToWs(); + // const addWsUser = useAddUserToWs(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addNewWs", @@ -497,7 +493,6 @@ const OrganizationPage = withPermission( const [searchFilter, setSearchFilter] = useState(""); const createWs = useCreateWorkspace(); const { user } = useUser(); - const uploadWsKey = useUploadWsKey(); const { data: serverDetails } = useFetchServerStatus(); const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => { @@ -510,45 +505,31 @@ const OrganizationPage = withPermission( } } = await createWs.mutateAsync({ organizationId: currentOrg, - workspaceName: name - }); - - const randomBytes = crypto.randomBytes(16).toString("hex"); - const PRIVATE_KEY = String(localStorage.getItem("PRIVATE_KEY")); - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: randomBytes, - publicKey: user.publicKey, - privateKey: PRIVATE_KEY - }); - - await uploadWsKey.mutateAsync({ - encryptedKey: ciphertext, - nonce, - userId: user?.id, - workspaceId: newWorkspaceId + inviteAllOrgMembers: addMembers, + projectName: name }); + /* if (addMembers) { // not using hooks because need at this point only const orgUsers = await fetchOrgUsers(currentOrg); const decryptKey = await fetchUserWsKey(newWorkspaceId); - const members = orgUsers - .filter( - ({ status, user: orgUser }) => status === "accepted" && user.email !== orgUser.email - ) - .map(({ user: orgUser, id: orgMembershipId }) => ({ - userPublicKey: orgUser.publicKey, - orgMembershipId - })); - if (members.length) { - await addWsUser.mutateAsync({ - workspaceId: newWorkspaceId, - decryptKey, - userPrivateKey: PRIVATE_KEY, - members - }); - } + + await addWsUser.mutateAsync({ + workspaceId: newWorkspaceId, + decryptKey, + userPrivateKey: PRIVATE_KEY, + members: orgUsers + .filter( + ({ status, user: orgUser }) => status === "accepted" && user.email !== orgUser.email + ) + .map(({ user: orgUser, id: orgMembershipId }) => ({ + userPublicKey: orgUser.publicKey, + orgMembershipId + })) + }); } + */ createNotification({ text: "Workspace created", type: "success" }); handlePopUpClose("addNewWs"); router.push(`/project/${newWorkspaceId}/secrets/overview`); @@ -735,7 +716,7 @@ const OrganizationPage = withPermission( new Date().getTime() - new Date(user?.createdAt).getTime() < 30 * 24 * 60 * 60 * 1000 ) && ( -
+

Onboarding Guide