From e4f32f092a4008971803bcc6dcd356449b6b258f Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 22 Jan 2024 15:20:26 +0530 Subject: [PATCH] feat(infisical-pg): completed test for secret folder and import --- .../e2e-test/routes/v1/project-env.spec.ts | 151 +++++++++++++++ .../e2e-test/routes/v1/secret-folder.spec.ts | 155 +++++++++++++++ .../e2e-test/routes/v1/secret-import.spec.ts | 179 ++++++++++++++++++ backend-pg/e2e-test/routes/v3/secrets.spec.ts | 0 .../20240101104907_secret-approval-request.ts | 2 +- backend-pg/src/db/seed-data.ts | 126 +++++++++++- backend-pg/src/db/seeds/1-user.ts | 72 +------ backend-pg/src/db/seeds/2-org.ts | 1 + backend-pg/src/db/seeds/3-project.ts | 14 +- .../services/permission/permission-service.ts | 6 +- .../secret-folder/secret-folder-service.ts | 39 ++-- 11 files changed, 646 insertions(+), 99 deletions(-) create mode 100644 backend-pg/e2e-test/routes/v1/project-env.spec.ts create mode 100644 backend-pg/e2e-test/routes/v1/secret-folder.spec.ts create mode 100644 backend-pg/e2e-test/routes/v1/secret-import.spec.ts create mode 100644 backend-pg/e2e-test/routes/v3/secrets.spec.ts diff --git a/backend-pg/e2e-test/routes/v1/project-env.spec.ts b/backend-pg/e2e-test/routes/v1/project-env.spec.ts new file mode 100644 index 000000000..936cfa859 --- /dev/null +++ b/backend-pg/e2e-test/routes/v1/project-env.spec.ts @@ -0,0 +1,151 @@ +import { seedData1 } from "@app/db/seed-data"; +import { DEFAULT_PROJECT_ENVS } from "@app/db/seeds/3-project"; + +describe("Project Environment Router", async () => { + test("Get default environments", async () => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v1/workspace/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("workspace"); + // check for default environments + expect(payload).toEqual({ + workspace: expect.objectContaining({ + name: seedData1.project.name, + id: seedData1.project.id, + slug: seedData1.project.slug, + environments: expect.arrayContaining([ + expect.objectContaining(DEFAULT_PROJECT_ENVS[0]), + expect.objectContaining(DEFAULT_PROJECT_ENVS[1]), + expect.objectContaining(DEFAULT_PROJECT_ENVS[2]) + ]) + }) + }); + // ensure only two default environments exist + expect(payload.workspace.environments.length).toBe(3); + }); + + const mockProjectEnv = { name: "temp", slug: "temp", id: "" }; // id will be filled in create op + test("Create environment", async () => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/workspace/${seedData1.project.id}/environments`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + name: mockProjectEnv.name, + slug: mockProjectEnv.slug + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("environment"); + expect(payload.environment).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: mockProjectEnv.name, + slug: mockProjectEnv.slug, + projectId: seedData1.project.id, + position: DEFAULT_PROJECT_ENVS.length + 1, + createdAt: expect.any(String), + updatedAt: expect.any(String) + }) + ); + mockProjectEnv.id = payload.environment.id; + }); + + test("Update environment", async () => { + const updatedName = { name: "temp#2", slug: "temp2" }; + const res = await testServer.inject({ + method: "PATCH", + url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + name: updatedName.name, + slug: updatedName.slug, + position: 1 + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("environment"); + expect(payload.environment).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: updatedName.name, + slug: updatedName.slug, + projectId: seedData1.project.id, + position: 1, + createdAt: expect.any(String), + updatedAt: expect.any(String) + }) + ); + mockProjectEnv.name = updatedName.name; + mockProjectEnv.slug = updatedName.slug; + }); + + test("Delete environment", async () => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("environment"); + expect(payload.environment).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: mockProjectEnv.name, + slug: mockProjectEnv.slug, + position: 1, + createdAt: expect.any(String), + updatedAt: expect.any(String) + }) + ); + }); + + // after all these opreations the list of environment should be still same + test("Default list of environment", async () => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v1/workspace/${seedData1.project.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("workspace"); + // check for default environments + expect(payload).toEqual({ + workspace: expect.objectContaining({ + name: seedData1.project.name, + id: seedData1.project.id, + slug: seedData1.project.slug, + environments: expect.arrayContaining([ + expect.objectContaining(DEFAULT_PROJECT_ENVS[0]), + expect.objectContaining(DEFAULT_PROJECT_ENVS[1]), + expect.objectContaining(DEFAULT_PROJECT_ENVS[2]) + ]) + }) + }); + // ensure only two default environments exist + expect(payload.workspace.environments.length).toBe(3); + }); +}); diff --git a/backend-pg/e2e-test/routes/v1/secret-folder.spec.ts b/backend-pg/e2e-test/routes/v1/secret-folder.spec.ts new file mode 100644 index 000000000..bc290fed3 --- /dev/null +++ b/backend-pg/e2e-test/routes/v1/secret-folder.spec.ts @@ -0,0 +1,155 @@ +import { seedData1 } from "@app/db/seed-data"; + +describe("Secret Folder Router", async () => { + test.each([ + { name: "folder1", path: "/" }, // one in root + { name: "folder1", path: "/level1/level2" }, // then create a deep one creating intermediate ones + { name: "folder2", path: "/" }, + { name: "folder1", path: "/level1/level2" } // this should not create folder return same thing + ])("Create folder $name in $path", async ({ name, path }) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name, + path + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("folder"); + // check for default environments + expect(payload).toEqual({ + folder: expect.objectContaining({ + name, + id: expect.any(String) + }) + }); + }); + + test.each([ + { + path: "/", + expected: { + folders: [{ name: "folder1" }, { name: "level1" }, { name: "folder2" }], + length: 3 + } + }, + { path: "/level1/level2", expected: { folders: [{ name: "folder1" }], length: 1 } } + ])("Get folders $path", async ({ path, expected }) => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("folders"); + expect(payload.folders.length).toBe(expected.length); + expect(payload).toEqual({ folders: expected.folders.map((el) => expect.objectContaining(el)) }); + }); + + let toBeDeleteFolderId = ""; + test("Update a deep folder", async () => { + const res = await testServer.inject({ + method: "PATCH", + url: `/api/v1/folders/folder1`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + name: "folder-updated", + path: "/level1/level2" + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("folder"); + expect(payload.folder).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: "folder-updated" + }) + ); + toBeDeleteFolderId = payload.folder.id; + + const resUpdatedFolders = await testServer.inject({ + method: "GET", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/level1/level2" + } + }); + + expect(resUpdatedFolders.statusCode).toBe(200); + const updatedFolderList = JSON.parse(resUpdatedFolders.payload); + expect(updatedFolderList).toHaveProperty("folders"); + expect(updatedFolderList.folders.length).toEqual(1); + expect(updatedFolderList.folders[0].name).toEqual("folder-updated"); + }); + + test("Delete a deep folder", async () => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/folders/${toBeDeleteFolderId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/level1/level2" + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("folder"); + expect(payload.folder).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: "folder-updated" + }) + ); + + const resUpdatedFolders = await testServer.inject({ + method: "GET", + url: `/api/v1/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/level1/level2" + } + }); + + expect(resUpdatedFolders.statusCode).toBe(200); + const updatedFolderList = JSON.parse(resUpdatedFolders.payload); + expect(updatedFolderList).toHaveProperty("folders"); + expect(updatedFolderList.folders.length).toEqual(0); + }); +}); diff --git a/backend-pg/e2e-test/routes/v1/secret-import.spec.ts b/backend-pg/e2e-test/routes/v1/secret-import.spec.ts new file mode 100644 index 000000000..f42c033c2 --- /dev/null +++ b/backend-pg/e2e-test/routes/v1/secret-import.spec.ts @@ -0,0 +1,179 @@ +import { seedData1 } from "@app/db/seed-data"; + +describe("Secret Folder Router", async () => { + test.each([ + { importEnv: "dev", importPath: "/" }, // one in root + { importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones + ])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v1/secret-imports`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/", + import: { + environment: importEnv, + path: importPath + } + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImport"); + // check for default environments + expect(payload.secretImport).toEqual( + expect.objectContaining({ + id: expect.any(String), + importPath: expect.any(String), + importEnv: expect.objectContaining({ + name: expect.any(String), + slug: expect.any(String), + id: expect.any(String) + }) + }) + ); + }); + + let testSecretImportId = ""; + test("Get secret imports", async () => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v1/secret-imports`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/" + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImports"); + expect(payload.secretImports.length).toBe(2); + testSecretImportId = payload.secretImports[0].id; + expect(payload.secretImports).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + importPath: expect.any(String), + importEnv: expect.objectContaining({ + name: expect.any(String), + slug: expect.any(String), + id: expect.any(String) + }) + }) + ]) + ); + }); + + test("Update secret import position", async () => { + const res = await testServer.inject({ + method: "PATCH", + url: `/api/v1/secret-imports/${testSecretImportId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/", + import: { + position: 2 + } + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImport"); + // check for default environments + expect(payload.secretImport).toEqual( + expect.objectContaining({ + id: expect.any(String), + importPath: expect.any(String), + position: 2, + importEnv: expect.objectContaining({ + name: expect.any(String), + slug: expect.any(String), + id: expect.any(String) + }) + }) + ); + + const secretImportsListRes = await testServer.inject({ + method: "GET", + url: `/api/v1/secret-imports`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/" + } + }); + + expect(secretImportsListRes.statusCode).toBe(200); + const secretImportList = JSON.parse(secretImportsListRes.payload); + expect(secretImportList).toHaveProperty("secretImports"); + expect(secretImportList.secretImports[1].id).toEqual(testSecretImportId); + }); + + test("Delete secret import position", async () => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v1/secret-imports/${testSecretImportId}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/" + } + }); + + expect(res.statusCode).toBe(200); + const payload = JSON.parse(res.payload); + expect(payload).toHaveProperty("secretImport"); + // check for default environments + expect(payload.secretImport).toEqual( + expect.objectContaining({ + id: expect.any(String), + importPath: expect.any(String), + importEnv: expect.objectContaining({ + name: expect.any(String), + slug: expect.any(String), + id: expect.any(String) + }) + }) + ); + + const secretImportsListRes = await testServer.inject({ + method: "GET", + url: `/api/v1/secret-imports`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + workspaceId: seedData1.project.id, + environment: seedData1.environment.slug, + path: "/" + } + }); + + expect(secretImportsListRes.statusCode).toBe(200); + const secretImportList = JSON.parse(secretImportsListRes.payload); + expect(secretImportList).toHaveProperty("secretImports"); + expect(secretImportList.secretImports.length).toEqual(1); + expect(secretImportList.secretImports[0].position).toEqual(1); + }); +}); diff --git a/backend-pg/e2e-test/routes/v3/secrets.spec.ts b/backend-pg/e2e-test/routes/v3/secrets.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/backend-pg/src/db/migrations/20240101104907_secret-approval-request.ts b/backend-pg/src/db/migrations/20240101104907_secret-approval-request.ts index 573dd6298..98bfb163b 100644 --- a/backend-pg/src/db/migrations/20240101104907_secret-approval-request.ts +++ b/backend-pg/src/db/migrations/20240101104907_secret-approval-request.ts @@ -22,7 +22,7 @@ export async function up(knex: Knex): Promise { t.foreign("statusChangeBy") .references("id") .inTable(TableName.ProjectMembership) - .onDelete("CASCADE"); + .onDelete("SET NULL"); t.uuid("committerId").notNullable(); t.foreign("committerId") .references("id") diff --git a/backend-pg/src/db/seed-data.ts b/backend-pg/src/db/seed-data.ts index 2f7da6d1a..7e474de9a 100644 --- a/backend-pg/src/db/seed-data.ts +++ b/backend-pg/src/db/seed-data.ts @@ -1,3 +1,19 @@ +import crypto from "node:crypto"; + +import argon2, { argon2id } from "argon2"; +import jsrp from "jsrp"; +import nacl from "tweetnacl"; +import { encodeBase64 } from "tweetnacl-util"; + +import { + decryptAsymmetric, + decryptSymmetric, + encryptAsymmetric, + encryptSymmetric +} from "@app/lib/crypto"; + +import { TUserEncryptionKeys } from "./schemas"; + export const seedData1 = { id: "3dafd81d-4388-432b-a4c5-f735616868c1", email: "test@localhost.local", @@ -8,9 +24,117 @@ export const seedData1 = { }, project: { id: "77fa7aed-9288-401e-a4c9-3a9430be62a0", - name: "first project" + name: "first project", + slug: "first-project" + }, + environment: { + name: "Development", + slug: "dev" }, token: { id: "a9dfafba-a3b7-42e3-8618-91abb702fd36" } }; + +export const generateUserSrpKeys = async (password: string) => { + const pair = nacl.box.keyPair(); + const secretKeyUint8Array = pair.secretKey; + const publicKeyUint8Array = pair.publicKey; + const privateKey = encodeBase64(secretKeyUint8Array); + const publicKey = encodeBase64(publicKeyUint8Array); + + // eslint-disable-next-line + const client = new jsrp.client(); + await new Promise((resolve) => { + client.init({ username: seedData1.email, password: seedData1.password }, () => resolve(null)); + }); + const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>( + (resolve, reject) => { + client.createVerifier((err, res) => { + if (err) return reject(err); + return resolve(res); + }); + } + ); + const derivedKey = await argon2.hash(password, { + salt: Buffer.from(salt), + memoryCost: 65536, + timeCost: 3, + parallelism: 1, + hashLength: 32, + type: argon2id, + raw: true + }); + if (!derivedKey) throw new Error("Failed to derive key from password"); + + const key = crypto.randomBytes(32); + + // create encrypted private key by encrypting the private + // key with the symmetric key [key] + const { + ciphertext: encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag + } = encryptSymmetric(privateKey, key.toString("base64")); + + // create the protected key by encrypting the symmetric key + // [key] with the derived key + const { + ciphertext: protectedKey, + iv: protectedKeyIV, + tag: protectedKeyTag + } = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64")); + + return { + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier + }; +}; + +export const getUserPrivateKey = async (password: string, user: TUserEncryptionKeys) => { + const derivedKey = await argon2.hash(password, { + salt: Buffer.from(user.salt), + memoryCost: 65536, + timeCost: 3, + parallelism: 1, + hashLength: 32, + type: argon2id, + raw: true + }); + if (!derivedKey) throw new Error("Failed to derive key from password"); + const key = decryptSymmetric({ + ciphertext: user.protectedKey, + iv: user.protectedKeyIV, + tag: user.protectedKeyTag, + key: derivedKey.toString("base64") + }); + const privateKey = decryptSymmetric({ + ciphertext: user.encryptedPrivateKey, + iv: user.iv, + tag: user.tag, + key + }); + return privateKey; +}; + +export const buildUserProjectKey = async (privateKey: string, publickey: string) => { + const randomBytes = crypto.randomBytes(16).toString("hex"); + const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey); + return { nonce, ciphertext }; +}; + +export const getUserProjectKey = async (privateKey: string) => { + const key = decryptAsymmetric({ + ciphertext: decryptFileKey.encryptedKey, + nonce: decryptFileKey.nonce, + publicKey: decryptFileKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); +}; diff --git a/backend-pg/src/db/seeds/1-user.ts b/backend-pg/src/db/seeds/1-user.ts index 03f77ca06..7855ebc78 100644 --- a/backend-pg/src/db/seeds/1-user.ts +++ b/backend-pg/src/db/seeds/1-user.ts @@ -1,78 +1,8 @@ -import crypto from "node:crypto"; - -import argon2, { argon2id } from "argon2"; -import jsrp from "jsrp"; import { Knex } from "knex"; -import nacl from "tweetnacl"; -import { encodeBase64 } from "tweetnacl-util"; - -import { encryptSymmetric } from "@app/lib/crypto"; import { AuthMethod } from "../../services/auth/auth-type"; import { TableName } from "../schemas"; -import { seedData1 } from "../seed-data"; - -export const generateUserSrpKeys = async (password: string) => { - const pair = nacl.box.keyPair(); - const secretKeyUint8Array = pair.secretKey; - const publicKeyUint8Array = pair.publicKey; - const privateKey = encodeBase64(secretKeyUint8Array); - const publicKey = encodeBase64(publicKeyUint8Array); - - // eslint-disable-next-line - const client = new jsrp.client(); - await new Promise((resolve) => { - client.init({ username: seedData1.email, password: seedData1.password }, () => resolve(null)); - }); - const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>( - (resolve, reject) => { - client.createVerifier((err, res) => { - if (err) return reject(err); - return resolve(res); - }); - } - ); - const derivedKey = await argon2.hash(password, { - salt: Buffer.from(salt), - memoryCost: 65536, - timeCost: 3, - parallelism: 1, - hashLength: 32, - type: argon2id, - raw: true - }); - if (!derivedKey) throw new Error("Failed to derive key from password"); - - const key = crypto.randomBytes(32); - - // create encrypted private key by encrypting the private - // key with the symmetric key [key] - const { - ciphertext: encryptedPrivateKey, - iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag - } = encryptSymmetric(privateKey, key.toString("base64")); - - // create the protected key by encrypting the symmetric key - // [key] with the derived key - const { - ciphertext: protectedKey, - iv: protectedKeyIV, - tag: protectedKeyTag - } = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64")); - - return { - protectedKey, - protectedKeyIV, - protectedKeyTag, - publicKey, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - }; -}; +import { generateUserSrpKeys, seedData1 } from "../seed-data"; export async function seed(knex: Knex): Promise { // Deletes ALL existing entries diff --git a/backend-pg/src/db/seeds/2-org.ts b/backend-pg/src/db/seeds/2-org.ts index ccc236909..ec7e9dd43 100644 --- a/backend-pg/src/db/seeds/2-org.ts +++ b/backend-pg/src/db/seeds/2-org.ts @@ -17,6 +17,7 @@ export async function seed(knex: Knex): Promise { // @ts-ignore because we need that id for api calls id: seedData1.organization.id, name: "infisical", + slug: "infisical", customerId: null } ]) diff --git a/backend-pg/src/db/seeds/3-project.ts b/backend-pg/src/db/seeds/3-project.ts index 4b79312b8..0f7d2408b 100644 --- a/backend-pg/src/db/seeds/3-project.ts +++ b/backend-pg/src/db/seeds/3-project.ts @@ -1,6 +1,6 @@ import { Knex } from "knex"; -import { TableName } from "../schemas"; +import { OrgMembershipRole, TableName } from "../schemas"; import { seedData1 } from "../seed-data"; export const DEFAULT_PROJECT_ENVS = [ @@ -19,10 +19,22 @@ export async function seed(knex: Knex): Promise { .insert({ name: seedData1.project.name, orgId: seedData1.organization.id, + slug: "first-project", // @ts-ignore pre calc id id: seedData1.project.id }) .returning("*"); + + await knex(TableName.ProjectKeys).insert({ + projectId: project.id, + senderId: seedData1.id + }); + + await knex(TableName.ProjectMembership).insert({ + projectId: project.id, + role: OrgMembershipRole.Admin, + userId: seedData1.id + }); const envs = await knex(TableName.Environment) .insert( DEFAULT_PROJECT_ENVS.map(({ name, slug }, index) => ({ diff --git a/backend-pg/src/ee/services/permission/permission-service.ts b/backend-pg/src/ee/services/permission/permission-service.ts index bc9f0bc3b..8549d00ee 100644 --- a/backend-pg/src/ee/services/permission/permission-service.ts +++ b/backend-pg/src/ee/services/permission/permission-service.ts @@ -149,7 +149,7 @@ export const permissionServiceFactory = ({ // user permission for a project in an organization const getUserProjectPermission = async (userId: string, projectId: string) => { const membership = await permissionDAL.getProjectPermission(userId, projectId); - if (!membership) throw new UnauthorizedError({ name: "User not in org" }); + if (!membership) throw new UnauthorizedError({ name: "User not in project" }); if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } @@ -161,7 +161,7 @@ export const permissionServiceFactory = ({ const getIdentityProjectPermission = async (identityId: string, projectId: string) => { const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId); - if (!membership) throw new UnauthorizedError({ name: "Identity not in org" }); + if (!membership) throw new UnauthorizedError({ name: "Identity not in project" }); if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } @@ -208,7 +208,7 @@ export const permissionServiceFactory = ({ default: throw new UnauthorizedError({ message: "Permission not defined", - name: "Get org permission" + name: "Get project permission" }); } }; diff --git a/backend-pg/src/services/secret-folder/secret-folder-service.ts b/backend-pg/src/services/secret-folder/secret-folder-service.ts index f68f89ec4..a9e7474c8 100644 --- a/backend-pg/src/services/secret-folder/secret-folder-service.ts +++ b/backend-pg/src/services/secret-folder/secret-folder-service.ts @@ -84,22 +84,17 @@ export const secretFolderServiceFactory = ({ .filter(Boolean); if (missingSegment.length) { const newFolders: Array = missingSegment.map( - (segment, i) => - i === 0 - ? { - name: segment, - parentId: parentFolder.id, - id: uuidv4(), - envId: env.id, - version: 1 - } - : { - name: segment, - parentId: newFolders[i - 1].id, - id: uuidv4(), - envId: env.id, - version: 1 - } + (segment) => { + const newFolder = { + name: segment, + parentId: parentFolderId, + id: uuidv4(), + envId: env.id, + version: 1 + }; + parentFolderId = newFolder.id; + return newFolder; + } ); parentFolderId = newFolders.at(-1)?.id as string; const docs = await folderDAL.insertMany(newFolders, tx); @@ -156,12 +151,12 @@ export const secretFolderServiceFactory = ({ const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new BadRequestError({ message: "Environment not found", name: "Update folder" }); - let folder = await folderDAL.findOne({ envId: env.id, id, parentId: parentFolder.id }); - // now folder api accepts id based change - // this is for cli and when cli removes this will remove this logic - if (!folder) { - folder = await folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id }); - } + const folder = await folderDAL + .findOne({ envId: env.id, id, parentId: parentFolder.id }) + // now folder api accepts id based change + // this is for cli backward compatiability and when cli removes this, we will remove this logic + .catch(() => folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id })); + if (!folder) throw new BadRequestError({ message: "Folder not found" }); const newFolder = await folderDAL.transaction(async (tx) => {