diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index bdafe3bcc..49ed3d15b 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -2,11 +2,27 @@ import { Knex } from "knex"; import { initEnvConfig } from "@app/lib/config/env"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; +import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { initLogger, logger } from "@app/lib/logger"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; +import { AuthMethod } from "@app/services/auth/auth-type"; +import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns"; +import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal"; +import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; +import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; +import { userDALFactory } from "@app/services/user/user-dal"; -import { ProjectMembershipRole, ProjectType, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas"; -import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data"; +import { + OrgMembershipRole, + OrgMembershipStatus, + ProjectMembershipRole, + ProjectType, + SecretEncryptionAlgo, + SecretKeyEncoding, + TableName +} from "../schemas"; +import { seedData1 } from "../seed-data"; export const DEFAULT_PROJECT_ENVS = [ { name: "Development", slug: "dev" }, @@ -14,6 +30,148 @@ export const DEFAULT_PROJECT_ENVS = [ { name: "Production", slug: "prod" } ]; +const createGhostUser = async ( + orgId: string, + projectId: string, + userId: string, + userOrgMembershipId: string, + knex: Knex +) => { + const projectKeyDAL = projectKeyDALFactory(knex); + const userDAL = userDALFactory(knex); + const projectMembershipDAL = projectMembershipDALFactory(knex); + const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(knex); + + const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key. + + const password = crypto.randomBytes(128).toString("hex"); + + const [ghostUser] = await knex(TableName.Users) + .insert({ + isGhost: true, + authMethods: [AuthMethod.EMAIL], + username: email, + email, + isAccepted: true + }) + .returning("*"); + + const encKeys = await generateUserSrpKeys(email, password); + + await knex(TableName.UserEncryptionKey) + .insert({ userId: ghostUser.id, encryptionVersion: 2, publicKey: encKeys.publicKey }) + .onConflict("userId") + .merge(); + + await knex(TableName.OrgMembership) + .insert({ + orgId, + userId: ghostUser.id, + role: OrgMembershipRole.Admin, + status: OrgMembershipStatus.Accepted, + isActive: true + }) + .returning("*"); + + const [projectMembership] = await knex(TableName.ProjectMembership) + .insert({ + userId: ghostUser.id, + projectId + }) + .returning("*"); + + await knex(TableName.ProjectUserMembershipRole).insert({ + projectMembershipId: projectMembership.id, + role: ProjectMembershipRole.Admin + }); + + const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({ + publicKey: encKeys.publicKey, + privateKey: encKeys.plainPrivateKey + }); + + await knex(TableName.ProjectKeys).insert({ + projectId, + receiverId: ghostUser.id, + encryptedKey: encryptedProjectKey, + nonce: encryptedProjectKeyIv, + senderId: ghostUser.id + }); + + const { iv, tag, ciphertext, encoding, algorithm } = crypto + .encryption() + .symmetric() + .encryptWithRootEncryptionKey(encKeys.plainPrivateKey); + + await knex(TableName.ProjectBot).insert({ + name: "Infisical Bot (Ghost)", + projectId, + tag, + iv, + encryptedProjectKey, + encryptedProjectKeyNonce: encryptedProjectKeyIv, + encryptedPrivateKey: ciphertext, + isActive: true, + publicKey: encKeys.publicKey, + senderId: ghostUser.id, + algorithm, + keyEncoding: encoding + }); + + const latestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, knex); + + if (!latestKey) { + throw new Error("Latest key not found for user"); + } + + const user = await userDAL.findUserEncKeyByUserId(userId, knex); + + if (!user || !user.publicKey) { + throw new Error("User not found"); + } + + const [projectAdmin] = assignWorkspaceKeysToMembers({ + decryptKey: latestKey, + userPrivateKey: encKeys.plainPrivateKey, + members: [ + { + userPublicKey: user.publicKey, + orgMembershipId: userOrgMembershipId + } + ] + }); + + // Create a membership for the user + const userProjectMembership = await projectMembershipDAL.create( + { + projectId, + userId: user.id + }, + knex + ); + await projectUserMembershipRoleDAL.create( + { projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin }, + knex + ); + + // Create a project key for the user + await projectKeyDAL.create( + { + encryptedKey: projectAdmin.workspaceEncryptedKey, + nonce: projectAdmin.workspaceEncryptedNonce, + senderId: ghostUser.id, + receiverId: user.id, + projectId + }, + knex + ); + + return { + user: ghostUser, + keys: encKeys + }; +}; + export async function seed(knex: Knex): Promise { // Deletes ALL existing entries await knex(TableName.Project).del(); @@ -37,7 +195,7 @@ export async function seed(knex: Knex): Promise { }) .returning("*"); - const projectMembership = await knex(TableName.ProjectMembership) + const [projectMembership] = await knex(TableName.ProjectMembership) .insert({ projectId: project.id, userId: seedData1.id @@ -45,7 +203,7 @@ export async function seed(knex: Knex): Promise { .returning("*"); await knex(TableName.ProjectUserMembershipRole).insert({ role: ProjectMembershipRole.Admin, - projectMembershipId: projectMembership[0].id + projectMembershipId: projectMembership.id }); const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first(); @@ -55,15 +213,7 @@ export async function seed(knex: Knex): Promise { throw new Error("User public key not found"); } - const userPrivateKey = await getUserPrivateKey(seedData1.password, user); - const projectKey = buildUserProjectKey(userPrivateKey, user.publicKey); - await knex(TableName.ProjectKeys).insert({ - projectId: project.id, - nonce: projectKey.nonce, - encryptedKey: projectKey.ciphertext, - receiverId: seedData1.id, - senderId: seedData1.id - }); + await createGhostUser(seedData1.organization.id, project.id, seedData1.id, projectMembership.id, knex); // create default environments and default folders const envs = await knex(TableName.Environment)