diff --git a/backend/e2e-test/routes/v2/secret-folder.spec.ts b/backend/e2e-test/routes/v2/secret-folder.spec.ts new file mode 100644 index 000000000..a2bed759a --- /dev/null +++ b/backend/e2e-test/routes/v2/secret-folder.spec.ts @@ -0,0 +1,165 @@ +import { seedData1 } from "@app/db/seed-data"; + +const createFolder = async (dto: { path: string; name: string }) => { + const res = await testServer.inject({ + method: "POST", + url: `/api/v2/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + projectId: seedData1.project.id, + environment: seedData1.environment.slug, + name: dto.name, + path: dto.path + } + }); + expect(res.statusCode).toBe(200); + return res.json().folder; +}; + +const deleteFolder = async (dto: { path: string; id: string }) => { + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v2/folders/${dto.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + projectId: seedData1.project.id, + environment: seedData1.environment.slug, + path: dto.path + } + }); + expect(res.statusCode).toBe(200); + return res.json().folder; +}; + +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 createdFolder = await createFolder({ path, name }); + // check for default environments + expect(createdFolder).toEqual( + expect.objectContaining({ + name, + id: expect.any(String) + }) + ); + await deleteFolder({ path, id: createdFolder.id }); + }); + + 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 newFolders = await Promise.all(expected.folders.map(({ name }) => createFolder({ name, path }))); + + const res = await testServer.inject({ + method: "GET", + url: `/api/v2/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + projectId: 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 >= expected.folders.length).toBeTruthy(); + expect(payload).toEqual({ + folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el))) + }); + + await Promise.all(newFolders.map(({ id }) => deleteFolder({ path, id }))); + }); + + test("Update a deep folder", async () => { + const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" }); + expect(newFolder).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: "folder-updated" + }) + ); + + const resUpdatedFolders = await testServer.inject({ + method: "GET", + url: `/api/v2/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + projectId: 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[0].name).toEqual("folder-updated"); + + await deleteFolder({ path: "/level1/level2", id: newFolder.id }); + }); + + test("Delete a deep folder", async () => { + const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" }); + const res = await testServer.inject({ + method: "DELETE", + url: `/api/v2/folders/${newFolder.id}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + projectId: 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/v2/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + query: { + projectId: 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/e2e-test/routes/v2/service-token.spec.ts b/backend/e2e-test/routes/v2/service-token.spec.ts index 025d9796f..4f72987cb 100644 --- a/backend/e2e-test/routes/v2/service-token.spec.ts +++ b/backend/e2e-test/routes/v2/service-token.spec.ts @@ -70,7 +70,7 @@ const createServiceToken = async ( const deleteServiceToken = async () => { const serviceTokenListRes = await testServer.inject({ method: "GET", - url: `/api/v1/workspace/${seedData1.project.id}/service-token-data`, + url: `/api/v1/projects/${seedData1.project.id}/service-token-data`, headers: { authorization: `Bearer ${jwtAuthToken}` } diff --git a/backend/e2e-test/routes/v4/secrets.spec.ts b/backend/e2e-test/routes/v4/secrets.spec.ts new file mode 100644 index 000000000..979adddf8 --- /dev/null +++ b/backend/e2e-test/routes/v4/secrets.spec.ts @@ -0,0 +1,678 @@ +import { SecretType } from "@app/db/schemas"; +import { seedData1 } from "@app/db/seed-data"; +import { AuthMode } from "@app/services/auth/auth-type"; + +type TRawSecret = { + secretKey: string; + secretValue: string; + secretComment?: string; + version: number; +}; + +const createSecret = async (dto: { path: string; key: string; value: string; comment: string; type?: SecretType }) => { + const createSecretReqBody = { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + type: dto.type || SecretType.Shared, + secretPath: dto.path, + secretKey: dto.key, + secretValue: dto.value, + secretComment: dto.comment + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v4/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + const createdSecretPayload = JSON.parse(createSecRes.payload); + expect(createdSecretPayload).toHaveProperty("secret"); + return createdSecretPayload.secret as TRawSecret; +}; + +const deleteSecret = async (dto: { path: string; key: string }) => { + const deleteSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v4/secrets/${dto.key}`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: dto.path + } + }); + expect(deleteSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(deleteSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + return updatedSecretPayload.secret as TRawSecret; +}; + +describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }])( + "Secret V4 - $auth mode", + async ({ auth }) => { + let folderId = ""; + let authToken = ""; + const secretTestCases = [ + { + path: "/", + secret: { + key: "SEC1", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "NESTED-SEC1", + value: "something-secret", + comment: "some comment" + } + }, + { + path: "/", + secret: { + key: "secret-key-2", + value: `-----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn + hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq + fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI + ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 + QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT + aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 + IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie + nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi + TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw + q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj + YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP + ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 + 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 + EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt + IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K + d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH + UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL + 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 + HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 + PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 + Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib + BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb + HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo + QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX + MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 + omQDpP86RX/hIIQ+JyLSaWYa + -----END PRIVATE KEY-----`, + comment: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "secret-key-3", + value: `-----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn + hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq + fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI + ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15 + QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT + aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46 + IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie + nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi + TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw + q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj + YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP + ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7 + 6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3 + EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt + IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K + d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH + UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL + 3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2 + HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0 + PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8 + Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib + BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb + HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo + QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX + MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9 + omQDpP86RX/hIIQ+JyLSaWYa + -----END PRIVATE KEY-----`, + comment: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation" + } + }, + { + path: "/nested1/nested2/folder", + secret: { + key: "secret-key-3", + value: + "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uCg==", + comment: "" + } + } + ]; + + beforeAll(async () => { + if (auth === AuthMode.JWT) { + authToken = jwtAuthToken; + } else if (auth === AuthMode.IDENTITY_ACCESS_TOKEN) { + const identityLogin = await testServer.inject({ + method: "POST", + url: "/api/v1/auth/universal-auth/login", + body: { + clientSecret: seedData1.machineIdentity.clientCredentials.secret, + clientId: seedData1.machineIdentity.clientCredentials.id + } + }); + expect(identityLogin.statusCode).toBe(200); + authToken = identityLogin.json().accessToken; + } + // create a deep folder + const folderCreate = await testServer.inject({ + method: "POST", + url: `/api/v2/folders`, + headers: { + authorization: `Bearer ${jwtAuthToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + name: "folder", + path: "/nested1/nested2" + } + }); + expect(folderCreate.statusCode).toBe(200); + folderId = folderCreate.json().folder.id; + }); + + afterAll(async () => { + const deleteFolder = await testServer.inject({ + method: "DELETE", + url: `/api/v2/folders/${folderId}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + path: "/nested1/nested2" + } + }); + expect(deleteFolder.statusCode).toBe(200); + }); + + const getSecrets = async (environment: string, secretPath = "/") => { + const res = await testServer.inject({ + method: "GET", + url: `/api/v4/secrets`, + headers: { + authorization: `Bearer ${authToken}` + }, + query: { + secretPath, + environment, + projectId: seedData1.projectV3.id + } + }); + const secrets: TRawSecret[] = JSON.parse(res.payload).secrets || []; + return secrets; + }; + + test.each(secretTestCases)("Create secret in path $path", async ({ secret, path }) => { + const createdSecret = await createSecret({ path, ...secret }); + expect(createdSecret.secretKey).toEqual(secret.key); + expect(createdSecret.secretValue).toEqual(secret.value); + expect(createdSecret.secretComment || "").toEqual(secret.comment); + expect(createdSecret.version).toEqual(1); + + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + secretKey: secret.key, + secretValue: secret.value, + type: SecretType.Shared + }) + ]) + ); + await deleteSecret({ path, key: secret.key }); + }); + + test.each(secretTestCases)("Get secret by name in path $path", async ({ secret, path }) => { + await createSecret({ path, ...secret }); + + const getSecByNameRes = await testServer.inject({ + method: "GET", + url: `/api/v4/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + query: { + secretPath: path, + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug + } + }); + expect(getSecByNameRes.statusCode).toBe(200); + const getSecretByNamePayload = JSON.parse(getSecByNameRes.payload); + expect(getSecretByNamePayload).toHaveProperty("secret"); + const decryptedSecret = getSecretByNamePayload.secret as TRawSecret; + expect(decryptedSecret.secretKey).toEqual(secret.key); + expect(decryptedSecret.secretValue).toEqual(secret.value); + expect(decryptedSecret.secretComment || "").toEqual(secret.comment); + + await deleteSecret({ path, key: secret.key }); + }); + + if (auth === AuthMode.JWT) { + test.each(secretTestCases)( + "Creating personal secret without shared throw error in path $path", + async ({ secret }) => { + const createSecretReqBody = { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + type: SecretType.Personal, + secretKey: secret.key, + secretValue: secret.value, + secretComment: secret.comment + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v4/secrets/SEC2`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: createSecretReqBody + }); + const payload = JSON.parse(createSecRes.payload); + expect(createSecRes.statusCode).toBe(400); + expect(payload.error).toEqual("BadRequest"); + } + ); + + test.each(secretTestCases)("Creating personal secret in path $path", async ({ secret, path }) => { + await createSecret({ path, ...secret }); + + const createSecretReqBody = { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + type: SecretType.Personal, + secretPath: path, + secretKey: secret.key, + secretValue: "personal-value", + secretComment: secret.comment + }; + const createSecRes = await testServer.inject({ + method: "POST", + url: `/api/v4/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: createSecretReqBody + }); + expect(createSecRes.statusCode).toBe(200); + + // list secrets should contain personal one and shared one + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + secretKey: secret.key, + secretValue: secret.value, + type: SecretType.Shared + }), + expect.objectContaining({ + secretKey: secret.key, + secretValue: "personal-value", + type: SecretType.Personal + }) + ]) + ); + + await deleteSecret({ path, key: secret.key }); + }); + + test.each(secretTestCases)( + "Deleting personal one should not delete shared secret in path $path", + async ({ secret, path }) => { + await createSecret({ path, ...secret }); // shared one + await createSecret({ path, ...secret, type: SecretType.Personal }); + + // shared secret deletion should delete personal ones also + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + secretKey: secret.key, + type: SecretType.Shared + }), + expect.not.objectContaining({ + secretKey: secret.key, + type: SecretType.Personal + }) + ]) + ); + await deleteSecret({ path, key: secret.key }); + } + ); + } + + test.each(secretTestCases)("Update secret in path $path", async ({ path, secret }) => { + await createSecret({ path, ...secret }); + const updateSecretReqBody = { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + type: SecretType.Shared, + secretPath: path, + secretKey: secret.key, + secretValue: "new-value", + secretComment: secret.comment + }; + const updateSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v4/secrets/${secret.key}`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: updateSecretReqBody + }); + expect(updateSecRes.statusCode).toBe(200); + const updatedSecretPayload = JSON.parse(updateSecRes.payload); + expect(updatedSecretPayload).toHaveProperty("secret"); + const decryptedSecret = updatedSecretPayload.secret; + expect(decryptedSecret.secretKey).toEqual(secret.key); + expect(decryptedSecret.secretValue).toEqual("new-value"); + expect(decryptedSecret.secretComment || "").toEqual(secret.comment); + + // list secret should have updated value + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + secretKey: secret.key, + secretValue: "new-value", + type: SecretType.Shared + }) + ]) + ); + + await deleteSecret({ path, key: secret.key }); + }); + + test.each(secretTestCases)("Delete secret in path $path", async ({ secret, path }) => { + await createSecret({ path, ...secret }); + const deletedSecret = await deleteSecret({ path, key: secret.key }); + expect(deletedSecret.secretKey).toEqual(secret.key); + + // shared secret deletion should delete personal ones also + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining([ + expect.objectContaining({ + secretKey: secret.key, + type: SecretType.Shared + }), + expect.objectContaining({ + secretKey: secret.key, + type: SecretType.Personal + }) + ]) + ); + }); + + test.each(secretTestCases)("Bulk create secrets in path $path", async ({ secret, path }) => { + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: secret.value, + secretComment: secret.comment + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(200); + const createSharedSecPayload = JSON.parse(createSharedSecRes.payload); + expect(createSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: secret.value, + type: SecretType.Shared + }) + ) + ) + ); + + await Promise.all( + Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) + ); + }); + + test.each(secretTestCases)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => { + await createSecret({ ...secret, key: `BULK-${secret.key}-1`, path }); + + const createSharedSecRes = await testServer.inject({ + method: "POST", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: secret.value, + secretComment: secret.comment + })) + } + }); + expect(createSharedSecRes.statusCode).toBe(400); + + await deleteSecret({ path, key: `BULK-${secret.key}-1` }); + }); + + test.each(secretTestCases)("Bulk update secrets in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => createSecret({ ...secret, key: `BULK-${secret.key}-${i + 1}`, path })) + ); + + const updateSharedSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: "update-value", + secretComment: secret.comment + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + await Promise.all( + Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) + ); + }); + + test.each(secretTestCases)("Bulk upsert secrets in path $path", async ({ secret, path }) => { + const updateSharedSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: path, + mode: "upsert", + secrets: Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: "update-value", + secretComment: secret.comment + })) + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ + secretKey: `BULK-${secret.key}-${i + 1}`, + secretValue: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + await Promise.all( + Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` })) + ); + }); + + test("Bulk upsert secrets in path multiple paths", async () => { + const firstBatchSecrets = Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-KEY-${secretTestCases[0].secret.key}-${i + 1}`, + secretValue: "update-value", + secretComment: "comment", + secretPath: secretTestCases[0].path + })); + const secondBatchSecrets = Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-KEY-${secretTestCases[1].secret.key}-${i + 1}`, + secretValue: "update-value", + secretComment: "comment", + secretPath: secretTestCases[1].path + })); + const testSecrets = [...firstBatchSecrets, ...secondBatchSecrets]; + + const updateSharedSecRes = await testServer.inject({ + method: "PATCH", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + mode: "upsert", + secrets: testSecrets + } + }); + expect(updateSharedSecRes.statusCode).toBe(200); + const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload); + expect(updateSharedSecPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const firstBatchSecretsOnInfisical = await getSecrets(seedData1.environment.slug, secretTestCases[0].path); + expect(firstBatchSecretsOnInfisical).toEqual( + expect.arrayContaining( + firstBatchSecrets.map((el) => + expect.objectContaining({ + secretKey: el.secretKey, + secretValue: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + const secondBatchSecretsOnInfisical = await getSecrets(seedData1.environment.slug, secretTestCases[1].path); + expect(secondBatchSecretsOnInfisical).toEqual( + expect.arrayContaining( + secondBatchSecrets.map((el) => + expect.objectContaining({ + secretKey: el.secretKey, + secretValue: "update-value", + type: SecretType.Shared + }) + ) + ) + ); + await Promise.all(testSecrets.map((el) => deleteSecret({ path: el.secretPath, key: el.secretKey }))); + }); + + test.each(secretTestCases)("Bulk delete secrets in path $path", async ({ secret, path }) => { + await Promise.all( + Array.from(Array(5)).map((_e, i) => createSecret({ ...secret, key: `BULK-${secret.key}-${i + 1}`, path })) + ); + + const deletedSharedSecRes = await testServer.inject({ + method: "DELETE", + url: `/api/v4/secrets/batch`, + headers: { + authorization: `Bearer ${authToken}` + }, + body: { + projectId: seedData1.projectV3.id, + environment: seedData1.environment.slug, + secretPath: path, + secrets: Array.from(Array(5)).map((_e, i) => ({ + secretKey: `BULK-${secret.key}-${i + 1}` + })) + } + }); + + expect(deletedSharedSecRes.statusCode).toBe(200); + const deletedSecretPayload = JSON.parse(deletedSharedSecRes.payload); + expect(deletedSecretPayload).toHaveProperty("secrets"); + + // bulk ones should exist + const secrets = await getSecrets(seedData1.environment.slug, path); + expect(secrets).toEqual( + expect.not.arrayContaining( + Array.from(Array(5)).map((_e, i) => + expect.objectContaining({ + secretKey: `BULK-${secret.value}-${i + 1}`, + type: SecretType.Shared + }) + ) + ) + ); + }); + } +); diff --git a/backend/src/db/migrations/20250204025010_app-connections-and-secret-syncs-unique-constraint.ts b/backend/src/db/migrations/20250204025010_app-connections-and-secret-syncs-unique-constraint.ts index 348694ae7..f9b46559d 100644 --- a/backend/src/db/migrations/20250204025010_app-connections-and-secret-syncs-unique-constraint.ts +++ b/backend/src/db/migrations/20250204025010_app-connections-and-secret-syncs-unique-constraint.ts @@ -1,5 +1,6 @@ import { Knex } from "knex"; +import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; import { TableName } from "@app/db/schemas"; export async function up(knex: Knex): Promise { @@ -13,9 +14,7 @@ export async function up(knex: Knex): Promise { } export async function down(knex: Knex): Promise { - await knex.schema.alterTable(TableName.AppConnection, (t) => { - t.dropUnique(["orgId", "name"]); - }); + await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex); await knex.schema.alterTable(TableName.SecretSync, (t) => { t.dropUnique(["projectId", "name"]); diff --git a/backend/src/db/migrations/20250912011133_app-connection-project-id-col.ts b/backend/src/db/migrations/20250912011133_app-connection-project-id-col.ts new file mode 100644 index 000000000..5c846a739 --- /dev/null +++ b/backend/src/db/migrations/20250912011133_app-connection-project-id-col.ts @@ -0,0 +1,41 @@ +import { Knex } from "knex"; + +import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; +import { TableName } from "@app/db/schemas"; + +const UNIQUE_NAME_ORG_CONNECTION_INDEX = "unique_name_org_app_connection"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.AppConnection)) { + // we can't add the constraint back after up since there may be conflicting names so we do if exists + await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex); + + if (!(await knex.schema.hasColumn(TableName.AppConnection, "projectId"))) { + await knex.schema.alterTable(TableName.AppConnection, (t) => { + t.string("projectId").nullable(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + // unique name for project-level connections + t.unique(["name", "projectId", "orgId"]); + }); + + // unique name for org-level connections + await knex.raw(` + CREATE UNIQUE INDEX ${UNIQUE_NAME_ORG_CONNECTION_INDEX} + ON ${TableName.AppConnection} ("name", "orgId") + WHERE "projectId" IS NULL + `); + } + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.AppConnection)) { + if (await knex.schema.hasColumn(TableName.AppConnection, "projectId")) { + await knex.schema.alterTable(TableName.AppConnection, (t) => { + t.dropUnique(["name", "projectId", "orgId"]); + t.dropColumn("projectId"); + }); + await dropConstraintIfExists(TableName.AppConnection, UNIQUE_NAME_ORG_CONNECTION_INDEX, knex); + } + } +} diff --git a/backend/src/db/schemas/app-connections.ts b/backend/src/db/schemas/app-connections.ts index 2218b75ce..41d1df17f 100644 --- a/backend/src/db/schemas/app-connections.ts +++ b/backend/src/db/schemas/app-connections.ts @@ -21,7 +21,8 @@ export const AppConnectionsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), isPlatformManagedCredentials: z.boolean().default(false).nullable().optional(), - gatewayId: z.string().uuid().nullable().optional() + gatewayId: z.string().uuid().nullable().optional(), + projectId: z.string().nullable().optional() }); export type TAppConnections = z.infer; diff --git a/backend/src/ee/routes/v1/deprecated-project-role-router.ts b/backend/src/ee/routes/v1/deprecated-project-role-router.ts new file mode 100644 index 000000000..d0ffc0239 --- /dev/null +++ b/backend/src/ee/routes/v1/deprecated-project-role-router.ts @@ -0,0 +1,287 @@ +import { packRules } from "@casl/ability/extra"; +import { z } from "zod"; + +import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; +import { + backfillPermissionV1SchemaToV2Schema, + ProjectPermissionV1Schema +} from "@app/ee/services/permission/project-permission"; +import { PROJECT_ROLE } from "@app/lib/api-docs"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas"; +import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; + +export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectSlug/roles", + config: { + rateLimit: writeLimit + }, + schema: { + description: "Create a project role", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectSlug: z.string().trim().describe(PROJECT_ROLE.CREATE.projectSlug) + }), + body: z.object({ + slug: slugSchema({ max: 64 }) + .refine( + (val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole), + "Please choose a different slug, the slug you have entered is reserved" + ) + .describe(PROJECT_ROLE.CREATE.slug), + name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name), + description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description), + permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions) + }), + response: { + 200: z.object({ + role: SanitizedRoleSchemaV1 + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const role = await server.services.projectRole.createRole({ + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + filter: { + type: ProjectRoleServiceIdentifierType.SLUG, + projectSlug: req.params.projectSlug + }, + data: { + ...req.body, + permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))) + } + }); + + return { role }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectSlug/roles/:roleId", + config: { + rateLimit: writeLimit + }, + schema: { + description: "Update a project role", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectSlug: z.string().trim().describe(PROJECT_ROLE.UPDATE.projectSlug), + roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId) + }), + body: z.object({ + slug: slugSchema({ max: 64 }) + .refine( + (val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole), + "Please choose a different slug, the slug you have entered is reserved" + ) + .describe(PROJECT_ROLE.UPDATE.slug) + .optional(), + name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name), + description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description), + permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional() + }), + response: { + 200: z.object({ + role: SanitizedRoleSchemaV1 + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const role = await server.services.projectRole.updateRole({ + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + roleId: req.params.roleId, + data: { + ...req.body, + permissions: req.body.permissions + ? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))) + : undefined + } + }); + return { role }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectSlug/roles/:roleId", + config: { + rateLimit: writeLimit + }, + schema: { + description: "Delete a project role", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectSlug: z.string().trim().describe(PROJECT_ROLE.DELETE.projectSlug), + roleId: z.string().trim().describe(PROJECT_ROLE.DELETE.roleId) + }), + response: { + 200: z.object({ + role: SanitizedRoleSchemaV1 + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const role = await server.services.projectRole.deleteRole({ + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + roleId: req.params.roleId + }); + return { role }; + } + }); + + server.route({ + method: "GET", + url: "/:projectSlug/roles", + config: { + rateLimit: readLimit + }, + schema: { + description: "List project role", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + projectSlug: z.string().trim().describe(PROJECT_ROLE.LIST.projectSlug) + }), + response: { + 200: z.object({ + roles: ProjectRolesSchema.omit({ permissions: true, version: true }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const roles = await server.services.projectRole.listRoles({ + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + filter: { + type: ProjectRoleServiceIdentifierType.SLUG, + projectSlug: req.params.projectSlug + } + }); + return { roles }; + } + }); + + server.route({ + method: "GET", + url: "/:projectSlug/roles/slug/:slug", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectSlug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.projectSlug), + slug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.roleSlug) + }), + response: { + 200: z.object({ + role: SanitizedRoleSchemaV1.omit({ version: true }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const role = await server.services.projectRole.getRoleBySlug({ + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + filter: { + type: ProjectRoleServiceIdentifierType.SLUG, + projectSlug: req.params.projectSlug + }, + roleSlug: req.params.slug + }); + + return { role }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/permissions", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + data: z.object({ + membership: z.object({ + id: z.string(), + roles: z + .object({ + role: z.string() + }) + .array() + }), + assumedPrivilegeDetails: z + .object({ + actorId: z.string(), + actorType: z.string(), + actorName: z.string(), + actorEmail: z.string().optional() + }) + .optional(), + permissions: z.any().array() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission( + req.permission.id, + req.params.projectId, + req.permission.authMethod, + req.permission.orgId + ); + + return { + data: { + permissions, + membership, + assumedPrivilegeDetails + } + }; + } + }); +}; diff --git a/backend/src/ee/routes/v1/deprecated-project-router.ts b/backend/src/ee/routes/v1/deprecated-project-router.ts new file mode 100644 index 000000000..6c4175f84 --- /dev/null +++ b/backend/src/ee/routes/v1/deprecated-project-router.ts @@ -0,0 +1,195 @@ +import { z } from "zod"; + +import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas"; +import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs"; +import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn"; +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:workspaceId/secret-snapshots", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Return project secret snapshots ids", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.projectId) + }), + querystring: z.object({ + environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path), + offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset), + limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit) + }), + response: { + 200: z.object({ + secretSnapshots: SecretSnapshotsSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretSnapshots = await server.services.snapshot.listSnapshots({ + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + ...req.query + }); + return { secretSnapshots }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/secret-snapshots/count", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + querystring: z.object({ + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + count: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const count = await server.services.snapshot.projectSecretSnapshotCount({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + environment: req.query.environment, + path: req.query.path + }); + return { count }; + } + }); + + /* + * Daniel: This endpoint is no longer is use. + * We are keeping it for now because it has been exposed in our public api docs for a while, so by removing it we are likely to break users workflows. + * + * Please refer to the new endpoint, GET /api/v1/organization/audit-logs, for the same (and more) functionality. + */ + server.route({ + method: "GET", + url: "/:workspaceId/audit-logs", + config: { + rateLimit: readLimit + }, + schema: { + description: "Return audit logs", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId) + }), + querystring: z + .object({ + eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType), + userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType), + startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate), + endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate), + offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset), + limit: z.coerce.number().max(1000).default(20).describe(AUDIT_LOGS.EXPORT.limit), + actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor) + }) + .superRefine((el, ctx) => { + if (el.endDate && el.startDate) { + const startDate = new Date(el.startDate); + const endDate = new Date(el.endDate); + const maxAllowedDate = new Date(startDate); + maxAllowedDate.setMonth(maxAllowedDate.getMonth() + 3); + if (endDate < startDate) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["endDate"], + message: "End date cannot be before start date" + }); + } + if (endDate > maxAllowedDate) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["endDate"], + message: "Dates must be within 3 months" + }); + } + } + }), + response: { + 200: z.object({ + auditLogs: AuditLogsSchema.omit({ + eventMetadata: true, + eventType: true, + actor: true, + actorMetadata: true + }) + .merge( + z.object({ + project: z + .object({ + name: z.string(), + slug: z.string() + }) + .optional(), + event: z.object({ + type: z.string(), + metadata: z.any() + }), + actor: z.object({ + type: z.string(), + metadata: z.any() + }) + }) + ) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const auditLogs = await server.services.auditLog.listAuditLogs({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + + filter: { + ...req.query, + projectId: req.params.workspaceId, + endDate: req.query.endDate || new Date().toISOString(), + startDate: req.query.startDate || getLastMidnightDateISO(), + auditLogActorId: req.query.actor, + eventType: req.query.eventType ? [req.query.eventType] : undefined + } + }); + return { auditLogs }; + } + }); +}; diff --git a/backend/src/ee/routes/v1/deprecated-secret-approval-policy-router.ts b/backend/src/ee/routes/v1/deprecated-secret-approval-policy-router.ts new file mode 100644 index 000000000..42f6f9c12 --- /dev/null +++ b/backend/src/ee/routes/v1/deprecated-secret-approval-policy-router.ts @@ -0,0 +1,293 @@ +import { nanoid } from "nanoid"; +import { z } from "zod"; + +import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { EnforcementLevel } from "@app/lib/types"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { sapPubSchema } from "@app/server/routes/sanitizedSchemas"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerDeprecatedSecretApprovalPolicyRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "POST", + config: { + rateLimit: writeLimit + }, + schema: { + body: z + .object({ + workspaceId: z.string(), + name: z.string().optional(), + environment: z.string().optional(), + environments: z.string().array().optional(), + secretPath: z + .string() + .min(1, { message: "Secret path cannot be empty" }) + .transform((val) => removeTrailingSlash(val)), + approvers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(ApproverType.Group), id: z.string() }), + z.object({ + type: z.literal(ApproverType.User), + id: z.string().optional(), + username: z.string().optional() + }) + ]) + .array() + .min(1, { message: "At least one approver should be provided" }) + .max(100, "Cannot have more than 100 approvers"), + bypassers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(BypasserType.Group), id: z.string() }), + z.object({ + type: z.literal(BypasserType.User), + id: z.string().optional(), + username: z.string().optional() + }) + ]) + .array() + .max(100, "Cannot have more than 100 bypassers") + .optional(), + approvals: z.number().min(1).default(1), + enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), + allowedSelfApprovals: z.boolean().default(true) + }) + .refine((data) => data.environment || data.environments, "At least one environment should be provided"), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.body.workspaceId, + ...req.body, + name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`, + enforcementLevel: req.body.enforcementLevel + }); + return { approval }; + } + }); + + server.route({ + url: "/:sapId", + method: "PATCH", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + body: z.object({ + name: z.string().optional(), + approvers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(ApproverType.Group), id: z.string() }), + z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() }) + ]) + .array() + .min(1, { message: "At least one approver should be provided" }) + .max(100, "Cannot have more than 100 approvers"), + bypassers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(BypasserType.Group), id: z.string() }), + z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() }) + ]) + .array() + .max(100, "Cannot have more than 100 bypassers") + .optional(), + approvals: z.number().min(1).default(1), + secretPath: z + .string() + .trim() + .min(1, { message: "Secret path cannot be empty" }) + .optional() + .transform((val) => (val ? removeTrailingSlash(val) : undefined)), + enforcementLevel: z.nativeEnum(EnforcementLevel).optional(), + allowedSelfApprovals: z.boolean().default(true), + environments: z.array(z.string()).optional() + }), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + secretPolicyId: req.params.sapId + }); + return { approval }; + } + }); + + server.route({ + url: "/:sapId", + method: "DELETE", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + response: { + 200: z.object({ + approval: sapPubSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + secretPolicyId: req.params.sapId + }); + return { approval }; + } + }); + + server.route({ + url: "/", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: z.object({ + approvals: sapPubSchema + .extend({ + approvers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(ApproverType) + }) + .array(), + bypassers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(BypasserType) + }) + .array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.query.workspaceId + }); + return { approvals }; + } + }); + + server.route({ + url: "/:sapId", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + sapId: z.string() + }), + response: { + 200: z.object({ + approval: sapPubSchema.extend({ + approvers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(ApproverType), + username: z.string().nullable().optional() + }) + .array(), + bypassers: z + .object({ + id: z.string().nullable().optional(), + type: z.nativeEnum(BypasserType), + username: z.string().nullable().optional() + }) + .array() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const approval = await server.services.secretApprovalPolicy.getSecretApprovalPolicyById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.params + }); + + return { approval }; + } + }); + + server.route({ + url: "/board", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + secretPath: z.string().trim().transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + policy: sapPubSchema + .extend({ + userApprovers: z.object({ userId: z.string().nullable().optional() }).array() + }) + .optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.query.workspaceId, + ...req.query + }); + return { policy }; + } + }); +}; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 5a43de381..56d450df3 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -5,6 +5,9 @@ import { registerAccessApprovalRequestRouter } from "./access-approval-request-r import { registerAssumePrivilegeRouter } from "./assume-privilege-router"; import { AUDIT_LOG_STREAM_REGISTER_ROUTER_MAP, registerAuditLogStreamRouter } from "./audit-log-stream-routers"; import { registerCaCrlRouter } from "./certificate-authority-crl-router"; +import { registerDeprecatedProjectRoleRouter } from "./deprecated-project-role-router"; +import { registerDeprecatedProjectRouter } from "./deprecated-project-router"; +import { registerDeprecatedSecretApprovalPolicyRouter } from "./deprecated-secret-approval-policy-router"; import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router"; import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router"; import { registerDynamicSecretRouter } from "./dynamic-secret-router"; @@ -27,7 +30,6 @@ import { registerRateLimitRouter } from "./rate-limit-router"; import { registerRelayRouter } from "./relay-router"; import { registerSamlRouter } from "./saml-router"; import { registerScimRouter } from "./scim-router"; -import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router"; import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router"; import { registerSecretRotationRouter } from "./secret-rotation-router"; @@ -47,18 +49,29 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { // org role starts with organization await server.register(registerOrgRoleRouter, { prefix: "/organization" }); await server.register(registerLicenseRouter, { prefix: "/organizations" }); + + // depreciated in favour of infisical workspace await server.register( async (projectRouter) => { - await projectRouter.register(registerProjectRoleRouter); - await projectRouter.register(registerProjectRouter); - await projectRouter.register(registerTrustedIpRouter); - await projectRouter.register(registerAssumePrivilegeRouter); + await projectRouter.register(registerDeprecatedProjectRoleRouter); + await projectRouter.register(registerDeprecatedProjectRouter); }, { prefix: "/workspace" } ); + + await server.register( + async (projectRouter) => { + await projectRouter.register(registerProjectRoleRouter); + await projectRouter.register(registerTrustedIpRouter); + await projectRouter.register(registerAssumePrivilegeRouter); + await projectRouter.register(registerProjectRouter); + }, + { prefix: "/projects" } + ); + await server.register(registerSnapshotRouter, { prefix: "/secret-snapshot" }); await server.register(registerPITRouter, { prefix: "/pit" }); - await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); + await server.register(registerDeprecatedSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); await server.register(registerSecretApprovalRequestRouter, { prefix: "/secret-approval-requests" }); diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index 949d4cf7e..23633c8f5 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -2,26 +2,26 @@ import { packRules } from "@casl/ability/extra"; import { z } from "zod"; import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; -import { - backfillPermissionV1SchemaToV2Schema, - ProjectPermissionV1Schema -} from "@app/ee/services/permission/project-permission"; -import { PROJECT_ROLE } from "@app/lib/api-docs"; +import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; +import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; +import { ApiDocsTags, PROJECT_ROLE } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas"; +import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:projectSlug/roles", + url: "/:projectId/roles", config: { rateLimit: writeLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectRoles], description: "Create a project role", security: [ { @@ -29,10 +29,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - projectSlug: z.string().trim().describe(PROJECT_ROLE.CREATE.projectSlug) + projectId: z.string().trim().describe(PROJECT_ROLE.CREATE.projectId) }), body: z.object({ - slug: slugSchema({ max: 64 }) + slug: slugSchema({ min: 1, max: 64 }) .refine( (val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole), "Please choose a different slug, the slug you have entered is reserved" @@ -40,11 +40,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { .describe(PROJECT_ROLE.CREATE.slug), name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name), description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description), - permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions) + permissions: ProjectPermissionV2Schema.array() + .describe(PROJECT_ROLE.CREATE.permissions) + .refine(checkForInvalidPermissionCombination) }), response: { 200: z.object({ - role: SanitizedRoleSchemaV1 + role: SanitizedRoleSchema }) } }, @@ -56,26 +58,27 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { actorOrgId: req.permission.orgId, actor: req.permission.type, filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug + type: ProjectRoleServiceIdentifierType.ID, + projectId: req.params.projectId }, data: { ...req.body, - permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))) + permissions: JSON.stringify(packRules(req.body.permissions)) } }); - return { role }; } }); server.route({ method: "PATCH", - url: "/:projectSlug/roles/:roleId", + url: "/:projectId/roles/:roleId", config: { rateLimit: writeLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectRoles], description: "Update a project role", security: [ { @@ -83,24 +86,27 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - projectSlug: z.string().trim().describe(PROJECT_ROLE.UPDATE.projectSlug), + projectId: z.string().trim().describe(PROJECT_ROLE.UPDATE.projectId), roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId) }), body: z.object({ - slug: slugSchema({ max: 64 }) + slug: slugSchema({ min: 1, max: 64 }) .refine( (val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole), "Please choose a different slug, the slug you have entered is reserved" ) - .describe(PROJECT_ROLE.UPDATE.slug) - .optional(), + .optional() + .describe(PROJECT_ROLE.UPDATE.slug), name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name), description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description), - permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional() + permissions: ProjectPermissionV2Schema.array() + .describe(PROJECT_ROLE.UPDATE.permissions) + .optional() + .superRefine(checkForInvalidPermissionCombination) }), response: { 200: z.object({ - role: SanitizedRoleSchemaV1 + role: SanitizedRoleSchema }) } }, @@ -114,9 +120,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { roleId: req.params.roleId, data: { ...req.body, - permissions: req.body.permissions - ? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))) - : undefined + permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined } }); return { role }; @@ -125,11 +129,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", - url: "/:projectSlug/roles/:roleId", + url: "/:projectId/roles/:roleId", config: { rateLimit: writeLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectRoles], description: "Delete a project role", security: [ { @@ -137,12 +143,12 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - projectSlug: z.string().trim().describe(PROJECT_ROLE.DELETE.projectSlug), + projectId: z.string().trim().describe(PROJECT_ROLE.DELETE.projectId), roleId: z.string().trim().describe(PROJECT_ROLE.DELETE.roleId) }), response: { 200: z.object({ - role: SanitizedRoleSchemaV1 + role: SanitizedRoleSchema }) } }, @@ -161,11 +167,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:projectSlug/roles", + url: "/:projectId/roles", config: { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectRoles], description: "List project role", security: [ { @@ -173,7 +181,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - projectSlug: z.string().trim().describe(PROJECT_ROLE.LIST.projectSlug) + projectId: z.string().trim().describe(PROJECT_ROLE.LIST.projectId) }), response: { 200: z.object({ @@ -189,8 +197,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { actorOrgId: req.permission.orgId, actor: req.permission.type, filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug + type: ProjectRoleServiceIdentifierType.ID, + projectId: req.params.projectId } }); return { roles }; @@ -199,18 +207,20 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:projectSlug/roles/slug/:slug", + url: "/:projectId/roles/slug/:roleSlug", config: { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectRoles], params: z.object({ - projectSlug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.projectSlug), - slug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.roleSlug) + projectId: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.projectId), + roleSlug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.roleSlug) }), response: { 200: z.object({ - role: SanitizedRoleSchemaV1.omit({ version: true }) + role: SanitizedRoleSchema.omit({ version: true }) }) } }, @@ -222,12 +232,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { actorOrgId: req.permission.orgId, actor: req.permission.type, filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug + type: ProjectRoleServiceIdentifierType.ID, + projectId: req.params.projectId }, - roleSlug: req.params.slug + roleSlug: req.params.roleSlug }); - return { role }; } }); diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 8d8cc4817..4988565f8 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -1,9 +1,9 @@ import { z } from "zod"; -import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas"; -import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; -import { ApiDocsTags, AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs"; -import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn"; +import { SecretSnapshotsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; +import { removeTrailingSlash } from "@app/lib/fn"; 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"; @@ -12,7 +12,7 @@ import { KmsType } from "@app/services/kms/kms-types"; export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/secret-snapshots", + url: "/:projectId/secret-snapshots", config: { rateLimit: readLimit }, @@ -26,7 +26,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId) + projectId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.projectId) }), querystring: z.object({ environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment), @@ -47,7 +47,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, ...req.query }); return { secretSnapshots }; @@ -56,13 +56,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/secret-snapshots/count", + url: "/:projectId/secret-snapshots/count", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), querystring: z.object({ environment: z.string().trim(), @@ -81,7 +81,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, environment: req.query.environment, path: req.query.path }); @@ -89,140 +89,15 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); - /* - * Daniel: This endpoint is no longer is use. - * We are keeping it for now because it has been exposed in our public api docs for a while, so by removing it we are likely to break users workflows. - * - * Please refer to the new endpoint, GET /api/v1/organization/audit-logs, for the same (and more) functionality. - */ server.route({ method: "GET", - url: "/:workspaceId/audit-logs", - config: { - rateLimit: readLimit - }, - schema: { - description: "Return audit logs", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId) - }), - querystring: z - .object({ - eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType), - userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType), - startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate), - endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate), - offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset), - limit: z.coerce.number().max(1000).default(20).describe(AUDIT_LOGS.EXPORT.limit), - actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor) - }) - .superRefine((el, ctx) => { - if (el.endDate && el.startDate) { - const startDate = new Date(el.startDate); - const endDate = new Date(el.endDate); - const maxAllowedDate = new Date(startDate); - maxAllowedDate.setMonth(maxAllowedDate.getMonth() + 3); - if (endDate < startDate) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ["endDate"], - message: "End date cannot be before start date" - }); - } - if (endDate > maxAllowedDate) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ["endDate"], - message: "Dates must be within 3 months" - }); - } - } - }), - response: { - 200: z.object({ - auditLogs: AuditLogsSchema.omit({ - eventMetadata: true, - eventType: true, - actor: true, - actorMetadata: true - }) - .merge( - z.object({ - project: z - .object({ - name: z.string(), - slug: z.string() - }) - .optional(), - event: z.object({ - type: z.string(), - metadata: z.any() - }), - actor: z.object({ - type: z.string(), - metadata: z.any() - }) - }) - ) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const auditLogs = await server.services.auditLog.listAuditLogs({ - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - actor: req.permission.type, - - filter: { - ...req.query, - projectId: req.params.workspaceId, - endDate: req.query.endDate || new Date().toISOString(), - startDate: req.query.startDate || getLastMidnightDateISO(), - auditLogActorId: req.query.actor, - eventType: req.query.eventType ? [req.query.eventType] : undefined - } - }); - return { auditLogs }; - } - }); - - server.route({ - method: "GET", - url: "/:workspaceId/audit-logs/filters/actors", + url: "/:projectId/kms", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() - }), - response: { - 200: z.object({ - actors: z.string().array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async () => ({ actors: [] }) - }); - - server.route({ - method: "GET", - url: "/:workspaceId/kms", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -241,7 +116,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return kmsKey; @@ -250,13 +125,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", - url: "/:workspaceId/kms", + url: "/:projectId/kms", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), body: z.object({ kms: z.discriminatedUnion("type", [ @@ -281,13 +156,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, ...req.body }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.UPDATE_PROJECT_KMS, metadata: { @@ -307,13 +182,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/kms/backup", + url: "/:projectId/kms/backup", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -328,12 +203,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.GET_PROJECT_KMS_BACKUP, metadata: {} @@ -346,13 +221,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:workspaceId/kms/backup", + url: "/:projectId/kms/backup", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), body: z.object({ backup: z.string().min(1) @@ -374,13 +249,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, backup: req.body.backup }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.LOAD_PROJECT_KMS_BACKUP, metadata: {} @@ -393,13 +268,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:workspaceId/migrate-v3", + url: "/:projectId/migrate-v3", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { @@ -415,7 +290,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return migration; diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index bdc9c2dcd..6a9b59f00 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -27,7 +27,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim().optional(), committer: z.string().trim().optional(), search: z.string().trim().optional(), @@ -80,7 +80,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { approvals, totalCount }; } @@ -94,7 +94,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), policyId: z.string().trim().optional() }), response: { @@ -113,7 +113,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId, + projectId: req.query.projectId, policyId: req.query.policyId }); return { approvals }; diff --git a/backend/src/ee/routes/v1/trusted-ip-router.ts b/backend/src/ee/routes/v1/trusted-ip-router.ts index b6fc3cc90..5416e8613 100644 --- a/backend/src/ee/routes/v1/trusted-ip-router.ts +++ b/backend/src/ee/routes/v1/trusted-ip-router.ts @@ -9,13 +9,13 @@ import { AuthMode } from "@app/services/auth/auth-type"; export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/trusted-ips", + url: "/:projectId/trusted-ips", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -27,7 +27,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const trustedIps = await server.services.trustedIp.listIpsByProjectId({ actorAuthMethod: req.permission.authMethod, - projectId: req.params.workspaceId, + projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId @@ -38,13 +38,13 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:workspaceId/trusted-ips", + url: "/:projectId/trusted-ips", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), body: z.object({ ipAddress: z.string().trim(), @@ -61,7 +61,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const { trustedIp, project } = await server.services.trustedIp.addProjectIp({ actorAuthMethod: req.permission.authMethod, - projectId: req.params.workspaceId, + projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, @@ -86,13 +86,13 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", - url: "/:workspaceId/trusted-ips/:trustedIpId", + url: "/:projectId/trusted-ips/:trustedIpId", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), trustedIpId: z.string().trim() }), body: z.object({ @@ -108,7 +108,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const { trustedIp, project } = await server.services.trustedIp.updateProjectIp({ - projectId: req.params.workspaceId, + projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -135,13 +135,13 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", - url: "/:workspaceId/trusted-ips/:trustedIpId", + url: "/:projectId/trusted-ips/:trustedIpId", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), trustedIpId: z.string().trim() }), response: { @@ -153,7 +153,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const { trustedIp, project } = await server.services.trustedIp.deleteProjectIp({ - projectId: req.params.workspaceId, + projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, diff --git a/backend/src/ee/routes/v2/project-role-router.ts b/backend/src/ee/routes/v2/deprecated-project-role-router.ts similarity index 98% rename from backend/src/ee/routes/v2/project-role-router.ts rename to backend/src/ee/routes/v2/deprecated-project-role-router.ts index 538929316..41a04974d 100644 --- a/backend/src/ee/routes/v2/project-role-router.ts +++ b/backend/src/ee/routes/v2/deprecated-project-role-router.ts @@ -12,7 +12,7 @@ import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; -export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { +export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/:projectId/roles", diff --git a/backend/src/ee/routes/v2/index.ts b/backend/src/ee/routes/v2/index.ts index e082773dd..c402ab00a 100644 --- a/backend/src/ee/routes/v2/index.ts +++ b/backend/src/ee/routes/v2/index.ts @@ -7,15 +7,16 @@ import { SECRET_SCANNING_REGISTER_ROUTER_MAP } from "@app/ee/routes/v2/secret-scanning-v2-routers"; +import { registerDeprecatedProjectRoleRouter } from "./deprecated-project-role-router"; import { registerGatewayV2Router } from "./gateway-router"; import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router"; -import { registerProjectRoleRouter } from "./project-role-router"; +import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; export const registerV2EERoutes = async (server: FastifyZodProvider) => { - // org role starts with organization await server.register( async (projectRouter) => { - await projectRouter.register(registerProjectRoleRouter); + // this has been depreciated and moved to /api/v1/projects + await projectRouter.register(registerDeprecatedProjectRoleRouter); }, { prefix: "/workspace" } ); @@ -26,6 +27,8 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => { await server.register(registerGatewayV2Router, { prefix: "/gateways" }); + await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" }); + await server.register( async (secretRotationV2Router) => { // register generic secret rotation endpoints diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v2/secret-approval-policy-router.ts similarity index 97% rename from backend/src/ee/routes/v1/secret-approval-policy-router.ts rename to backend/src/ee/routes/v2/secret-approval-policy-router.ts index dc87b83f2..f7f770197 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v2/secret-approval-policy-router.ts @@ -19,7 +19,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi schema: { body: z .object({ - workspaceId: z.string(), + projectId: z.string(), name: z.string().optional(), environment: z.string().optional(), environments: z.string().array().optional(), @@ -69,7 +69,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId, ...req.body, name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`, enforcementLevel: req.body.enforcementLevel @@ -174,7 +173,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi }, schema: { querystring: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -204,7 +203,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { approvals }; } @@ -263,7 +262,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi }, schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), secretPath: z.string().trim().transform(removeTrailingSlash) }), @@ -284,7 +283,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId, ...req.query }); return { policy }; 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 fa5208ad4..07773885a 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", @@ -250,9 +250,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", @@ -265,8 +265,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", @@ -393,6 +393,8 @@ export enum EventType { CREATE_APP_CONNECTION = "create-app-connection", UPDATE_APP_CONNECTION = "update-app-connection", DELETE_APP_CONNECTION = "delete-app-connection", + GET_APP_CONNECTION_USAGE = "get-app-connection-usage", + MIGRATE_APP_CONNECTION = "migrate-app-connection", CREATE_SHARED_SECRET = "create-shared-secret", CREATE_SECRET_REQUEST = "create-secret-request", DELETE_SHARED_SECRET = "delete-shared-secret", @@ -665,8 +667,8 @@ interface DeleteSecretBatchEvent { }; } -interface GetWorkspaceKeyEvent { - type: EventType.GET_WORKSPACE_KEY; +interface GetProjectKeyEvent { + type: EventType.GET_PROJECT_KEY; metadata: { keyId: string; }; @@ -1573,24 +1575,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; @@ -1729,7 +1731,7 @@ interface DeleteSecretImportEvent { } interface UpdateUserRole { - type: EventType.UPDATE_USER_WORKSPACE_ROLE; + type: EventType.UPDATE_USER_PROJECT_ROLE; metadata: { userId: string; email: string; @@ -1739,7 +1741,7 @@ interface UpdateUserRole { } interface UpdateUserDeniedPermissions { - type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS; + type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS; metadata: { userId: string; email: string; @@ -2797,14 +2799,31 @@ interface GetAppConnectionEvent { }; } +interface GetAppConnectionUsageEvent { + type: EventType.GET_APP_CONNECTION_USAGE; + metadata: { + connectionId: string; + }; +} + +interface MigrateAppConnectionEvent { + type: EventType.MIGRATE_APP_CONNECTION; + metadata: { + connectionId: string; + }; +} + interface CreateAppConnectionEvent { type: EventType.CREATE_APP_CONNECTION; - metadata: Omit & { connectionId: string }; + metadata: Omit & { connectionId: string }; } interface UpdateAppConnectionEvent { type: EventType.UPDATE_APP_CONNECTION; - metadata: Omit & { connectionId: string; credentialsUpdated: boolean }; + metadata: Omit & { + connectionId: string; + credentialsUpdated: boolean; + }; } interface DeleteAppConnectionEvent { @@ -3493,7 +3512,7 @@ export type Event = | MoveSecretsEvent | DeleteSecretEvent | DeleteSecretBatchEvent - | GetWorkspaceKeyEvent + | GetProjectKeyEvent | AuthorizeIntegrationEvent | UpdateIntegrationAuthEvent | UnauthorizeIntegrationEvent @@ -3583,9 +3602,9 @@ export type Event = | GetEnvironmentEvent | UpdateEnvironmentEvent | DeleteEnvironmentEvent - | AddWorkspaceMemberEvent - | AddBatchWorkspaceMemberEvent - | RemoveWorkspaceMemberEvent + | AddProjectMemberEvent + | AddBatchProjectMemberEvent + | RemoveProjectMemberEvent | CreateFolderEvent | UpdateFolderEvent | DeleteFolderEvent @@ -3714,6 +3733,8 @@ export type Event = | CreateAppConnectionEvent | UpdateAppConnectionEvent | DeleteAppConnectionEvent + | GetAppConnectionUsageEvent + | MigrateAppConnectionEvent | GetSshHostGroupEvent | CreateSshHostGroupEvent | UpdateSshHostGroupEvent diff --git a/backend/src/ee/services/permission/default-roles.ts b/backend/src/ee/services/permission/default-roles.ts index 9329c3c7f..953d0195e 100644 --- a/backend/src/ee/services/permission/default-roles.ts +++ b/backend/src/ee/services/permission/default-roles.ts @@ -2,6 +2,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability" import { ProjectPermissionActions, + ProjectPermissionAppConnectionActions, ProjectPermissionAuditLogsActions, ProjectPermissionCertificateActions, ProjectPermissionCmekActions, @@ -264,6 +265,17 @@ const buildAdminPermissionRules = () => { ProjectPermissionSub.SecretEvents ); + can( + [ + ProjectPermissionAppConnectionActions.Create, + ProjectPermissionAppConnectionActions.Edit, + ProjectPermissionAppConnectionActions.Delete, + ProjectPermissionAppConnectionActions.Read, + ProjectPermissionAppConnectionActions.Connect + ], + ProjectPermissionSub.AppConnections + ); + return rules; }; @@ -477,6 +489,8 @@ const buildMemberPermissionRules = () => { ProjectPermissionSub.SecretEvents ); + can(ProjectPermissionAppConnectionActions.Connect, ProjectPermissionSub.AppConnections); + return rules; }; diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index d4155d02c..89a518032 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -87,6 +87,7 @@ export enum OrgPermissionBillingActions { export enum OrgPermissionSubjects { Workspace = "workspace", + Project = "project", Role = "role", Member = "member", Settings = "settings", @@ -117,6 +118,7 @@ export type AppConnectionSubjectFields = { export type OrgPermissionSet = | [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace] + | [OrgPermissionActions.Create, OrgPermissionSubjects.Project] | [OrgPermissionActions, OrgPermissionSubjects.Role] | [OrgPermissionActions, OrgPermissionSubjects.Member] | [OrgPermissionActions, OrgPermissionSubjects.Settings] @@ -166,6 +168,10 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [ subject: z.literal(OrgPermissionSubjects.Workspace).describe("The entity this permission pertains to."), action: CASL_ACTION_SCHEMA_ENUM([OrgPermissionActions.Create]).describe("Describe what action an entity can take.") }), + z.object({ + subject: z.literal(OrgPermissionSubjects.Project).describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_ENUM([OrgPermissionActions.Create]).describe("Describe what action an entity can take.") + }), z.object({ subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."), action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.") @@ -280,6 +286,7 @@ const buildAdminPermission = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); // ws permissions can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); + can(OrgPermissionActions.Create, OrgPermissionSubjects.Project); // role permission can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Create, OrgPermissionSubjects.Role); @@ -413,6 +420,7 @@ const buildMemberPermission = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); + can(OrgPermissionActions.Create, OrgPermissionSubjects.Project); can(OrgPermissionActions.Read, OrgPermissionSubjects.Member); can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 20b4344d3..099461f7f 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -147,6 +147,14 @@ export enum ProjectPermissionSecretScanningDataSourceActions { ReadResources = "read-data-source-resources" } +export enum ProjectPermissionAppConnectionActions { + Read = "read-app-connections", + Create = "create-app-connections", + Edit = "edit-app-connections", + Delete = "delete-app-connections", + Connect = "connect-app-connections" +} + export enum ProjectPermissionSecretScanningFindingActions { Read = "read-findings", Update = "update-findings" @@ -208,7 +216,8 @@ export enum ProjectPermissionSub { SecretScanningDataSources = "secret-scanning-data-sources", SecretScanningFindings = "secret-scanning-findings", SecretScanningConfigs = "secret-scanning-configs", - SecretEvents = "secret-events" + SecretEvents = "secret-events", + AppConnections = "app-connections" } export type SecretSubjectFields = { @@ -272,6 +281,10 @@ export type PkiSubscriberSubjectFields = { // (dangtony98): consider adding [commonName] as a subject field in the future }; +export type AppConnectionSubjectFields = { + connectionId: string; +}; + export type ProjectPermissionSet = | [ ProjectPermissionSecretActions, @@ -365,6 +378,13 @@ export type ProjectPermissionSet = | [ ProjectPermissionSecretEventActions, ProjectPermissionSub.SecretEvents | (ForcedSubject & SecretEventSubjectFields) + ] + | [ + ProjectPermissionAppConnectionActions, + ( + | ProjectPermissionSub.AppConnections + | (ForcedSubject & AppConnectionSubjectFields) + ) ]; const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'"; @@ -580,6 +600,21 @@ const PkiTemplateConditionSchema = z }) .partial(); +const AppConnectionConditionSchema = z + .object({ + connectionId: z.union([ + z.string(), + z + .object({ + [PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ], + [PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ], + [PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN] + }) + .partial() + ]) + }) + .partial(); + const GeneralPermissionSchema = [ z.object({ subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."), @@ -760,6 +795,16 @@ const GeneralPermissionSchema = [ action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretScanningConfigActions).describe( "Describe what action an entity can take." ) + }), + z.object({ + subject: z.literal(ProjectPermissionSub.AppConnections).describe("The entity this permission pertains to."), + inverted: z.boolean().optional().describe("Whether rule allows or forbids."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionAppConnectionActions).describe( + "Describe what action an entity can take." + ), + conditions: AppConnectionConditionSchema.describe( + "When specified, only matching conditions will be allowed to access given resource." + ).optional() }) ]; diff --git a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts index 07cf97a7e..9b1fd14a0 100644 --- a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts @@ -175,7 +175,8 @@ export const ldapPasswordRotationFactory: TRotationFactory< const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: updatedCredentials, orgId, - kmsService + kmsService, + projectId: connection.projectId }); await appConnectionDAL.updateById(connection.id, { encryptedCredentials }); diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts index 787c07bae..cf236b56f 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-dal.ts @@ -52,6 +52,7 @@ const baseSecretRotationV2Query = ({ db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"), db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"), db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"), + db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"), db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"), db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"), db @@ -106,6 +107,7 @@ const expandSecretRotation = ; + appConnectionService: Pick; permissionService: Pick; projectBotService: Pick; kmsService: Pick; @@ -459,7 +459,11 @@ export const secretRotationV2ServiceFactory = ({ const typeApp = SECRET_ROTATION_CONNECTION_MAP[payload.type]; // validates permission to connect and app is valid for rotation type - const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor); + const connection = await appConnectionService.validateAppConnectionUsageById( + typeApp, + { connectionId: payload.connectionId, projectId }, + actor + ); const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type]( { diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index 557e71e6c..eefe6b63a 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -431,7 +431,7 @@ export const secretRotationQueueFactory = ({ numberOfSecrets: numberOfSecretsRotated, environment: secretRotation.environment.slug, secretPath: secretRotation.secretPath, - workspaceId: secretRotation.projectId + projectId: secretRotation.projectId } }); diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts index c6ca50c5e..405e60159 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-dal.ts @@ -50,6 +50,7 @@ const baseSecretScanningDataSourceQuery = ({ db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"), db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"), db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"), + db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"), db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"), db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"), db @@ -84,6 +85,7 @@ const expandSecretScanningDataSource = < connectionVersion, connectionIsPlatformManagedCredentials, connectionGatewayId, + connectionProjectId, ...el } = dataSource; @@ -103,7 +105,8 @@ const expandSecretScanningDataSource = < updatedAt: connectionUpdatedAt, version: connectionVersion, isPlatformManagedCredentials: connectionIsPlatformManagedCredentials, - gatewayId: connectionGatewayId + gatewayId: connectionGatewayId, + projectId: connectionProjectId } : undefined }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts index 6bef41e10..c48139e17 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts @@ -60,7 +60,7 @@ import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue export type TSecretScanningV2ServiceFactoryDep = { secretScanningV2DAL: TSecretScanningV2DALFactory; - appConnectionService: Pick; + appConnectionService: Pick; appConnectionDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -252,9 +252,9 @@ export const secretScanningV2ServiceFactory = ({ let connection: TAppConnection | null = null; if (payload.connectionId) { // validates permission to connect and app is valid for data source - connection = await appConnectionService.connectAppConnectionById( + connection = await appConnectionService.validateAppConnectionUsageById( SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[payload.type], - payload.connectionId, + { connectionId: payload.connectionId, projectId: payload.projectId }, actor ); } @@ -373,9 +373,9 @@ export const secretScanningV2ServiceFactory = ({ let connection: TAppConnection | null = null; if (dataSource.connectionId) { // validates permission to connect and app is valid for data source - connection = await appConnectionService.connectAppConnectionById( + connection = await appConnectionService.validateAppConnectionUsageById( SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSource.type], - dataSource.connectionId, + { connectionId: dataSource.connectionId, projectId: dataSource.projectId }, actor ); } diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 64ed15073..53a2ca993 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -724,13 +724,13 @@ export const PROJECTS = { template: "The name of the project template, if specified, to apply to this project." }, DELETE: { - workspaceId: "The ID of the project to delete." + projectId: "The ID of the project to delete." }, GET: { - workspaceId: "The ID of the project." + projectId: "The ID of the project." }, UPDATE: { - workspaceId: "The ID of the project to update.", + projectId: "The ID of the project to update.", name: "The new name of the project.", projectDescription: "An optional description label for the project.", autoCapitalization: "Disable or enable auto-capitalization for the project.", @@ -742,10 +742,10 @@ export const PROJECTS = { secretDetectionIgnoreValues: "The list of secret values to ignore for secret detection." }, GET_KEY: { - workspaceId: "The ID of the project to get the key from." + projectId: "The ID of the project to get the key from." }, GET_SNAPSHOTS: { - workspaceId: "The ID of the project to get snapshots from.", + projectId: "The ID of the project to get snapshots from.", environment: "The environment to get snapshots from.", path: "The secret path to get snapshots from.", offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.", @@ -772,10 +772,10 @@ export const PROJECTS = { projectId: "The ID of the project to list groups for." }, LIST_INTEGRATION: { - workspaceId: "The ID of the project to list integrations for." + projectId: "The ID of the project to list integrations for." }, LIST_INTEGRATION_AUTHORIZATION: { - workspaceId: "The ID of the project to list integration auths for." + projectId: "The ID of the project to list integration auths for." }, LIST_SSH_CAS: { projectId: "The ID of the project to list SSH CAs for." @@ -828,15 +828,15 @@ export const PROJECT_USERS = { usernames: "A list of usernames to remove from the project." }, GET_USER_MEMBERSHIPS: { - workspaceId: "The ID of the project to get memberships from." + projectId: "The ID of the project to get memberships from." }, GET_USER_MEMBERSHIP: { - workspaceId: "The ID of the project to get memberships from.", + projectId: "The ID of the project to get memberships from.", membershipId: "The ID of the user's project membership.", username: "The username to get project membership of. Email is the default username." }, UPDATE_USER_MEMBERSHIP: { - workspaceId: "The ID of the project to update the membership for.", + projectId: "The ID of the project to update the membership for.", membershipId: "The ID of the membership to update.", roles: "A list of roles to update the membership to." } @@ -890,31 +890,31 @@ export const PROJECT_IDENTITIES = { export const ENVIRONMENTS = { CREATE: { - workspaceId: "The ID of the project to create the environment in.", + projectId: "The ID of the project to create the environment in.", name: "The name of the environment to create.", slug: "The slug of the environment to create.", position: "The position of the environment. The lowest number will be displayed as the first environment." }, UPDATE: { - workspaceId: "The ID of the project to update the environment in.", + projectId: "The ID of the project to update the environment in.", id: "The ID of the environment to update.", name: "The new name of the environment.", slug: "The new slug of the environment.", position: "The new position of the environment. The lowest number will be displayed as the first environment." }, DELETE: { - workspaceId: "The ID of the project to delete the environment from.", + projectId: "The ID of the project to delete the environment from.", id: "The ID of the environment to delete." }, GET: { - workspaceId: "The ID of the project the environment belongs to.", + projectId: "The ID of the project the environment belongs to.", id: "The ID of the environment to fetch." } } as const; export const FOLDERS = { LIST: { - workspaceId: "The ID of the project to list folders from.", + projectId: "The ID of the project to list folders from.", environment: "The slug of the environment to list folders from.", path: "The path to list folders from.", directory: "The directory to list folders from. (Deprecated in favor of path)", @@ -926,7 +926,7 @@ export const FOLDERS = { folderId: "The ID of the folder to get details." }, CREATE: { - workspaceId: "The ID of the project to create the folder in.", + projectId: "The ID of the project to create the folder in.", environment: "The slug of the environment to create the folder in.", name: "The name of the folder to create.", path: "The path of the folder to create.", @@ -940,12 +940,12 @@ export const FOLDERS = { path: "The path of the folder to update.", directory: "The new directory of the folder to update. (Deprecated in favor of path)", projectSlug: "The slug of the project where the folder is located.", - workspaceId: "The ID of the project where the folder is located.", + projectId: "The ID of the project where the folder is located.", description: "An optional description label for the folder." }, DELETE: { folderIdOrName: "The ID or name of the folder to delete.", - workspaceId: "The ID of the project to delete the folder from.", + projectId: "The ID of the project to delete the folder from.", environment: "The slug of the environment where the folder is located.", directory: "The directory of the folder to delete. (Deprecated in favor of path)", path: "The path of the folder to delete." @@ -977,7 +977,7 @@ export const RAW_SECRETS = { expand: "Whether or not to expand secret references.", recursive: "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.", - workspaceId: "The ID of the project to list secrets from.", + projectId: "The ID of the project to list secrets from.", workspaceSlug: "The slug of the project to list secrets from. This parameter is only applicable by machine identities.", environment: "The slug of the environment to list secrets from.", @@ -997,7 +997,7 @@ export const RAW_SECRETS = { secretValue: "The value of the secret to create.", skipMultilineEncoding: "Skip multiline encoding for the secret value.", type: "The type of the secret to create.", - workspaceId: "The ID of the project to create the secret in.", + projectId: "The ID of the project to create the secret in.", tagIds: "The ID of the tags to be attached to the created secret.", secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.", secretReminderNote: "Note to be attached in notification email." @@ -1005,7 +1005,7 @@ export const RAW_SECRETS = { GET: { expand: "Whether or not to expand secret references.", secretName: "The name of the secret to get.", - workspaceId: "The ID of the project to get the secret from.", + projectId: "The ID of the project to get the secret from.", workspaceSlug: "The slug of the project to get the secret from.", environment: "The slug of the environment to get the secret from.", secretPath: "The path of the secret to get.", @@ -1024,7 +1024,7 @@ export const RAW_SECRETS = { skipMultilineEncoding: "Skip multiline encoding for the secret value.", type: "The type of the secret to update.", projectSlug: "The slug of the project to update the secret in.", - workspaceId: "The ID of the project to update the secret in.", + projectId: "The ID of the project to update the secret in.", tagIds: "The ID of the tags to be attached to the updated secret.", secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.", secretReminderNote: "Note to be attached in notification email.", @@ -1038,11 +1038,11 @@ export const RAW_SECRETS = { secretPath: "The path of the secret.", type: "The type of the secret to delete.", projectSlug: "The slug of the project to delete the secret in.", - workspaceId: "The ID of the project where the secret is located." + projectId: "The ID of the project where the secret is located." }, GET_REFERENCE_TREE: { secretName: "The name of the secret to get the reference tree for.", - workspaceId: "The ID of the project where the secret is located.", + projectId: "The ID of the project where the secret is located.", environment: "The slug of the environment where the the secret is located.", secretPath: "The folder path where the secret is located." }, @@ -1056,7 +1056,7 @@ export const RAW_SECRETS = { export const SECRET_IMPORTS = { LIST: { - workspaceId: "The ID of the project to list secret imports from.", + projectId: "The ID of the project to list secret imports from.", environment: "The slug of the environment to list secret imports from.", path: "The path to list secret imports from." }, @@ -1066,7 +1066,7 @@ export const SECRET_IMPORTS = { CREATE: { environment: "The slug of the environment to import into.", path: "The path to import into.", - workspaceId: "The ID of the project you are working in.", + projectId: "The ID of the project you are working in.", isReplication: "When true, secrets from the source will be automatically sent to the destination. If approval policies exist at the destination, the secrets will be sent as approval requests instead of being applied immediately.", import: { @@ -1083,10 +1083,10 @@ export const SECRET_IMPORTS = { position: "The new position of the secret import. The lowest number will be displayed as the first import." }, path: "The path of the secret import to update.", - workspaceId: "The ID of the project where the secret import is located." + projectId: "The ID of the project where the secret import is located." }, DELETE: { - workspaceId: "The ID of the project to delete the secret import from.", + projectId: "The ID of the project to delete the secret import from.", secretImportId: "The ID of the secret import to delete.", environment: "The slug of the environment where the secret import is located.", path: "The path of the secret import to delete." @@ -2198,11 +2198,15 @@ export const CertificateAuthorities = { }; export const AppConnections = { + LIST: (app?: AppConnection) => ({ + projectId: `The ID of the project to list ${app ? APP_CONNECTION_NAME_MAP[app] : "App"} Connections from.` + }), GET_BY_ID: (app: AppConnection) => ({ connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.` }), GET_BY_NAME: (app: AppConnection) => ({ - connectionName: `The name of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.` + connectionName: `The name of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`, + projectId: `The project ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection is associated with. Leave unspecified to get organization-level connections.` }), CREATE: (app: AppConnection) => { const appName = APP_CONNECTION_NAME_MAP[app]; @@ -2211,7 +2215,8 @@ export const AppConnections = { description: `An optional description for the ${appName} Connection.`, credentials: `The credentials used to connect with ${appName}.`, method: `The method used to authenticate with ${appName}.`, - isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.` + isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.`, + projectId: `The ID of the project to create the ${appName} Connection in.` }; }, UPDATE: (app: AppConnection) => { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 69dea2f8d..bb7d202da 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -328,6 +328,7 @@ import { registerV1Routes } from "./v1"; import { initializeOauthConfigSync } from "./v1/sso-router"; import { registerV2Routes } from "./v2"; import { registerV3Routes } from "./v3"; +import { registerV4Routes } from "./v4"; const histogram = monitorEventLoopDelay({ resolution: 20 }); histogram.enable(); @@ -1838,7 +1839,8 @@ export const registerRoutes = async ( gatewayService, gatewayV2Service, gatewayDAL, - gatewayV2DAL + gatewayV2DAL, + projectDAL }); const secretSyncService = secretSyncServiceFactory({ @@ -2295,6 +2297,7 @@ export const registerRoutes = async ( { prefix: "/api/v2" } ); await server.register(registerV3Routes, { prefix: "/api/v3" }); + await server.register(registerV4Routes, { prefix: "/api/v4" }); server.addHook("onClose", async () => { cronJobs.forEach((job) => job.stop()); diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-endpoints.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-endpoints.ts index 50111b109..720726c34 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-endpoints.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-endpoints.ts @@ -26,6 +26,7 @@ export const registerAppConnectionEndpoints = ; updateSchema: z.ZodType<{ name?: string; @@ -47,18 +48,27 @@ export const registerAppConnectionEndpoints = { - const appConnections = (await server.services.appConnection.listAppConnectionsByOrg(req.permission, app)) as T[]; + const { projectId } = req.query; + const appConnections = (await server.services.appConnection.listAppConnections( + req.permission, + app, + projectId + )) as T[]; await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, + projectId, event: { type: EventType.GET_APP_CONNECTIONS, metadata: { @@ -82,14 +92,19 @@ export const registerAppConnectionEndpoints = { + const { projectId } = req.query; const appConnections = await server.services.appConnection.listAvailableAppConnectionsForUser( app, - req.permission + req.permission, + projectId ); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, + projectId, event: { type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS, metadata: { @@ -149,6 +167,7 @@ export const registerAppConnectionEndpoints = { const { connectionName } = req.params; + const { projectId } = req.query; const appConnection = (await server.services.appConnection.findAppConnectionByName( app, - connectionName, + { + connectionName, + projectId + }, req.permission )) as T; await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, + projectId: appConnection.projectId ?? undefined, event: { type: EventType.GET_APP_CONNECTION, metadata: { @@ -216,9 +243,7 @@ export const registerAppConnectionEndpoints = { - const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId } = req.body; + const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId, projectId } = req.body; const appConnection = (await server.services.appConnection.createAppConnection( - { name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId }, + { name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId, projectId }, req.permission )) as T; await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, + projectId, event: { type: EventType.CREATE_APP_CONNECTION, metadata: { @@ -283,6 +309,7 @@ export const registerAppConnectionEndpoints = { + // const { connectionId } = req.params; + // + // const projects = await server.services.appConnection.findAppConnectionUsageById( + // app, + // connectionId, + // req.permission + // ); + // + // await server.services.auditLog.createAuditLog({ + // ...req.auditLogInfo, + // orgId: req.permission.orgId, + // event: { + // type: EventType.GET_APP_CONNECTION_USAGE, + // metadata: { + // connectionId + // } + // } + // }); + // + // return { projects }; + // } + // }); }; diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index c2033f4b4..37558b817 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -1,12 +1,13 @@ import { z } from "zod"; +import { ProjectType } from "@app/db/schemas"; import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci"; import { OracleDBConnectionListItemSchema, SanitizedOracleDBConnectionSchema } from "@app/ee/services/app-connections/oracledb"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; -import { ApiDocsTags } from "@app/lib/api-docs"; +import { ApiDocsTags, AppConnections } from "@app/lib/api-docs"; import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { @@ -210,6 +211,9 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) => hide: false, tags: [ApiDocsTags.AppConnections], description: "List the available App Connection Options.", + querystring: z.object({ + projectType: z.nativeEnum(ProjectType).optional() + }), response: { 200: z.object({ appConnectionOptions: AppConnectionOptionsSchema.array() @@ -217,8 +221,8 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) => } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: () => { - const appConnectionOptions = server.services.appConnection.listAppConnectionOptions(); + handler: (req) => { + const appConnectionOptions = server.services.appConnection.listAppConnectionOptions(req.query.projectType); return { appConnectionOptions }; } }); @@ -232,18 +236,27 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) => schema: { hide: false, tags: [ApiDocsTags.AppConnections], - description: "List all the App Connections for the current organization.", + description: "List all the App Connections for the current organization or project.", + querystring: z.object({ + projectId: z.string().optional().describe(AppConnections.LIST().projectId) + }), response: { 200: z.object({ appConnections: SanitizedAppConnectionSchema.array() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const appConnections = await server.services.appConnection.listAppConnectionsByOrg(req.permission); + const { projectId } = req.query; + const appConnections = await server.services.appConnection.listAppConnections( + req.permission, + undefined, + projectId + ); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, + projectId, event: { type: EventType.GET_APP_CONNECTIONS, metadata: { diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index a54bd5ccf..c394ee8e9 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -207,7 +207,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { const environments = req.query.environments.split(","); if (!projectId || environments.length === 0) - throw new BadRequestError({ message: "Missing workspace id or environment(s)" }); + throw new BadRequestError({ message: "Missing project id or environment(s)" }); const { shouldUseSecretV2Bridge } = await server.services.projectBot.getBotKey(projectId); @@ -474,7 +474,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCountFromEnv, - workspaceId: projectId, + projectId, environment, secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -696,7 +696,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { includeSecretRotations } = req.query; - if (!projectId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" }); + if (!projectId || !environment) throw new BadRequestError({ message: "Missing project id or environment" }); const { shouldUseSecretV2Bridge } = await server.services.projectBot.getBotKey(projectId); @@ -1001,7 +1001,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCount, - workspaceId: projectId, + projectId, environment, secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1168,7 +1168,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCountForEnv, - workspaceId: projectId, + projectId, environment, secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1361,7 +1361,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: projectId, + projectId, environment, secretPath, channel: getUserAgentType(req.headers["user-agent"]), diff --git a/backend/src/server/routes/v1/deprecated-project-env-router.ts b/backend/src/server/routes/v1/deprecated-project-env-router.ts new file mode 100644 index 000000000..1a187e2b1 --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-project-env-router.ts @@ -0,0 +1,298 @@ +import { z } from "zod"; + +import { ProjectEnvironmentsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, ENVIRONMENTS } from "@app/lib/api-docs"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerDeprecatedProjectEnvRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:workspaceId/environments/:envId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Environments], + description: "Get Environment", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + // NOTE(daniel): workspaceId isn't used, but we need to keep it for backwards compatibility. The endpoint defined below, uses no project ID, and is takes a pure environment ID. + workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.projectId), + envId: z.string().trim().describe(ENVIRONMENTS.GET.id) + }), + response: { + 200: z.object({ + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const environment = await server.services.projectEnv.getEnvironmentById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + id: req.params.envId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.GET_ENVIRONMENT, + metadata: { + id: environment.id + } + } + }); + + return { environment }; + } + }); + + server.route({ + method: "GET", + url: "/environments/:envId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Environments], + description: "Get Environment by ID", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + envId: z.string().trim().describe(ENVIRONMENTS.GET.id) + }), + response: { + 200: z.object({ + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const environment = await server.services.projectEnv.getEnvironmentById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + id: req.params.envId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.GET_ENVIRONMENT, + metadata: { + id: environment.id + } + } + }); + + return { environment }; + } + }); + + server.route({ + method: "POST", + url: "/:workspaceId/environments", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Environments], + description: "Create environment", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.projectId) + }), + body: z.object({ + name: z.string().trim().describe(ENVIRONMENTS.CREATE.name), + position: z.number().min(1).optional().describe(ENVIRONMENTS.CREATE.position), + slug: slugSchema({ max: 64 }).describe(ENVIRONMENTS.CREATE.slug) + }), + response: { + 200: z.object({ + message: z.string(), + workspace: z.string(), + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const environment = await server.services.projectEnv.createEnvironment({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + projectId: req.params.workspaceId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.CREATE_ENVIRONMENT, + metadata: { + name: environment.name, + slug: environment.slug + } + } + }); + return { + message: "Successfully created new environment", + workspace: req.params.workspaceId, + environment + }; + } + }); + + server.route({ + method: "PATCH", + url: "/:workspaceId/environments/:id", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Environments], + description: "Update environment", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.projectId), + id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id) + }), + body: z.object({ + slug: slugSchema({ max: 64 }).optional().describe(ENVIRONMENTS.UPDATE.slug), + name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name), + position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position) + }), + response: { + 200: z.object({ + message: z.string(), + workspace: z.string(), + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { environment, old } = await server.services.projectEnv.updateEnvironment({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + id: req.params.id, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.UPDATE_ENVIRONMENT, + metadata: { + oldName: old.name, + oldSlug: old.slug, + oldPos: old.position, + newName: environment.name, + newSlug: environment.slug, + newPos: environment.position + } + } + }); + + return { + message: "Successfully updated environment", + workspace: req.params.workspaceId, + environment + }; + } + }); + + server.route({ + method: "DELETE", + url: "/:workspaceId/environments/:id", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Environments], + description: "Delete environment", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.projectId), + id: z.string().trim().describe(ENVIRONMENTS.DELETE.id) + }), + response: { + 200: z.object({ + message: z.string(), + workspace: z.string(), + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const environment = await server.services.projectEnv.deleteEnvironment({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + id: req.params.id + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.DELETE_ENVIRONMENT, + metadata: { + slug: environment.slug, + name: environment.name + } + } + }); + + return { + message: "Successfully deleted environment", + workspace: req.params.workspaceId, + environment + }; + } + }); +}; diff --git a/backend/src/server/routes/v1/deprecated-project-membership-router.ts b/backend/src/server/routes/v1/deprecated-project-membership-router.ts new file mode 100644 index 000000000..ab225929f --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-project-membership-router.ts @@ -0,0 +1,378 @@ +import { z } from "zod"; + +import { + OrgMembershipsSchema, + ProjectMembershipsSchema, + ProjectUserMembershipRolesSchema, + UserEncryptionKeysSchema, + UsersSchema +} from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, PROJECT_USERS } 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 registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:workspaceId/memberships", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Return project user memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.projectId) + }), + response: { + 200: z.object({ + memberships: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true, + username: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + 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() + }) + ) + }) + .omit({ updatedAt: true }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const memberships = await server.services.projectMembership.getProjectMemberships({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId + }); + return { memberships }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/memberships/:membershipId", + config: { + rateLimit: readLimit + }, + schema: { + description: "Return project user membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId), + membershipId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.membershipId) + }), + response: { + 200: z.object({ + membership: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true, + username: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + 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() + }) + ) + }).omit({ updatedAt: true }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const membership = await server.services.projectMembership.getProjectMembershipById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + id: req.params.membershipId + }); + return { membership }; + } + }); + + server.route({ + method: "POST", + url: "/:workspaceId/memberships/details", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Return project user memberships", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId) + }), + body: z.object({ + username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username) + }), + response: { + 200: z.object({ + membership: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + 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() + }) + ) + }).omit({ createdAt: true, updatedAt: true }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const membership = await server.services.projectMembership.getProjectMembershipByUsername({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + username: req.body.username + }); + return { membership }; + } + }); + + server.route({ + method: "POST", + url: "/:workspaceId/memberships", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + body: z.object({ + members: z + .object({ + orgMembershipId: z.string().trim(), + workspaceEncryptedKey: z.string().trim(), + workspaceEncryptedNonce: z.string().trim() + }) + .array() + .min(1) + }), + response: { + 200: z.object({ + success: z.boolean(), + data: OrgMembershipsSchema.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, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + members: req.body.members + }); + + await server.services.auditLog.createAuditLog({ + projectId: req.params.workspaceId, + ...req.auditLogInfo, + event: { + type: EventType.ADD_BATCH_PROJECT_MEMBER, + metadata: data.map(({ userId }) => ({ + userId: userId || "", + email: "" + })) + } + }); + + return { data, success: true }; + } + }); + + server.route({ + method: "PATCH", + url: "/:workspaceId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Update project user membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.projectId), + membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId) + }), + 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) + .refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required") + .describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.roles) + }), + response: { + 200: z.object({ + roles: ProjectUserMembershipRolesSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const roles = await server.services.projectMembership.updateProjectMembership({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + membershipId: req.params.membershipId, + roles: req.body.roles + }); + + // await server.services.auditLog.createAuditLog({ + // ...req.auditLogInfo, + // projectId: req.params.workspaceId, + // 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: "/:workspaceId/memberships/:membershipId", + config: { + rateLimit: writeLimit + }, + schema: { + description: "Delete project user membership", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim(), + membershipId: z.string().trim() + }), + response: { + 200: z.object({ + membership: ProjectMembershipsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const membership = await server.services.projectMembership.deleteProjectMembership({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + membershipId: req.params.membershipId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.params.workspaceId, + event: { + type: EventType.REMOVE_PROJECT_MEMBER, + metadata: { + userId: membership.userId, + email: "" + } + } + }); + return { membership }; + } + }); +}; diff --git a/backend/src/server/routes/v1/deprecated-project-router.ts b/backend/src/server/routes/v1/deprecated-project-router.ts new file mode 100644 index 000000000..687d95b9b --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-project-router.ts @@ -0,0 +1,728 @@ +import { z } from "zod"; + +import { + IntegrationsSchema, + ProjectRolesSchema, + ProjectSlackConfigsSchema, + ProjectSshConfigsSchema, + ProjectType, + SortDirection +} from "@app/db/schemas"; +import { ProjectMicrosoftTeamsConfigsSchema } from "@app/db/schemas/project-microsoft-teams-configs"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; +import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; +import { re2Validator } from "@app/lib/zod"; +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 { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns"; +import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types"; +import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators"; +import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types"; + +import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas"; + +const projectWithEnv = SanitizedProjectSchema.merge( + z.object({ + _id: z.string(), + environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array() + }) +); + +export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + includeRoles: z + .enum(["true", "false"]) + .default("false") + .transform((value) => value === "true"), + type: z.nativeEnum(ProjectType).optional() + }), + response: { + 200: z.object({ + workspaces: projectWithEnv + .extend({ + roles: ProjectRolesSchema.array().optional() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaces = await server.services.project.getProjects({ + includeRoles: req.query.includeRoles, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + type: req.query.type + }); + return { workspaces }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Get project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.GET.projectId) + }), + response: { + 200: z.object({ + workspace: projectWithEnv.optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspace = await server.services.project.getAProject({ + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + return { workspace }; + } + }); + + server.route({ + method: "DELETE", + url: "/:workspaceId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Delete project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.DELETE.projectId) + }), + response: { + 200: z.object({ + workspace: SanitizedProjectSchema.optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspace = await server.services.project.deleteProject({ + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.DELETE_PROJECT, + metadata: workspace + } + }); + + return { workspace }; + } + }); + + server.route({ + method: "PATCH", + url: "/:workspaceId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "Update project", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.UPDATE.projectId) + }), + body: z.object({ + name: z + .string() + .trim() + .max(64, { message: "Name must be 64 or fewer characters" }) + .optional() + .describe(PROJECTS.UPDATE.name), + description: z + .string() + .trim() + .max(256, { message: "Description must be 256 or fewer characters" }) + .optional() + .describe(PROJECTS.UPDATE.projectDescription), + autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization), + hasDeleteProtection: z.boolean().optional().describe(PROJECTS.UPDATE.hasDeleteProtection), + slug: z + .string() + .trim() + .max(64, { message: "Slug must be 64 characters or fewer" }) + .refine(re2Validator(/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/), { + message: + "Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore." + }) + .optional() + .describe(PROJECTS.UPDATE.slug), + secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing), + showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy), + defaultProduct: z.nativeEnum(ProjectType).optional().describe(PROJECTS.UPDATE.defaultProduct), + secretDetectionIgnoreValues: z + .array(z.string()) + .optional() + .describe(PROJECTS.UPDATE.secretDetectionIgnoreValues) + }), + response: { + 200: z.object({ + workspace: SanitizedProjectSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspace = await server.services.project.updateProject({ + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, + update: { + name: req.body.name, + description: req.body.description, + autoCapitalization: req.body.autoCapitalization, + defaultProduct: req.body.defaultProduct, + hasDeleteProtection: req.body.hasDeleteProtection, + slug: req.body.slug, + secretSharing: req.body.secretSharing, + showSnapshotsLegacy: req.body.showSnapshotsLegacy, + secretDetectionIgnoreValues: req.body.secretDetectionIgnoreValues + }, + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + + return { + workspace + }; + } + }); + + server.route({ + method: "PUT", + url: "/:workspaceSlug/audit-logs-retention", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + workspaceSlug: z.string().trim() + }), + body: z.object({ + auditLogsRetentionDays: z.number().min(0) + }), + response: { + 200: z.object({ + message: z.string(), + workspace: SanitizedProjectSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspace = await server.services.project.updateAuditLogsRetention({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.workspaceSlug, + orgId: req.permission.orgId + }, + auditLogsRetentionDays: req.body.auditLogsRetentionDays + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: workspace.id, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + + return { + message: "Successfully updated project's audit logs retention period", + workspace + }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/integrations", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Integrations], + description: "List integrations for a project.", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.projectId) + }), + response: { + 200: z.object({ + integrations: IntegrationsSchema.merge( + z.object({ + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }) + }) + ).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const integrations = await server.services.integration.listIntegrationByProject({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId + }); + return { integrations }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/authorizations", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Integrations], + description: "List integration auth objects for a workspace.", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.projectId) + }), + response: { + 200: z.object({ + authorizations: integrationAuthPubSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId + }); + return { authorizations }; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/ssh-config", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: ProjectSshConfigsSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + projectId: true, + defaultUserSshCaId: true, + defaultHostSshCaId: true + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const sshConfig = await server.services.project.getProjectSshConfig({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: sshConfig.projectId, + event: { + type: EventType.GET_PROJECT_SSH_CONFIG, + metadata: { + id: sshConfig.id, + projectId: sshConfig.projectId + } + } + }); + + return sshConfig; + } + }); + + server.route({ + method: "PATCH", + url: "/:workspaceId/ssh-config", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + body: z.object({ + defaultUserSshCaId: z.string().optional(), + defaultHostSshCaId: z.string().optional() + }), + response: { + 200: ProjectSshConfigsSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + projectId: true, + defaultUserSshCaId: true, + defaultHostSshCaId: true + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const sshConfig = await server.services.project.updateProjectSshConfig({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: sshConfig.projectId, + event: { + type: EventType.UPDATE_PROJECT_SSH_CONFIG, + metadata: { + id: sshConfig.id, + projectId: sshConfig.projectId, + defaultUserSshCaId: sshConfig.defaultUserSshCaId, + defaultHostSshCaId: sshConfig.defaultHostSshCaId + } + } + }); + + return sshConfig; + } + }); + + server.route({ + method: "GET", + url: "/:workspaceId/workflow-integration-config/:integration", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim(), + integration: z.nativeEnum(WorkflowIntegration) + }), + response: { + 200: z.discriminatedUnion("integration", [ + ProjectSlackConfigsSchema.pick({ + id: true, + isAccessRequestNotificationEnabled: true, + accessRequestChannels: true, + isSecretRequestNotificationEnabled: true, + secretRequestChannels: true + }).merge( + z.object({ + integration: z.literal(WorkflowIntegration.SLACK), + integrationId: z.string() + }) + ), + ProjectMicrosoftTeamsConfigsSchema.pick({ + id: true, + isAccessRequestNotificationEnabled: true, + accessRequestChannels: true, + isSecretRequestNotificationEnabled: true, + secretRequestChannels: true + }).merge( + z.object({ + integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS), + integrationId: z.string() + }) + ) + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const config = await server.services.project.getProjectWorkflowIntegrationConfig({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + integration: req.params.integration + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.params.workspaceId, + event: { + type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG, + metadata: { + id: config.id, + integration: config.integration + } + } + }); + + return config; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/workflow-integration/:integration/:integrationId", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim(), + integration: z.nativeEnum(WorkflowIntegration), + integrationId: z.string() + }), + response: { + 200: z.object({ + integrationConfig: z.object({ + id: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const deletedIntegration = await server.services.project.deleteProjectWorkflowIntegration({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + integration: req.params.integration, + integrationId: req.params.integrationId + }); + + return { + integrationConfig: deletedIntegration + }; + } + }); + + server.route({ + method: "PUT", + url: "/:workspaceId/workflow-integration", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + + body: z.discriminatedUnion("integration", [ + z.object({ + integration: z.literal(WorkflowIntegration.SLACK), + integrationId: z.string(), + accessRequestChannels: validateSlackChannelsField, + secretRequestChannels: validateSlackChannelsField, + isAccessRequestNotificationEnabled: z.boolean(), + isSecretRequestNotificationEnabled: z.boolean() + }), + z.object({ + integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS), + integrationId: z.string(), + accessRequestChannels: validateMicrosoftTeamsChannelsSchema, + secretRequestChannels: validateMicrosoftTeamsChannelsSchema, + isAccessRequestNotificationEnabled: z.boolean(), + isSecretRequestNotificationEnabled: z.boolean() + }) + ]), + response: { + 200: z.discriminatedUnion("integration", [ + ProjectSlackConfigsSchema.pick({ + id: true, + isAccessRequestNotificationEnabled: true, + accessRequestChannels: true, + isSecretRequestNotificationEnabled: true, + secretRequestChannels: true + }).merge( + z.object({ + integration: z.literal(WorkflowIntegration.SLACK), + integrationId: z.string() + }) + ), + ProjectMicrosoftTeamsConfigsSchema.pick({ + id: true, + isAccessRequestNotificationEnabled: true, + isSecretRequestNotificationEnabled: true + }).merge( + z.object({ + integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS), + integrationId: z.string(), + accessRequestChannels: validateMicrosoftTeamsChannelsSchema, + secretRequestChannels: validateMicrosoftTeamsChannelsSchema + }) + ) + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workflowIntegrationConfig = await server.services.project.updateProjectWorkflowIntegration({ + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + projectId: req.params.workspaceId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG, + metadata: { + id: workflowIntegrationConfig.id, + integrationId: workflowIntegrationConfig.integrationId, + integration: workflowIntegrationConfig.integration, + isAccessRequestNotificationEnabled: workflowIntegrationConfig.isAccessRequestNotificationEnabled, + accessRequestChannels: workflowIntegrationConfig.accessRequestChannels, + isSecretRequestNotificationEnabled: workflowIntegrationConfig.isSecretRequestNotificationEnabled, + secretRequestChannels: workflowIntegrationConfig.secretRequestChannels + } + } + }); + + return workflowIntegrationConfig; + } + }); + + server.route({ + method: "POST", + url: "/search", + config: { + rateLimit: readLimit + }, + schema: { + body: z.object({ + limit: z.number().default(100), + offset: z.number().default(0), + type: z.nativeEnum(ProjectType).optional(), + orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME), + orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC), + name: z + .string() + .trim() + .refine((val) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(val), { + message: "Invalid pattern: only alphanumeric characters, - are allowed." + }) + .optional() + }), + response: { + 200: z.object({ + projects: SanitizedProjectSchema.extend({ isMember: z.boolean() }).array(), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { docs: projects, totalCount } = await server.services.project.searchProjects({ + permission: req.permission, + ...req.body + }); + + return { projects, totalCount }; + } + }); +}; diff --git a/backend/src/server/routes/v1/deprecated-secret-folder-router.ts b/backend/src/server/routes/v1/deprecated-secret-folder-router.ts new file mode 100644 index 000000000..ecb955025 --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-secret-folder-router.ts @@ -0,0 +1,444 @@ +import { z } from "zod"; + +import { SecretFoldersSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, FOLDERS } from "@app/lib/api-docs"; +import { prefixWithSlash, removeTrailingSlash } from "@app/lib/fn"; +import { isValidFolderName } from "@app/lib/validator"; +import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { booleanSchema } from "../sanitizedSchemas"; + +export const registerDeprecatedSecretFolderRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "POST", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Create folders", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.CREATE.projectId), + environment: z.string().trim().describe(FOLDERS.CREATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.CREATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.CREATE.directory) + .optional(), + description: z.string().optional().nullable().describe(FOLDERS.CREATE.description) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + path: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const folder = await server.services.folder.createFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + path, + description: req.body.description + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.CREATE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderName: folder.name, + folderPath: path, + ...(req.body.description ? { description: req.body.description } : {}) + } + } + }); + return { folder }; + } + }); + + server.route({ + url: "/:folderId", + method: "PATCH", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Update folder", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + // old way this was name + folderId: z.string().describe(FOLDERS.UPDATE.folderId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.UPDATE.projectId), + environment: z.string().trim().describe(FOLDERS.UPDATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.UPDATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.directory) + .optional(), + description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + path: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const { folder, old } = await server.services.folder.updateFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + id: req.params.folderId, + path + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.UPDATE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderPath: path, + newFolderName: folder.name, + oldFolderName: old.name + } + } + }); + return { folder }; + } + }); + + server.route({ + url: "/batch", + method: "PATCH", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Update folders by batch", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug), + folders: z + .object({ + id: z.string().describe(FOLDERS.UPDATE.folderId), + environment: z.string().trim().describe(FOLDERS.UPDATE.environment), + name: z + .string() + .trim() + .describe(FOLDERS.UPDATE.name) + .refine((name) => isValidFolderName(name), { + message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." + }), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) + .transform(removeTrailingSlash) + .describe(FOLDERS.UPDATE.path), + description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) + }) + .array() + .min(1) + }), + response: { + 200: z.object({ + folders: SecretFoldersSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { newFolders, oldFolders, projectId } = await server.services.folder.updateManyFolders({ + ...req.body, + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId + }); + + await Promise.all( + req.body.folders.map(async (folder, index) => { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.UPDATE_FOLDER, + metadata: { + environment: oldFolders[index].envId, + folderId: oldFolders[index].id, + folderPath: folder.path, + newFolderName: newFolders[index].name, + oldFolderName: oldFolders[index].name + } + } + }); + }) + ); + + return { folders: newFolders }; + } + }); + + // TODO(daniel): Expose this route in api reference and write docs for it. + server.route({ + method: "DELETE", + url: "/:folderIdOrName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Delete a folder", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName) + }), + body: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.DELETE.projectId), + environment: z.string().trim().describe(FOLDERS.DELETE.environment), + path: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.path) + .optional(), + // keep this here as cli need directory + directory: z + .string() + .trim() + .default("/") + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.DELETE.directory) + .optional() + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.body.path || req.body.directory || "/"; + const folder = await server.services.folder.deleteFolder({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + idOrName: req.params.folderIdOrName, + path + }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.DELETE_FOLDER, + metadata: { + environment: req.body.environment, + folderId: folder.id, + folderPath: path, + folderName: folder.name + } + } + }); + return { folder }; + } + }); + + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Get folders", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + workspaceId: z.string().trim().describe(FOLDERS.LIST.projectId), + environment: z.string().trim().describe(FOLDERS.LIST.environment), + lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified), + path: z + .string() + .trim() + .transform(prefixWithSlash) // Transformations get skipped if path is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.path) + .optional(), + // backward compatibility with cli + directory: z + .string() + .trim() + .transform(prefixWithSlash) // Transformations get skipped if directory is undefined + .transform(removeTrailingSlash) + .describe(FOLDERS.LIST.directory) + .optional(), + recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive) + }), + response: { + 200: z.object({ + folders: SecretFoldersSchema.extend({ + relativePath: z.string().optional() + }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const path = req.query.path || req.query.directory || "/"; + const folders = await server.services.folder.getFolders({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId, + path + }); + return { folders }; + } + }); + + server.route({ + method: "GET", + url: "/:id", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + description: "Get folder by id", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId) + }), + response: { + 200: z.object({ + folder: SecretFoldersSchema.extend({ + environment: z.object({ + envId: z.string(), + envName: z.string(), + envSlug: z.string() + }), + path: z.string(), + projectId: z.string() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const folder = await server.services.folder.getFolderById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.id + }); + return { folder }; + } + }); +}; diff --git a/backend/src/server/routes/v1/deprecated-secret-import-router.ts b/backend/src/server/routes/v1/deprecated-secret-import-router.ts new file mode 100644 index 000000000..2fdbe4216 --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-secret-import-router.ts @@ -0,0 +1,472 @@ +import { z } from "zod"; + +import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { secretRawSchema } from "../sanitizedSchemas"; + +export const registerDeprecatedSecretImportRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Create secret imports", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path), + import: z.object({ + environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment), + path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path) + }), + isReplication: z.boolean().default(false).describe(SECRET_IMPORTS.CREATE.isReplication) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.createImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + projectId: req.body.workspaceId, + data: req.body.import + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.CREATE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + importFromSecretPath: secretImport.importPath, + importFromEnvironment: secretImport.importEnv.slug, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + return { message: "Successfully created secret import", secretImport }; + } + }); + + server.route({ + method: "PATCH", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Update secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path), + import: z.object({ + environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment), + path: z + .string() + .trim() + .optional() + .transform((val) => (val ? removeTrailingSlash(val) : val)) + .describe(SECRET_IMPORTS.UPDATE.import.path), + position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position) + }) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.updateImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId, + data: req.body.import + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.UPDATE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + position: secretImport.position, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + + return { message: "Successfully updated secret import", secretImport }; + } + }); + + server.route({ + method: "DELETE", + url: "/:secretImportId", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Delete secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path) + }), + response: { + 200: z.object({ + message: z.string(), + secretImport: SecretImportsSchema.omit({ importEnv: true }).merge( + z.object({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.deleteImport({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.body.workspaceId, + event: { + type: EventType.DELETE_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId, + importFromEnvironment: secretImport.importEnv.slug, + importFromSecretPath: secretImport.importPath, + importToEnvironment: req.body.environment, + importToSecretPath: req.body.path + } + } + }); + return { message: "Successfully deleted secret import", secretImport }; + } + }); + + server.route({ + method: "POST", + url: "/:secretImportId/replication-resync", + config: { + rateLimit: secretsLimit + }, + schema: { + description: "Resync secret replication of secret imports", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) + }), + body: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path) + }), + response: { + 200: z.object({ + message: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { message } = await server.services.secretImport.resyncSecretImportReplication({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId, + ...req.body, + projectId: req.body.workspaceId + }); + + return { message }; + } + }); + + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Get secret imports", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId), + environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment), + path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path) + }), + response: { + 200: z.object({ + message: z.string(), + secretImports: SecretImportsSchema.omit({ importEnv: true }) + .extend({ + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }) + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImports = await server.services.secretImport.getImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.query.workspaceId, + event: { + type: EventType.GET_SECRET_IMPORTS, + metadata: { + environment: req.query.environment, + folderId: secretImports?.[0]?.folderId, + numberOfImports: secretImports.length + } + } + }); + return { message: "Successfully fetched secret imports", secretImports }; + } + }); + + server.route({ + url: "/:secretImportId", + method: "GET", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + description: "Get single secret import", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretImportId: z.string().trim().describe(SECRET_IMPORTS.GET.secretImportId) + }), + response: { + 200: z.object({ + secretImport: SecretImportsSchema.omit({ importEnv: true }).extend({ + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + projectId: z.string(), + importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }), + secretPath: z.string() + }) + }) + } + }, + + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretImport = await server.services.secretImport.getImportById({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.secretImportId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: secretImport.projectId, + event: { + type: EventType.GET_SECRET_IMPORT, + metadata: { + secretImportId: secretImport.id, + folderId: secretImport.folderId + } + } + }); + + return { secretImport }; + } + }); + + server.route({ + url: "/secrets", + method: "GET", + config: { + rateLimit: secretsLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + secrets: z + .object({ + secretPath: z.string(), + environment: z.string(), + environmentInfo: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + folderId: z.string().optional(), + secrets: SecretsSchema.omit({ secretBlindIndex: true }).array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const importedSecrets = await server.services.secretImport.getSecretsFromImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + return { secrets: importedSecrets }; + } + }); + + server.route({ + url: "/secrets/raw", + method: "GET", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SecretImports], + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + secrets: z + .object({ + secretPath: z.string(), + environment: z.string(), + environmentInfo: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + folderId: z.string().optional(), + secrets: secretRawSchema.array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const importedSecrets = await server.services.secretImport.getRawSecretsFromImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + return { secrets: importedSecrets }; + } + }); +}; diff --git a/backend/src/server/routes/v1/deprecated-secret-tag-router.ts b/backend/src/server/routes/v1/deprecated-secret-tag-router.ts new file mode 100644 index 000000000..d90475408 --- /dev/null +++ b/backend/src/server/routes/v1/deprecated-secret-tag-router.ts @@ -0,0 +1,213 @@ +import { z } from "zod"; + +import { SecretTagsSchema } from "@app/db/schemas"; +import { ApiDocsTags, SECRET_TAGS } from "@app/lib/api-docs"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerDeprecatedSecretTagRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:projectId/tags", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.LIST.projectId) + }), + response: { + 200: z.object({ + workspaceTags: SecretTagsSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTags = await server.services.secretTag.getProjectTags({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId + }); + return { workspaceTags }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/tags/:tagId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.projectId), + tagId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.tagId) + }), + response: { + 200: z.object({ + // akhilmhdh: for terraform backward compatiability + workspaceTag: SecretTagsSchema.extend({ name: z.string() }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.getTagById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.tagId + }); + return { workspaceTag }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/tags/slug/:tagSlug", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.projectId), + tagSlug: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.tagSlug) + }), + response: { + 200: z.object({ + // akhilmhdh: for terraform backward compatiability + workspaceTag: SecretTagsSchema.extend({ name: z.string() }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.getTagBySlug({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + slug: req.params.tagSlug, + projectId: req.params.projectId + }); + return { workspaceTag }; + } + }); + + server.route({ + method: "POST", + url: "/:projectId/tags", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId) + }), + body: z.object({ + slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.CREATE.slug), + color: z.string().trim().describe(SECRET_TAGS.CREATE.color) + }), + response: { + 200: z.object({ + workspaceTag: SecretTagsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.createTag({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId: req.params.projectId, + ...req.body + }); + return { workspaceTag }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/tags/:tagId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.UPDATE.projectId), + tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId) + }), + body: z.object({ + slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.UPDATE.slug), + color: z.string().trim().describe(SECRET_TAGS.UPDATE.color) + }), + response: { + 200: z.object({ + workspaceTag: SecretTagsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.updateTag({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + id: req.params.tagId + }); + return { workspaceTag }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/tags/:tagId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Folders], + params: z.object({ + projectId: z.string().trim().describe(SECRET_TAGS.DELETE.projectId), + tagId: z.string().trim().describe(SECRET_TAGS.DELETE.tagId) + }), + response: { + 200: z.object({ + workspaceTag: SecretTagsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.deleteTag({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.tagId + }); + return { workspaceTag }; + } + }); +}; 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 6108be32b..4fb07aeac 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -13,8 +13,15 @@ import { registerCaRouter } from "./certificate-authority-router"; import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers"; import { registerCertRouter } from "./certificate-router"; import { registerCertificateTemplateRouter } from "./certificate-template-router"; +import { registerDeprecatedProjectEnvRouter } from "./deprecated-project-env-router"; +import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router"; +import { registerDeprecatedProjectRouter } from "./deprecated-project-router"; +import { registerDeprecatedSecretFolderRouter } from "./deprecated-secret-folder-router"; +import { registerDeprecatedSecretImportRouter } from "./deprecated-secret-import-router"; +import { registerDeprecatedSecretTagRouter } from "./deprecated-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"; @@ -25,6 +32,7 @@ import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-rou import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router"; import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router"; import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router"; +import { registerIdentityProjectRouter } from "./identity-project-router"; import { registerIdentityRouter } from "./identity-router"; import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router"; import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router"; @@ -45,8 +53,6 @@ import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectMembershipRouter } from "./project-membership-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"; @@ -87,8 +93,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerNotificationRouter, { prefix: "/notifications" }); await server.register(registerInviteOrgRouter, { prefix: "/invite-org" }); await server.register(registerUserActionRouter, { prefix: "/user-action" }); - await server.register(registerSecretImportRouter, { prefix: "/secret-imports" }); - await server.register(registerSecretFolderRouter, { prefix: "/folders" }); + await server.register(registerDeprecatedSecretImportRouter, { prefix: "/secret-imports" }); + await server.register(registerDeprecatedSecretFolderRouter, { prefix: "/folders" }); await server.register( async (workflowIntegrationRouter) => { @@ -101,15 +107,28 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register( async (projectRouter) => { - await projectRouter.register(registerProjectRouter); - await projectRouter.register(registerProjectEnvRouter); + await projectRouter.register(registerDeprecatedProjectRouter); + await projectRouter.register(registerDeprecatedProjectEnvRouter); + // depreciated completed in use await projectRouter.register(registerProjectKeyRouter); - await projectRouter.register(registerProjectMembershipRouter); - await projectRouter.register(registerSecretTagRouter); + await projectRouter.register(registerDeprecatedProjectMembershipRouter); + await projectRouter.register(registerDeprecatedSecretTagRouter); }, { prefix: "/workspace" } ); + await server.register( + async (projectRouter) => { + await projectRouter.register(registerProjectRouter); + await projectRouter.register(registerProjectMembershipRouter); + await projectRouter.register(registerProjectEnvRouter); + await projectRouter.register(registerSecretTagRouter); + await projectRouter.register(registerGroupProjectRouter); + await projectRouter.register(registerIdentityProjectRouter); + }, + { prefix: "/projects" } + ); + await server.register( async (pkiRouter) => { await pkiRouter.register(registerCaRouter, { prefix: "/ca" }); diff --git a/backend/src/server/routes/v1/project-env-router.ts b/backend/src/server/routes/v1/project-env-router.ts index 9a136e160..81b828b55 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -11,58 +11,7 @@ import { AuthMode } from "@app/services/auth/auth-type"; export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/environments/:envId", - config: { - rateLimit: readLimit - }, - schema: { - hide: false, - tags: [ApiDocsTags.Environments], - description: "Get Environment", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - // NOTE(daniel): workspaceId isn't used, but we need to keep it for backwards compatibility. The endpoint defined below, uses no project ID, and is takes a pure environment ID. - workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId), - envId: z.string().trim().describe(ENVIRONMENTS.GET.id) - }), - response: { - 200: z.object({ - environment: ProjectEnvironmentsSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const environment = await server.services.projectEnv.getEnvironmentById({ - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - id: req.params.envId - }); - - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - projectId: environment.projectId, - event: { - type: EventType.GET_ENVIRONMENT, - metadata: { - id: environment.id - } - } - }); - - return { environment }; - } - }); - - server.route({ - method: "GET", - url: "/environments/:envId", + url: "/:projectId/environments/:envId", config: { rateLimit: readLimit }, @@ -76,7 +25,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - envId: z.string().trim().describe(ENVIRONMENTS.GET.id) + envId: z.string().trim().describe(ENVIRONMENTS.GET.id), + projectId: z.string().trim().describe(ENVIRONMENTS.GET.projectId) }), response: { 200: z.object({ @@ -111,7 +61,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:workspaceId/environments", + url: "/:projectId/environments", config: { rateLimit: writeLimit }, @@ -125,7 +75,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId) + projectId: z.string().trim().describe(ENVIRONMENTS.CREATE.projectId) }), body: z.object({ name: z.string().trim().describe(ENVIRONMENTS.CREATE.name), @@ -135,7 +85,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ message: z.string(), - workspace: z.string(), + projectId: z.string(), environment: ProjectEnvironmentsSchema }) } @@ -147,7 +97,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, - projectId: req.params.workspaceId, + projectId: req.params.projectId, ...req.body }); @@ -164,7 +114,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { }); return { message: "Successfully created new environment", - workspace: req.params.workspaceId, + projectId: req.params.projectId, environment }; } @@ -172,7 +122,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", - url: "/:workspaceId/environments/:id", + url: "/:projectId/environments/:id", config: { rateLimit: writeLimit }, @@ -186,7 +136,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId), + projectId: z.string().trim().describe(ENVIRONMENTS.UPDATE.projectId), id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id) }), body: z.object({ @@ -197,7 +147,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ message: z.string(), - workspace: z.string(), + projectId: z.string(), environment: ProjectEnvironmentsSchema }) } @@ -209,7 +159,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, id: req.params.id, ...req.body }); @@ -232,7 +182,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { return { message: "Successfully updated environment", - workspace: req.params.workspaceId, + projectId: req.params.projectId, environment }; } @@ -240,7 +190,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { server.route({ method: "DELETE", - url: "/:workspaceId/environments/:id", + url: "/:projectId/environments/:id", config: { rateLimit: writeLimit }, @@ -254,13 +204,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId), + projectId: z.string().trim().describe(ENVIRONMENTS.DELETE.projectId), id: z.string().trim().describe(ENVIRONMENTS.DELETE.id) }), response: { 200: z.object({ message: z.string(), - workspace: z.string(), + projectId: z.string(), environment: ProjectEnvironmentsSchema }) } @@ -272,7 +222,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, id: req.params.id }); @@ -290,7 +240,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { return { message: "Successfully deleted environment", - workspace: req.params.workspaceId, + projectId: req.params.projectId, environment }; } diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index cd3734efc..f3cdfe701 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, @@ -18,7 +19,7 @@ import { ProjectUserMembershipTemporaryMode } from "@app/services/project-member export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/memberships", + url: "/:projectId/memberships", config: { rateLimit: readLimit }, @@ -32,7 +33,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.workspaceId) + projectId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.projectId) }), response: { 200: z.object({ @@ -71,7 +72,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { memberships }; } @@ -79,7 +80,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "GET", - url: "/:workspaceId/memberships/:membershipId", + url: "/:projectId/memberships/:membershipId", config: { rateLimit: readLimit }, @@ -91,7 +92,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider } ], params: z.object({ - workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId), + projectId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId), membershipId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.membershipId) }), response: { @@ -129,7 +130,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, id: req.params.membershipId }); return { membership }; @@ -138,7 +139,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "POST", - url: "/:workspaceId/memberships/details", + url: "/:projectId/memberships/details", config: { rateLimit: readLimit }, @@ -152,7 +153,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider } ], params: z.object({ - workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId) + projectId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId) }), body: z.object({ username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username) @@ -191,7 +192,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, username: req.body.username }); return { membership }; @@ -200,61 +201,83 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "POST", - url: "/:workspaceId/memberships", + url: "/:projectId/memberships", config: { rateLimit: writeLimit }, schema: { + hide: false, + tags: [ApiDocsTags.ProjectUsers], + description: "Invite members to project", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - workspaceId: 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.workspaceId, - 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.workspaceId, + 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 }; } }); server.route({ method: "PATCH", - url: "/:workspaceId/memberships/:membershipId", + url: "/:projectId/memberships/:membershipId", config: { rateLimit: writeLimit }, @@ -268,7 +291,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.workspaceId), + projectId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.projectId), membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId) }), body: z.object({ @@ -305,31 +328,87 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, membershipId: req.params.membershipId, roles: req.body.roles }); - // await server.services.auditLog.createAuditLog({ - // ...req.auditLogInfo, - // projectId: req.params.workspaceId, - // 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: "/:workspaceId/memberships/:membershipId", + 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", config: { rateLimit: writeLimit }, @@ -341,7 +420,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider } ], params: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), membershipId: z.string().trim() }), response: { @@ -357,15 +436,15 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, membershipId: req.params.membershipId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { - type: EventType.REMOVE_WORKSPACE_MEMBER, + type: EventType.REMOVE_PROJECT_MEMBER, metadata: { userId: membership.userId, email: "" @@ -378,13 +457,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider server.route({ method: "DELETE", - url: "/:workspaceId/leave", + url: "/:projectId/leave", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -398,7 +477,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.leaveProject({ actorId: req.permission.id, actor: req.permission.type, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { membership }; } diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 5e7dce76e..be8b46dd5 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -2,7 +2,10 @@ import slugify from "@sindresorhus/slugify"; import { z } from "zod"; import { + CertificatesSchema, IntegrationsSchema, + PkiAlertsSchema, + PkiCollectionsSchema, ProjectEnvironmentsSchema, ProjectMembershipsSchema, ProjectRolesSchema, @@ -16,18 +19,35 @@ import { } from "@app/db/schemas"; import { ProjectMicrosoftTeamsConfigsSchema } from "@app/db/schemas/project-microsoft-teams-configs"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types"; +import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema"; +import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema"; +import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema"; +import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema"; +import { LoginMappingSource } from "@app/ee/services/ssh-host/ssh-host-types"; +import { sanitizedSshHostGroup } from "@app/ee/services/ssh-host-group/ssh-host-group-schema"; import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; import { re2Validator } from "@app/lib/zod"; import { readLimit, requestAccessLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; +import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; +import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums"; +import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema"; import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns"; +import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema"; import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types"; import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators"; +import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types"; -import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas"; +import { + integrationAuthPubSchema, + InternalCertificateAuthorityResponseSchema, + SanitizedProjectSchema +} from "../sanitizedSchemas"; import { sanitizedServiceTokenSchema } from "../v2/service-token-router"; const projectWithEnv = SanitizedProjectSchema.merge( @@ -40,41 +60,7 @@ const projectWithEnv = SanitizedProjectSchema.merge( export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/keys", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() - }), - response: { - 200: z.object({ - publicKeys: z - .object({ - publicKey: z.string().nullable().optional(), - userId: z.string() - }) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const publicKeys = await server.services.projectKey.getProjectPublicKeys({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId - }); - return { publicKeys }; - } - }); - - server.route({ - method: "GET", - url: "/:workspaceId/users", + url: "/:projectId/users", config: { rateLimit: readLimit }, @@ -96,7 +82,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { .optional() }), params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -142,7 +128,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorAuthMethod: req.permission.authMethod, includeGroupMembers: req.query.includeGroupMembers, - projectId: req.params.workspaceId, + projectId: req.params.projectId, actorOrgId: req.permission.orgId, roles }); @@ -151,6 +137,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, + projectName: req.body.projectName, + projectDescription: 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: "/", @@ -158,6 +221,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.Projects], + description: "List projects", + security: [ + { + bearerAuth: [] + } + ], querystring: z.object({ includeRoles: z .enum(["true", "false"]) @@ -167,7 +238,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaces: projectWithEnv + projects: projectWithEnv .extend({ roles: ProjectRolesSchema.array().optional() }) @@ -177,7 +248,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaces = await server.services.project.getProjects({ + const projects = await server.services.project.getProjects({ includeRoles: req.query.includeRoles, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -185,13 +256,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorOrgId: req.permission.orgId, type: req.query.type }); - return { workspaces }; + return { projects }; } }); server.route({ method: "GET", - url: "/:workspaceId", + url: "/:projectId", config: { rateLimit: readLimit }, @@ -205,33 +276,73 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId) + projectId: z.string().trim().describe(PROJECTS.GET.projectId) }), response: { 200: z.object({ - workspace: projectWithEnv.optional() + project: projectWithEnv.optional() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspace = await server.services.project.getAProject({ + const project = await server.services.project.getAProject({ filter: { type: ProjectFilterType.ID, - projectId: req.params.workspaceId + projectId: req.params.projectId }, actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId }); - return { workspace }; + return { project }; + } + }); + + 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: "/:workspaceId", + url: "/:projectId", config: { rateLimit: writeLimit }, @@ -245,20 +356,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId) + projectId: z.string().trim().describe(PROJECTS.DELETE.projectId) }), response: { 200: z.object({ - workspace: SanitizedProjectSchema.optional() + project: SanitizedProjectSchema.optional() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspace = await server.services.project.deleteProject({ + const project = await server.services.project.deleteProject({ filter: { type: ProjectFilterType.ID, - projectId: req.params.workspaceId + projectId: req.params.projectId }, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -269,68 +380,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.DELETE_PROJECT, - metadata: workspace + metadata: project } }); - return { workspace }; - } - }); - - server.route({ - url: "/:workspaceId/name", - method: "POST", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() - }), - body: z.object({ - name: z.string().trim() - }), - response: { - 200: z.object({ - message: z.string(), - workspace: SanitizedProjectSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const workspace = await server.services.project.updateName({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, - name: req.body.name - }); - - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - orgId: req.permission.orgId, - projectId: req.params.workspaceId, - event: { - type: EventType.UPDATE_PROJECT, - metadata: req.body - } - }); - - return { - message: "Successfully changed workspace name", - workspace - }; + return { project }; } }); server.route({ method: "PATCH", - url: "/:workspaceId", + url: "/:projectId", config: { rateLimit: writeLimit }, @@ -344,7 +407,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId) + projectId: z.string().trim().describe(PROJECTS.UPDATE.projectId) }), body: z.object({ name: z @@ -373,35 +436,35 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { .describe(PROJECTS.UPDATE.slug), secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing), showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy), - defaultProduct: z.nativeEnum(ProjectType).optional().describe(PROJECTS.UPDATE.defaultProduct), secretDetectionIgnoreValues: z .array(z.string()) .optional() - .describe(PROJECTS.UPDATE.secretDetectionIgnoreValues) + .describe(PROJECTS.UPDATE.secretDetectionIgnoreValues), + pitVersionLimit: z.number().min(1).max(100).optional() }), response: { 200: z.object({ - workspace: SanitizedProjectSchema + project: SanitizedProjectSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspace = await server.services.project.updateProject({ + const project = await server.services.project.updateProject({ filter: { type: ProjectFilterType.ID, - projectId: req.params.workspaceId + projectId: req.params.projectId }, update: { name: req.body.name, description: req.body.description, autoCapitalization: req.body.autoCapitalization, - defaultProduct: req.body.defaultProduct, hasDeleteProtection: req.body.hasDeleteProtection, slug: req.body.slug, secretSharing: req.body.secretSharing, showSnapshotsLegacy: req.body.showSnapshotsLegacy, - secretDetectionIgnoreValues: req.body.secretDetectionIgnoreValues + secretDetectionIgnoreValues: req.body.secretDetectionIgnoreValues, + pitVersionLimit: req.body.pitVersionLimit }, actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, @@ -412,7 +475,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.UPDATE_PROJECT, metadata: req.body @@ -420,164 +483,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }); return { - workspace - }; - } - }); - - server.route({ - method: "POST", - url: "/:workspaceId/auto-capitalization", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() - }), - body: z.object({ - autoCapitalization: z.boolean() - }), - response: { - 200: z.object({ - message: z.string(), - workspace: SanitizedProjectSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const workspace = await server.services.project.toggleAutoCapitalization({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, - autoCapitalization: req.body.autoCapitalization - }); - - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - orgId: req.permission.orgId, - projectId: req.params.workspaceId, - event: { - type: EventType.UPDATE_PROJECT, - metadata: req.body - } - }); - - return { - message: "Successfully changed workspace settings", - workspace - }; - } - }); - - server.route({ - method: "POST", - url: "/:workspaceId/delete-protection", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() - }), - body: z.object({ - hasDeleteProtection: z.boolean() - }), - response: { - 200: z.object({ - message: z.string(), - workspace: SanitizedProjectSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const workspace = await server.services.project.toggleDeleteProtection({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, - hasDeleteProtection: req.body.hasDeleteProtection - }); - - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - orgId: req.permission.orgId, - projectId: req.params.workspaceId, - event: { - type: EventType.UPDATE_PROJECT, - metadata: req.body - } - }); - - return { - message: "Successfully changed workspace settings", - workspace + project }; } }); server.route({ method: "PUT", - url: "/:workspaceSlug/version-limit", + url: "/:projectId/audit-logs-retention", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceSlug: z.string().trim() - }), - body: z.object({ - pitVersionLimit: z.number().min(1).max(100) - }), - response: { - 200: z.object({ - message: z.string(), - workspace: SanitizedProjectSchema - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const workspace = await server.services.project.updateVersionLimit({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - pitVersionLimit: req.body.pitVersionLimit, - workspaceSlug: req.params.workspaceSlug - }); - - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - orgId: req.permission.orgId, - projectId: workspace.id, - event: { - type: EventType.UPDATE_PROJECT, - metadata: req.body - } - }); - - return { - message: "Successfully changed workspace version limit", - workspace - }; - } - }); - - server.route({ - method: "PUT", - url: "/:workspaceSlug/audit-logs-retention", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - workspaceSlug: z.string().trim() + projectId: z.string().trim() }), body: z.object({ auditLogsRetentionDays: z.number().min(0) @@ -585,25 +504,28 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ message: z.string(), - workspace: SanitizedProjectSchema + project: SanitizedProjectSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspace = await server.services.project.updateAuditLogsRetention({ + const project = await server.services.project.updateAuditLogsRetention({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - workspaceSlug: req.params.workspaceSlug, - auditLogsRetentionDays: req.body.auditLogsRetentionDays + auditLogsRetentionDays: req.body.auditLogsRetentionDays, + filter: { + projectId: req.params.projectId, + type: ProjectFilterType.ID + } }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: workspace.id, + projectId: project.id, event: { type: EventType.UPDATE_PROJECT, metadata: req.body @@ -612,14 +534,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { message: "Successfully updated project's audit logs retention period", - workspace + project }; } }); server.route({ method: "GET", - url: "/:workspaceId/integrations", + url: "/:projectId/integrations", config: { rateLimit: readLimit }, @@ -633,7 +555,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.workspaceId) + projectId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.projectId) }), response: { 200: z.object({ @@ -656,7 +578,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { integrations }; } @@ -664,21 +586,21 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/authorizations", + url: "/:projectId/authorizations", config: { rateLimit: readLimit }, schema: { hide: false, tags: [ApiDocsTags.Integrations], - description: "List integration auth objects for a workspace.", + description: "List integration auth objects for a project.", security: [ { bearerAuth: [] } ], params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.workspaceId) + projectId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.projectId) }), response: { 200: z.object({ @@ -693,7 +615,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { authorizations }; } @@ -701,13 +623,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/service-token-data", + url: "/:projectId/service-token-data", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ @@ -722,7 +644,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { serviceTokenData }; } @@ -730,13 +652,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/ssh-config", + url: "/:projectId/ssh-config", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: ProjectSshConfigsSchema.pick({ @@ -756,7 +678,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + projectId: req.params.projectId }); await server.services.auditLog.createAuditLog({ @@ -777,13 +699,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "PATCH", - url: "/:workspaceId/ssh-config", + url: "/:projectId/ssh-config", config: { rateLimit: writeLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), body: z.object({ defaultUserSshCaId: z.string().optional(), @@ -807,7 +729,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, ...req.body }); @@ -831,13 +753,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/workflow-integration-config/:integration", + url: "/:projectId/workflow-integration-config/:integration", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), integration: z.nativeEnum(WorkflowIntegration) }), response: { @@ -876,13 +798,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, integration: req.params.integration }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG, metadata: { @@ -936,15 +858,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "PUT", - url: "/:workspaceId/workflow-integration", + url: "/:projectId/workflow-integration", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), - body: z.discriminatedUnion("integration", [ z.object({ integration: z.literal(WorkflowIntegration.SLACK), @@ -999,13 +920,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + projectId: req.params.projectId, ...req.body }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG, metadata: { @@ -1026,13 +947,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:workspaceId/environment-folder-tree", + url: "/:projectId/environment-folder-tree", config: { rateLimit: readLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.record( @@ -1043,7 +964,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const environmentsFolders = await server.services.folder.getProjectEnvironmentsFolders( - req.params.workspaceId, + req.params.projectId, req.permission ); @@ -1064,6 +985,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { type: z.nativeEnum(ProjectType).optional(), orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME), orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC), + projectIds: z.string().trim().array().optional(), name: z .string() .trim() @@ -1092,13 +1014,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", - url: "/:workspaceId/project-access", + url: "/:projectId/project-access", config: { rateLimit: requestAccessLimit }, schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), body: z.object({ comment: z @@ -1132,17 +1054,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { await server.services.project.requestProjectAccess({ permission: req.permission, comment: req.body.comment, - projectId: req.params.workspaceId + projectId: req.params.projectId }); if (req.auth.actor === ActorType.USER) { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.params.workspaceId, + projectId: req.params.projectId, event: { type: EventType.PROJECT_ACCESS_REQUEST, metadata: { - projectId: req.params.workspaceId, + projectId: req.params.projectId, requesterEmail: req.auth.user.email || req.auth.user.username, requesterId: req.auth.userId } @@ -1153,4 +1075,456 @@ 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 }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/cas", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateAuthorities], + params: z.object({ + projectId: z.string().trim() + }), + querystring: z.object({ + status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status), + friendlyName: z.string().optional().describe(PROJECTS.LIST_CAS.friendlyName), + commonName: z.string().optional().describe(PROJECTS.LIST_CAS.commonName), + offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CAS.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CAS.limit) + }), + response: { + 200: z.object({ + cas: z.array(InternalCertificateAuthorityResponseSchema) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const cas = await server.services.project.listProjectCas({ + filter: { + projectId: req.params.projectId, + type: ProjectFilterType.ID + }, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + ...req.query + }); + return { cas }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/certificates", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificates], + params: z.object({ + projectId: z.string().trim() + }), + querystring: z.object({ + friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName), + commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName), + offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit) + }), + response: { + 200: z.object({ + certificates: z.array(CertificatesSchema), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificates, totalCount } = await server.services.project.listProjectCertificates({ + filter: { + projectId: req.params.projectId, + type: ProjectFilterType.ID + }, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + ...req.query + }); + return { certificates, totalCount }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/pki-alerts", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAlerting], + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + alerts: z.array(PkiAlertsSchema) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { alerts } = await server.services.project.listProjectAlerts({ + projectId: req.params.projectId, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type + }); + + return { alerts }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/pki-collections", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateCollections], + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + collections: z.array(PkiCollectionsSchema) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { pkiCollections } = await server.services.project.listProjectPkiCollections({ + projectId: req.params.projectId, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type + }); + + return { collections: pkiCollections }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/pki-subscribers", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiSubscribers], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_PKI_SUBSCRIBERS.projectId) + }), + response: { + 200: z.object({ + subscribers: z.array(sanitizedPkiSubscriber) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const subscribers = await server.services.project.listProjectPkiSubscribers({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { subscribers }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/certificate-templates", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateTemplates], + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + certificateTemplates: sanitizedCertificateTemplate.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificateTemplates } = await server.services.project.listProjectCertificateTemplates({ + projectId: req.params.projectId, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type + }); + + return { certificateTemplates }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-certificates", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId) + }), + querystring: z.object({ + offset: z.coerce.number().default(0).describe(PROJECTS.LIST_SSH_CERTIFICATES.offset), + limit: z.coerce.number().default(25).describe(PROJECTS.LIST_SSH_CERTIFICATES.limit) + }), + response: { + 200: z.object({ + certificates: z.array(sanitizedSshCertificate), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificates, totalCount } = await server.services.project.listProjectSshCertificates({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId, + offset: req.query.offset, + limit: req.query.limit + }); + + return { certificates, totalCount }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-certificate-templates", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SshCertificateTemplates], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CERTIFICATE_TEMPLATES.projectId) + }), + response: { + 200: z.object({ + certificateTemplates: z.array(sanitizedSshCertificateTemplate) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificateTemplates } = await server.services.project.listProjectSshCertificateTemplates({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { certificateTemplates }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-cas", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SshCertificateAuthorities], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId) + }), + response: { + 200: z.object({ + cas: z.array(sanitizedSshCa) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const cas = await server.services.project.listProjectSshCas({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { cas }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-hosts", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SshHosts], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOSTS.projectId) + }), + response: { + 200: z.object({ + hosts: z.array( + sanitizedSshHost.extend({ + loginMappings: loginMappingSchema + .extend({ + source: z.nativeEnum(LoginMappingSource) + }) + .array() + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const hosts = await server.services.project.listProjectSshHosts({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { hosts }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-host-groups", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.SshHostGroups], + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOST_GROUPS.projectId) + }), + response: { + 200: z.object({ + groups: z.array( + sanitizedSshHostGroup.extend({ + loginMappings: loginMappingSchema.array(), + hostCount: z.number() + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const groups = await server.services.project.listProjectSshHostGroups({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { groups }; + } + }); }; diff --git a/backend/src/server/routes/v1/secret-tag-router.ts b/backend/src/server/routes/v1/secret-tag-router.ts index 01ba783fe..3c6c99eaf 100644 --- a/backend/src/server/routes/v1/secret-tag-router.ts +++ b/backend/src/server/routes/v1/secret-tag-router.ts @@ -22,20 +22,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTags: SecretTagsSchema.array() + tags: SecretTagsSchema.array() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTags = await server.services.secretTag.getProjectTags({ + const tags = await server.services.secretTag.getProjectTags({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.projectId }); - return { workspaceTags }; + return { tags }; } }); @@ -55,20 +55,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ // akhilmhdh: for terraform backward compatiability - workspaceTag: SecretTagsSchema.extend({ name: z.string() }) + tag: SecretTagsSchema.extend({ name: z.string() }) }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTag = await server.services.secretTag.getTagById({ + const tag = await server.services.secretTag.getTagById({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.tagId }); - return { workspaceTag }; + return { tag }; } }); @@ -88,13 +88,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ // akhilmhdh: for terraform backward compatiability - workspaceTag: SecretTagsSchema.extend({ name: z.string() }) + tag: SecretTagsSchema.extend({ name: z.string() }) }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTag = await server.services.secretTag.getTagBySlug({ + const tag = await server.services.secretTag.getTagBySlug({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -102,7 +102,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { slug: req.params.tagSlug, projectId: req.params.projectId }); - return { workspaceTag }; + return { tag }; } }); @@ -124,13 +124,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTag: SecretTagsSchema + tag: SecretTagsSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTag = await server.services.secretTag.createTag({ + const tag = await server.services.secretTag.createTag({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -138,7 +138,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { projectId: req.params.projectId, ...req.body }); - return { workspaceTag }; + return { tag }; } }); @@ -161,13 +161,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTag: SecretTagsSchema + tag: SecretTagsSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTag = await server.services.secretTag.updateTag({ + const tag = await server.services.secretTag.updateTag({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -175,7 +175,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { ...req.body, id: req.params.tagId }); - return { workspaceTag }; + return { tag }; } }); @@ -194,20 +194,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - workspaceTag: SecretTagsSchema + tag: SecretTagsSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const workspaceTag = await server.services.secretTag.deleteTag({ + const tag = await server.services.secretTag.deleteTag({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.tagId }); - return { workspaceTag }; + return { tag }; } }); }; diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 377af135c..386628d28 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -39,7 +39,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { body: z .object({ type: z.nativeEnum(WebhookType).default(WebhookType.GENERAL), - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), webhookUrl: z.string().url().trim(), webhookSecretKey: z.string().trim().optional(), @@ -67,13 +67,12 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId, ...req.body }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_WEBHOOK, metadata: { @@ -216,7 +215,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), schema: { querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim().optional(), secretPath: z .string() @@ -238,7 +237,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); return { message: "Successfully fetched webhook", webhooks }; } diff --git a/backend/src/server/routes/v2/deprecated-group-project-router.ts b/backend/src/server/routes/v2/deprecated-group-project-router.ts new file mode 100644 index 000000000..f0e4ee705 --- /dev/null +++ b/backend/src/server/routes/v2/deprecated-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 registerDeprecatedGroupProjectRouter = 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/deprecated-identity-project-router.ts b/backend/src/server/routes/v2/deprecated-identity-project-router.ts new file mode 100644 index 000000000..c16c874ae --- /dev/null +++ b/backend/src/server/routes/v2/deprecated-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 registerDeprecatedIdentityProjectRouter = 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/deprecated-project-membership-router.ts similarity index 96% rename from backend/src/server/routes/v2/project-membership-router.ts rename to backend/src/server/routes/v2/deprecated-project-membership-router.ts index 76f1e9c5e..d88d2f996 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/deprecated-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 registerDeprecatedProjectMembershipRouter = 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/deprecated-project-router.ts similarity index 92% rename from backend/src/server/routes/v2/project-router.ts rename to backend/src/server/routes/v2/deprecated-project-router.ts index 8b9091364..7c1045855 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/deprecated-project-router.ts @@ -35,7 +35,8 @@ const projectWithEnv = SanitizedProjectSchema.extend({ kmsSecretManagerKeyId: z.string().nullable().optional() }); -export const registerProjectRouter = async (server: FastifyZodProvider) => { +export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider) => { + // depreciated /* Get project key */ server.route({ method: "GET", @@ -46,7 +47,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { schema: { description: "Return encrypted project key", params: z.object({ - workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId) + workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.projectId) }), response: { 200: ProjectKeysSchema.merge( @@ -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", @@ -186,8 +125,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, - workspaceName: req.body.projectName, - workspaceDescription: req.body.projectDescription, + projectName: req.body.projectName, + projectDescription: req.body.projectDescription, slug: req.body.slug, kmsKeyId: req.body.kmsKeyId, template: req.body.template, @@ -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..aade29bb7 100644 --- a/backend/src/server/routes/v2/index.ts +++ b/backend/src/server/routes/v2/index.ts @@ -1,13 +1,15 @@ import { registerCaRouter } from "./certificate-authority-router"; -import { registerGroupProjectRouter } from "./group-project-router"; +import { registerDeprecatedGroupProjectRouter } from "./deprecated-group-project-router"; +import { registerDeprecatedIdentityProjectRouter } from "./deprecated-identity-project-router"; +import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router"; +import { registerDeprecatedProjectRouter } from "./deprecated-project-router"; import { registerIdentityOrgRouter } from "./identity-org-router"; -import { registerIdentityProjectRouter } from "./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 { registerSecretFolderRouter } from "./secret-folder-router"; +import { registerSecretImportRouter } from "./secret-import-router"; import { registerServiceTokenRouter } from "./service-token-router"; import { registerUserRouter } from "./user-router"; @@ -32,12 +34,17 @@ export const registerV2Routes = async (server: FastifyZodProvider) => { }, { prefix: "/organizations" } ); + + await server.register(registerSecretFolderRouter, { prefix: "/folders" }); + await server.register(registerSecretImportRouter, { prefix: "/secret-imports" }); + + // 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(registerDeprecatedProjectRouter); + await projectServer.register(registerDeprecatedIdentityProjectRouter); + await projectServer.register(registerDeprecatedGroupProjectRouter); + await projectServer.register(registerDeprecatedProjectMembershipRouter); }, { prefix: "/workspace" } ); diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v2/secret-folder-router.ts similarity index 80% rename from backend/src/server/routes/v1/secret-folder-router.ts rename to backend/src/server/routes/v2/secret-folder-router.ts index 871259147..0bf062452 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v2/secret-folder-router.ts @@ -28,7 +28,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.CREATE.projectId), environment: z.string().trim().describe(FOLDERS.CREATE.environment), name: z .string() @@ -43,17 +43,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .default("/") .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.CREATE.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.CREATE.directory) - .optional(), + .describe(FOLDERS.CREATE.path), description: z.string().optional().nullable().describe(FOLDERS.CREATE.description) }), response: { @@ -66,27 +56,24 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const folder = await server.services.folder.createFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - path, description: req.body.description }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, folderName: folder.name, - folderPath: path, + folderPath: req.body.path, ...(req.body.description ? { description: req.body.description } : {}) } } @@ -115,7 +102,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => folderId: z.string().describe(FOLDERS.UPDATE.folderId) }), body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId), environment: z.string().trim().describe(FOLDERS.UPDATE.environment), name: z .string() @@ -130,17 +117,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .default("/") .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.UPDATE.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.UPDATE.directory) - .optional(), + .describe(FOLDERS.UPDATE.path), description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description) }), response: { @@ -153,26 +130,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const { folder, old } = await server.services.folder.updateFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - id: req.params.folderId, - path + id: req.params.folderId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.UPDATE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, - folderPath: path, + folderPath: req.body.path, newFolderName: folder.name, oldFolderName: old.name } @@ -198,7 +172,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug), + projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId), folders: z .object({ id: z.string().describe(FOLDERS.UPDATE.folderId), @@ -281,7 +255,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName) }), body: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId), + projectId: z.string().trim().describe(FOLDERS.DELETE.projectId), environment: z.string().trim().describe(FOLDERS.DELETE.environment), path: z .string() @@ -290,16 +264,6 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) .describe(FOLDERS.DELETE.path) - .optional(), - // keep this here as cli need directory - directory: z - .string() - .trim() - .default("/") - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.DELETE.directory) - .optional() }), response: { 200: z.object({ @@ -309,26 +273,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.body.path || req.body.directory || "/"; const folder = await server.services.folder.deleteFolder({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, - idOrName: req.params.folderIdOrName, - path + idOrName: req.params.folderIdOrName }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.DELETE_FOLDER, metadata: { environment: req.body.environment, folderId: folder.id, - folderPath: path, + folderPath: req.body.path, folderName: folder.name } } @@ -353,7 +314,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => } ], querystring: z.object({ - workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId), + projectId: z.string().trim().describe(FOLDERS.LIST.projectId), environment: z.string().trim().describe(FOLDERS.LIST.environment), lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified), path: z @@ -361,16 +322,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .trim() .transform(prefixWithSlash) // Transformations get skipped if path is undefined .transform(removeTrailingSlash) - .describe(FOLDERS.LIST.path) - .optional(), - // backward compatibility with cli - directory: z - .string() - .trim() - .transform(prefixWithSlash) // Transformations get skipped if directory is undefined - .transform(removeTrailingSlash) - .describe(FOLDERS.LIST.directory) - .optional(), + .describe(FOLDERS.LIST.path), recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive) }), response: { @@ -383,15 +335,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const path = req.query.path || req.query.directory || "/"; const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId, - path + ...req.query }); return { folders }; } diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v2/secret-import-router.ts similarity index 85% rename from backend/src/server/routes/v1/secret-import-router.ts rename to backend/src/server/routes/v2/secret-import-router.ts index fca11f8a0..d9802c4b3 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v2/secret-import-router.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas"; +import { SecretImportsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; @@ -27,7 +27,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => } ], body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path), import: z.object({ @@ -55,13 +55,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - projectId: req.body.workspaceId, + projectId: req.body.projectId, data: req.body.import }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.CREATE_SECRET_IMPORT, metadata: { @@ -97,7 +97,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path), import: z.object({ @@ -131,13 +131,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId, + projectId: req.body.projectId, data: req.body.import }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.UPDATE_SECRET_IMPORT, metadata: { @@ -173,7 +173,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path) }), @@ -197,12 +197,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId + projectId: req.body.projectId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.body.workspaceId, + projectId: req.body.projectId, event: { type: EventType.DELETE_SECRET_IMPORT, metadata: { @@ -236,7 +236,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId) }), body: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path) }), @@ -255,7 +255,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, - projectId: req.body.workspaceId + projectId: req.body.projectId }); return { message }; @@ -278,7 +278,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => } ], querystring: z.object({ - workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId), + projectId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId), environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path) }), @@ -301,12 +301,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, - projectId: req.query.workspaceId + projectId: req.query.projectId }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: req.query.workspaceId, + projectId: req.query.projectId, event: { type: EventType.GET_SECRET_IMPORTS, metadata: { @@ -386,55 +386,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => config: { rateLimit: secretsLimit }, - schema: { - querystring: z.object({ - workspaceId: z.string().trim(), - environment: z.string().trim(), - path: z.string().trim().default("/").transform(removeTrailingSlash) - }), - response: { - 200: z.object({ - secrets: z - .object({ - secretPath: z.string(), - environment: z.string(), - environmentInfo: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }), - folderId: z.string().optional(), - secrets: SecretsSchema.omit({ secretBlindIndex: true }).array() - }) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const importedSecrets = await server.services.secretImport.getSecretsFromImports({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId - }); - return { secrets: importedSecrets }; - } - }); - - server.route({ - url: "/secrets/raw", - method: "GET", - config: { - rateLimit: secretsLimit - }, schema: { hide: false, tags: [ApiDocsTags.SecretImports], querystring: z.object({ - workspaceId: z.string().trim(), + projectId: z.string().trim(), environment: z.string().trim(), path: z.string().trim().default("/").transform(removeTrailingSlash) }), @@ -463,8 +419,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => actor: req.permission.type, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - ...req.query, - projectId: req.query.workspaceId + ...req.query }); return { secrets: importedSecrets }; } diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/deprecated-secret-router.ts similarity index 96% rename from backend/src/server/routes/v3/secret-router.ts rename to backend/src/server/routes/v3/deprecated-secret-router.ts index ce0d4f188..c037a3dee 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/deprecated-secret-router.ts @@ -39,7 +39,7 @@ const SecretReferenceNodeTree: z.ZodType = SecretReference children: z.lazy(() => SecretReferenceNodeTree.array()) }); -export const registerSecretRouter = async (server: FastifyZodProvider) => { +export const registerDeprecatedSecretRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", url: "/tags/:secretName", @@ -230,7 +230,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } }) .describe(RAW_SECRETS.LIST.metadataFilter), - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.projectId), workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug), environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath), @@ -298,7 +298,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId = projectId; } - if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" }); + if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing project id or environment" }); const { secrets, imports } = await server.services.secret.getSecretsRaw({ actorId: req.permission.id, @@ -336,7 +336,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId, + projectId: workspaceId, environment, secretPath: req.query.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -405,7 +405,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName) }), querystring: z.object({ - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.projectId), workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceSlug), environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath), @@ -495,7 +495,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { distinctId: getTelemetryDistinctId(req), properties: { numberOfSecrets: 1, - workspaceId: secret.workspace, + projectId: secret.workspace, environment, secretPath: req.query.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -526,7 +526,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName) }), body: z.object({ - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.CREATE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.CREATE.projectId), projectSlug: z.string().trim().optional().describe(RAW_SECRETS.CREATE.projectSlug), environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment), secretPath: z @@ -638,7 +638,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: projectId, + projectId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -669,7 +669,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretName: BaseSecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName) }), body: z.object({ - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectId), projectSlug: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectSlug), environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment), secretValue: z @@ -791,7 +791,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: projectId, + projectId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -821,7 +821,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName) }), body: z.object({ - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId), projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug), environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment), secretPath: z @@ -909,7 +909,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: projectId, + projectId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1017,7 +1017,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: req.query.workspaceId, + projectId: req.query.workspaceId, environment: req.query.environment, secretPath: req.query.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1097,7 +1097,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: req.query.workspaceId, + projectId: req.query.workspaceId, environment: req.query.environment, secretPath: req.query.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1272,7 +1272,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1466,7 +1466,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1594,7 +1594,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1781,7 +1781,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -1914,7 +1914,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -2039,7 +2039,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: req.body.workspaceId, + projectId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -2067,7 +2067,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { ], body: z.object({ projectSlug: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectSlug), - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectId), environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment), secretPath: z .string() @@ -2166,7 +2166,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: secrets[0].workspace, + projectId: secrets[0].workspace, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -2194,7 +2194,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { ], body: z.object({ projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug), - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId), environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment), secretPath: z .string() @@ -2341,7 +2341,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: secrets[0].workspace, + projectId: secrets[0].workspace, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -2369,7 +2369,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { ], body: z.object({ projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug), - workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId), environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment), secretPath: z .string() @@ -2459,7 +2459,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, - workspaceId: secrets[0].workspace, + projectId: secrets[0].workspace, environment: req.body.environment, secretPath: req.body.secretPath, channel: getUserAgentType(req.headers["user-agent"]), @@ -2469,95 +2469,4 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { return { secrets }; } }); - - server.route({ - method: "GET", - url: "/raw/:secretName/secret-reference-tree", - config: { - rateLimit: secretsLimit - }, - schema: { - hide: false, - tags: [ApiDocsTags.Secrets], - description: "Get secret reference tree", - security: [ - { - bearerAuth: [] - } - ], - params: z.object({ - secretName: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.secretName) - }), - querystring: z.object({ - workspaceId: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.workspaceId), - environment: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.environment), - secretPath: z - .string() - .trim() - .default("/") - .transform(removeTrailingSlash) - .describe(RAW_SECRETS.GET_REFERENCE_TREE.secretPath) - }), - response: { - 200: z.object({ - tree: SecretReferenceNodeTree, - value: z.string().optional() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const { secretName } = req.params; - const { secretPath, environment, workspaceId } = req.query; - const { tree, value } = await server.services.secret.getSecretReferenceTree({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: workspaceId, - secretName, - secretPath, - environment - }); - - return { tree, value }; - } - }); - - server.route({ - method: "POST", - url: "/backfill-secret-references", - config: { - rateLimit: secretsLimit - }, - schema: { - description: "Backfill secret references", - security: [ - { - bearerAuth: [] - } - ], - body: z.object({ - projectId: z.string().trim().min(1) - }), - response: { - 200: z.object({ - message: z.string() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const { projectId } = req.body; - const message = await server.services.secret.backfillSecretReferences({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId - }); - - return message; - } - }); }; diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index 1c31741a1..d7fa94d6b 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -1,7 +1,6 @@ +import { registerDeprecatedSecretRouter } from "./deprecated-secret-router"; 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"; @@ -9,7 +8,6 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerSignupRouter, { prefix: "/signup" }); 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(registerDeprecatedSecretRouter, { prefix: "/secrets" }); 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/backend/src/server/routes/v4/index.ts b/backend/src/server/routes/v4/index.ts new file mode 100644 index 000000000..db2e69222 --- /dev/null +++ b/backend/src/server/routes/v4/index.ts @@ -0,0 +1,5 @@ +import { registerSecretRouter } from "./secret-router"; + +export const registerV4Routes = async (server: FastifyZodProvider) => { + await server.register(registerSecretRouter, { prefix: "/secrets" }); +}; diff --git a/backend/src/server/routes/v4/secret-router.ts b/backend/src/server/routes/v4/secret-router.ts new file mode 100644 index 000000000..ead96668c --- /dev/null +++ b/backend/src/server/routes/v4/secret-router.ts @@ -0,0 +1,1316 @@ +import picomatch from "picomatch"; +import { z } from "zod"; + +import { SecretApprovalRequestsSchema, SecretType, ServiceTokenScopes } from "@app/db/schemas"; +import { EventType, SecretApprovalEvent, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, RAW_SECRETS } from "@app/lib/api-docs"; +import { BadRequestError } from "@app/lib/errors"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { secretsLimit } from "@app/server/config/rateLimiter"; +import { BaseSecretNameSchema, SecretNameSchema } from "@app/server/lib/schemas"; +import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; +import { getUserAgentType } from "@app/server/plugins/audit-log"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { ActorType, AuthMode } from "@app/services/auth/auth-type"; +import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; +import { SecretProtectionType } from "@app/services/secret/secret-types"; +import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types"; +import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; + +import { SanitizedTagSchema, secretRawSchema } from "../sanitizedSchemas"; + +const SecretReferenceNode = z.object({ + key: z.string(), + value: z.string().optional(), + environment: z.string(), + secretPath: z.string() +}); + +const convertStringBoolean = (defaultValue: boolean = false) => { + return z + .enum(["true", "false"]) + .default(defaultValue ? "true" : "false") + .transform((value) => value === "true"); +}; + +type TSecretReferenceNode = z.infer & { children: TSecretReferenceNode[] }; + +const SecretReferenceNodeTree: z.ZodType = SecretReferenceNode.extend({ + children: z.lazy(() => SecretReferenceNodeTree.array()) +}); + +export const registerSecretRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "List secrets", + security: [ + { + bearerAuth: [] + } + ], + querystring: z.object({ + metadataFilter: z + .string() + .optional() + .transform((val) => { + if (!val) return undefined; + + const result: { key?: string; value?: string }[] = []; + const pairs = val.split("|"); + + for (const pair of pairs) { + const keyValuePair: { key?: string; value?: string } = {}; + const parts = pair.split(/[,=]/); + + for (let i = 0; i < parts.length; i += 2) { + const identifier = parts[i].trim().toLowerCase(); + const value = parts[i + 1]?.trim(); + + if (identifier === "key" && value) { + keyValuePair.key = value; + } else if (identifier === "value" && value) { + keyValuePair.value = value; + } + } + + if (keyValuePair.key && keyValuePair.value) { + result.push(keyValuePair); + } + } + + return result.length ? result : undefined; + }) + .superRefine((metadata, ctx) => { + if (metadata && !Array.isArray(metadata)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "Invalid secretMetadata format. Correct format is key=value1,value=value2|key=value3,value=value4." + }); + } + + if (metadata) { + if (metadata.length > 10) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "You can only filter by up to 10 metadata fields" + }); + } + + for (const item of metadata) { + if (!item.key && !item.value) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "Invalid secretMetadata format, key or value must be provided. Correct format is key=value1,value=value2|key=value3,value=value4." + }); + } + } + } + }) + .describe(RAW_SECRETS.LIST.metadataFilter), + projectId: z.string().trim().optional().describe(RAW_SECRETS.LIST.projectId), + environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath), + viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.LIST.viewSecretValue), + expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.LIST.expand), + recursive: convertStringBoolean().describe(RAW_SECRETS.LIST.recursive), + include_imports: convertStringBoolean().describe(RAW_SECRETS.LIST.includeImports), + tagSlugs: z + .string() + .describe(RAW_SECRETS.LIST.tagSlugs) + .optional() + // split by comma and trim the strings + .transform((el) => (el ? el.split(",").map((i) => i.trim()) : [])) + }), + response: { + 200: z.object({ + secrets: secretRawSchema + .extend({ + secretPath: z.string().optional(), + secretValueHidden: z.boolean(), + secretMetadata: ResourceMetadataSchema.optional(), + tags: SanitizedTagSchema.array().optional() + }) + .array(), + imports: z + .object({ + secretPath: z.string(), + environment: z.string(), + folderId: z.string().optional(), + secrets: secretRawSchema + .omit({ createdAt: true, updatedAt: true }) + .extend({ + secretValueHidden: z.boolean(), + secretMetadata: ResourceMetadataSchema.optional() + }) + .array() + }) + .array() + .optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + // just for delivery hero usecase + let { secretPath, environment, projectId } = req.query; + if (req.auth.actor === ActorType.SERVICE) { + const scope = ServiceTokenScopes.parse(req.auth.serviceToken.scopes); + const isSingleScope = scope.length === 1; + if (isSingleScope && !picomatch.scan(scope[0].secretPath).isGlob) { + secretPath = scope[0].secretPath; + environment = scope[0].environment; + projectId = req.auth.serviceToken.projectId; + } + } + + if (!projectId || !environment) throw new BadRequestError({ message: "Missing project id or environment" }); + + const { secrets, imports } = await server.services.secret.getSecretsRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + environment, + expandSecretReferences: req.query.expandSecretReferences, + actorAuthMethod: req.permission.authMethod, + projectId, + viewSecretValue: req.query.viewSecretValue, + path: secretPath, + metadataFilter: req.query.metadataFilter, + includeImports: req.query.include_imports, + recursive: req.query.recursive, + tagSlugs: req.query.tagSlugs + }); + + await server.services.auditLog.createAuditLog({ + projectId, + ...req.auditLogInfo, + event: { + type: EventType.GET_SECRETS, + metadata: { + environment, + secretPath: req.query.secretPath, + numberOfSecrets: secrets.length + } + } + }); + + if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) { + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretPulled, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: secrets.length, + projectId, + environment, + secretPath: req.query.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + } + + return { secrets, imports }; + } + }); + + server.route({ + method: "GET", + url: "/id/:secretId", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + params: z.object({ + secretId: z.string() + }), + response: { + 200: z.object({ + secret: secretRawSchema.extend({ + secretPath: z.string(), + tags: SanitizedTagSchema.array().optional(), + secretMetadata: ResourceMetadataSchema.optional() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { secretId } = req.params; + const secret = await server.services.secret.getSecretByIdRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + secretId + }); + + return { secret }; + } + }); + + server.route({ + method: "GET", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Get a secret by name", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName) + }), + querystring: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.GET.projectId), + environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment), + secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath), + version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version), + type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type), + viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.GET.viewSecretValue), + expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.GET.expand), + include_imports: convertStringBoolean().describe(RAW_SECRETS.GET.includeImports) + }), + response: { + 200: z.object({ + secret: secretRawSchema.extend({ + secretValueHidden: z.boolean(), + secretPath: z.string(), + tags: SanitizedTagSchema.array().optional(), + secretMetadata: ResourceMetadataSchema.optional() + }) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + let { secretPath, environment, projectId } = req.query; + if (req.auth.actor === ActorType.SERVICE) { + const scope = ServiceTokenScopes.parse(req.auth.serviceToken.scopes); + const isSingleScope = scope.length === 1; + if (isSingleScope && !picomatch.scan(scope[0].secretPath).isGlob) { + secretPath = scope[0].secretPath; + environment = scope[0].environment; + projectId = req.auth.serviceToken.projectId; + } + } + + if (!environment) throw new BadRequestError({ message: "Missing environment" }); + if (!projectId) { + throw new BadRequestError({ message: "You must provide workspaceId" }); + } + + const secret = await server.services.secret.getSecretByNameRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + expandSecretReferences: req.query.expandSecretReferences, + environment, + projectId, + viewSecretValue: req.query.viewSecretValue, + path: secretPath, + secretName: req.params.secretName, + type: req.query.type, + includeImports: req.query.include_imports, + version: req.query.version + }); + + await server.services.auditLog.createAuditLog({ + projectId, + ...req.auditLogInfo, + event: { + type: EventType.GET_SECRET, + metadata: { + environment, + secretPath: req.query.secretPath, + secretId: secret.id, + secretKey: req.params.secretName, + secretVersion: secret.version, + secretMetadata: secret.secretMetadata + } + } + }); + + if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) { + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretPulled, + organizationId: req.permission.orgId, + distinctId: getTelemetryDistinctId(req), + properties: { + numberOfSecrets: 1, + projectId, + environment, + secretPath: req.query.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + } + return { secret }; + } + }); + + server.route({ + method: "POST", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Create secret", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName) + }), + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.CREATE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.CREATE.secretPath), + secretValue: z + .string() + .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) + .describe(RAW_SECRETS.CREATE.secretValue), + secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), + secretMetadata: ResourceMetadataSchema.optional(), + tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds), + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), + type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.CREATE.secretReminderRepeatDays), + secretReminderNote: z + .string() + .max(1024, "Secret reminder note cannot exceed 1024 characters") + .optional() + .nullable() + .describe(RAW_SECRETS.CREATE.secretReminderNote) + }), + response: { + 200: z.union([ + z.object({ + secret: secretRawSchema + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretOperation = await server.services.secret.createSecretRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + environment: req.body.environment, + actorAuthMethod: req.permission.authMethod, + projectId: req.body.projectId, + secretPath: req.body.secretPath, + secretName: req.params.secretName, + type: req.body.type, + secretValue: req.body.secretValue, + skipMultilineEncoding: req.body.skipMultilineEncoding, + secretComment: req.body.secretComment, + secretMetadata: req.body.secretMetadata, + tagIds: req.body.tagIds, + secretReminderNote: req.body.secretReminderNote, + secretReminderRepeatDays: req.body.secretReminderRepeatDays + }); + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath: req.body.secretPath, + environment: req.body.environment, + secretKey: req.params.secretName, + eventType: SecretApprovalEvent.Create + } + } + }); + + return { approval: secretOperation.approval }; + } + + const { secret } = secretOperation; + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_SECRET, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secretId: secret.id, + secretKey: req.params.secretName, + secretVersion: secret.version, + secretMetadata: req.body.secretMetadata + } + } + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretCreated, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: 1, + projectId: req.body.projectId, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + + return { secret }; + } + }); + + server.route({ + method: "PATCH", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Update secret", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretName: BaseSecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName) + }), + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.UPDATE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment), + secretValue: z + .string() + .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) + .optional() + .describe(RAW_SECRETS.UPDATE.secretValue), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.UPDATE.secretPath), + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding), + type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type), + tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), + metadata: z.record(z.string()).optional(), + secretMetadata: ResourceMetadataSchema.optional(), + secretReminderNote: z + .string() + .max(1024, "Secret reminder note cannot exceed 1024 characters") + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderNote), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays), + secretReminderRecipients: z.string().array().optional().describe(RAW_SECRETS.UPDATE.secretReminderRecipients), + newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName), + secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment) + }), + response: { + 200: z.union([ + z.object({ + secret: secretRawSchema.extend({ + secretValueHidden: z.boolean() + }) + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretOperation = await server.services.secret.updateSecretRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + environment: req.body.environment, + projectId: req.body.projectId, + secretPath: req.body.secretPath, + secretName: req.params.secretName, + type: req.body.type, + secretValue: req.body.secretValue, + skipMultilineEncoding: req.body.skipMultilineEncoding, + tagIds: req.body.tagIds, + secretReminderRepeatDays: req.body.secretReminderRepeatDays, + secretReminderRecipients: req.body.secretReminderRecipients, + secretReminderNote: req.body.secretReminderNote, + metadata: req.body.metadata, + newSecretName: req.body.newSecretName, + secretComment: req.body.secretComment, + secretMetadata: req.body.secretMetadata + }); + + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath: req.body.secretPath, + environment: req.body.environment, + secretKey: req.params.secretName, + eventType: SecretApprovalEvent.Update + } + } + }); + + return { approval: secretOperation.approval }; + } + const { secret } = secretOperation; + + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.UPDATE_SECRET, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secretId: secret.id, + secretKey: req.params.secretName, + secretVersion: secret.version, + secretMetadata: req.body.secretMetadata + } + } + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretUpdated, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: 1, + projectId: req.body.projectId, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + return { secret }; + } + }); + + server.route({ + method: "DELETE", + url: "/:secretName", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Delete secret", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName) + }), + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.DELETE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.DELETE.secretPath), + type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.DELETE.type) + }), + response: { + 200: z.union([ + z.object({ + secret: secretRawSchema.extend({ + secretValueHidden: z.boolean() + }) + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const secretOperation = await server.services.secret.deleteSecretRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + environment: req.body.environment, + projectId: req.body.projectId, + secretPath: req.body.secretPath, + secretName: req.params.secretName, + type: req.body.type + }); + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath: req.body.secretPath, + environment: req.body.environment, + secretKey: req.params.secretName, + eventType: SecretApprovalEvent.Delete + } + } + }); + + return { approval: secretOperation.approval }; + } + + const { secret } = secretOperation; + + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.DELETE_SECRET, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secretId: secret.id, + secretKey: req.params.secretName, + secretVersion: secret.version + } + } + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretDeleted, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: 1, + projectId: req.body.projectId, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + + return { secret }; + } + }); + + server.route({ + method: "POST", + url: "/move", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + body: z.object({ + projectSlug: z.string().trim(), + sourceEnvironment: z.string().trim(), + sourceSecretPath: z.string().trim().default("/").transform(removeTrailingSlash), + destinationEnvironment: z.string().trim(), + destinationSecretPath: z.string().trim().default("/").transform(removeTrailingSlash), + secretIds: z.string().array(), + shouldOverwrite: z.boolean().default(false) + }), + response: { + 200: z.object({ + isSourceUpdated: z.boolean(), + isDestinationUpdated: z.boolean() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { projectId, isSourceUpdated, isDestinationUpdated } = await server.services.secret.moveSecrets({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + projectId, + ...req.auditLogInfo, + event: { + type: EventType.MOVE_SECRETS, + metadata: { + sourceEnvironment: req.body.sourceEnvironment, + sourceSecretPath: req.body.sourceSecretPath, + destinationEnvironment: req.body.destinationEnvironment, + destinationSecretPath: req.body.destinationSecretPath, + secretIds: req.body.secretIds + } + } + }); + + return { + isSourceUpdated, + isDestinationUpdated + }; + } + }); + + server.route({ + method: "POST", + url: "/batch", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Create many secrets", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.UPDATE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.CREATE.secretPath), + secrets: z + .object({ + secretKey: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName), + secretValue: z + .string() + .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) + .describe(RAW_SECRETS.CREATE.secretValue), + secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), + metadata: z.record(z.string()).optional(), + secretMetadata: ResourceMetadataSchema.optional(), + tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds) + }) + .array() + .min(1) + }), + response: { + 200: z.union([ + z.object({ + secrets: secretRawSchema.array() + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { environment, secretPath, secrets: inputSecrets } = req.body; + + const secretOperation = await server.services.secret.createManySecretsRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + secretPath, + environment, + projectId: req.body.projectId, + secrets: inputSecrets + }); + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath, + environment, + secrets: inputSecrets.map((secret) => ({ + secretKey: secret.secretKey + })), + eventType: SecretApprovalEvent.CreateMany + } + } + }); + return { approval: secretOperation.approval }; + } + const { secrets } = secretOperation; + + const secretMetadataMap = new Map( + inputSecrets.map(({ secretKey, secretMetadata }) => [secretKey, secretMetadata]) + ); + + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_SECRETS, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secrets: secrets.map((secret) => ({ + secretId: secret.id, + secretKey: secret.secretKey, + secretVersion: secret.version, + secretMetadata: secretMetadataMap.get(secret.secretKey) + })) + } + } + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretCreated, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: secrets.length, + projectId: req.body.projectId, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + return { secrets }; + } + }); + + server.route({ + method: "PATCH", + url: "/batch", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Update many secrets", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.DELETE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.UPDATE.secretPath), + mode: z + .nativeEnum(SecretUpdateMode) + .optional() + .default(SecretUpdateMode.FailOnNotFound) + .describe(RAW_SECRETS.UPDATE.mode), + secrets: z + .object({ + secretKey: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName), + secretValue: z + .string() + .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) + .optional() + .describe(RAW_SECRETS.UPDATE.secretValue), + secretPath: z + .string() + .trim() + .transform(removeTrailingSlash) + .optional() + .describe(RAW_SECRETS.UPDATE.secretPath), + secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment), + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding), + newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName), + tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), + secretReminderNote: z + .string() + .max(1024, "Secret reminder note cannot exceed 1024 characters") + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderNote), + secretMetadata: ResourceMetadataSchema.optional(), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays) + }) + .array() + .min(1) + }), + response: { + 200: z.union([ + z.object({ + secrets: secretRawSchema.extend({ secretValueHidden: z.boolean() }).array() + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { environment, secretPath, secrets: inputSecrets } = req.body; + const secretOperation = await server.services.secret.updateManySecretsRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + secretPath, + environment, + projectId: req.body.projectId, + secrets: inputSecrets, + mode: req.body.mode + }); + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath, + environment, + secrets: inputSecrets.map((secret) => ({ + secretKey: secret.secretKey, + secretPath: secret.secretPath + })), + eventType: SecretApprovalEvent.UpdateMany + } + } + }); + return { approval: secretOperation.approval }; + } + const { secrets } = secretOperation; + + const secretMetadataMap = new Map( + inputSecrets.map(({ secretKey, secretMetadata }) => [secretKey, secretMetadata]) + ); + + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.UPDATE_SECRETS, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secrets: secrets + .filter((el) => el.version > 1) + .map((secret) => ({ + secretId: secret.id, + secretPath: secret.secretPath, + secretKey: secret.secretKey, + secretVersion: secret.version, + secretMetadata: secretMetadataMap.get(secret.secretKey) + })) + } + } + }); + const createdSecrets = secrets.filter((el) => el.version === 1); + if (createdSecrets.length) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.CREATE_SECRETS, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secrets: createdSecrets.map((secret) => ({ + secretId: secret.id, + secretPath: secret.secretPath, + secretKey: secret.secretKey, + secretVersion: secret.version, + secretMetadata: secretMetadataMap.get(secret.secretKey) + })) + } + } + }); + } + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretUpdated, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: secrets.length, + projectId: req.body.projectId, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + return { secrets }; + } + }); + + server.route({ + method: "DELETE", + url: "/batch", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Delete many secrets", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.DELETE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.DELETE.secretPath), + secrets: z + .object({ + secretKey: z.string().describe(RAW_SECRETS.DELETE.secretName), + type: z.nativeEnum(SecretType).default(SecretType.Shared) + }) + .array() + .min(1) + }), + response: { + 200: z.union([ + z.object({ + secrets: secretRawSchema + .extend({ + secretValueHidden: z.boolean() + }) + .array() + }), + z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled") + ]) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { environment, secretPath, secrets: inputSecrets } = req.body; + const secretOperation = await server.services.secret.deleteManySecretsRaw({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + environment, + secretPath, + projectId: req.body.projectId, + secrets: inputSecrets + }); + if (secretOperation.type === SecretProtectionType.Approval) { + await server.services.auditLog.createAuditLog({ + projectId: req.body.projectId, + ...req.auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath, + environment, + secrets: inputSecrets.map((secret) => ({ + secretKey: secret.secretKey + })), + eventType: SecretApprovalEvent.DeleteMany + } + } + }); + + return { approval: secretOperation.approval }; + } + const { secrets } = secretOperation; + + await server.services.auditLog.createAuditLog({ + projectId: secrets[0].workspace, + ...req.auditLogInfo, + event: { + type: EventType.DELETE_SECRETS, + metadata: { + environment: req.body.environment, + secretPath: req.body.secretPath, + secrets: secrets.map((secret) => ({ + secretId: secret.id, + secretKey: secret.secretKey, + secretVersion: secret.version + })) + } + } + }); + + await server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretDeleted, + distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, + properties: { + numberOfSecrets: secrets.length, + projectId: secrets[0].workspace, + environment: req.body.environment, + secretPath: req.body.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + return { secrets }; + } + }); + + server.route({ + method: "GET", + url: "/:secretName/secret-reference-tree", + config: { + rateLimit: secretsLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.Secrets], + description: "Get secret reference tree", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + secretName: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.secretName) + }), + querystring: z.object({ + projectId: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.projectId), + environment: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.environment), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(RAW_SECRETS.GET_REFERENCE_TREE.secretPath) + }), + response: { + 200: z.object({ + tree: SecretReferenceNodeTree, + value: z.string().optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { secretName } = req.params; + const { secretPath, environment, projectId } = req.query; + const { tree, value } = await server.services.secret.getSecretReferenceTree({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId, + secretName, + secretPath, + environment + }); + + return { tree, value }; + } + }); + + server.route({ + method: "POST", + url: "/backfill-secret-references", + config: { + rateLimit: secretsLimit + }, + schema: { + description: "Backfill secret references", + security: [ + { + bearerAuth: [] + } + ], + body: z.object({ + projectId: z.string().trim().min(1) + }), + response: { + 200: z.object({ + message: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { projectId } = req.body; + const message = await server.services.secret.backfillSecretReferences({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + projectId + }); + + return message; + } + }); +}; diff --git a/backend/src/services/app-connection/app-connection-dal.ts b/backend/src/services/app-connection/app-connection-dal.ts index f74f7cf06..10b6a6274 100644 --- a/backend/src/services/app-connection/app-connection-dal.ts +++ b/backend/src/services/app-connection/app-connection-dal.ts @@ -1,11 +1,115 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; -import { ormify } from "@app/lib/knex"; +import { TableName, TAppConnections } from "@app/db/schemas"; +import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex"; +import { transformUsageToProjects } from "@app/services/app-connection/app-connection-fns"; export type TAppConnectionDALFactory = ReturnType; +type AppConnectionFindFilter = Parameters>[0]; + export const appConnectionDALFactory = (db: TDbClient) => { const appConnectionOrm = ormify(db, TableName.AppConnection); - return { ...appConnectionOrm }; + const findWithProjectDetails = async (filter: AppConnectionFindFilter, tx?: Knex) => { + const query = (tx || db.replicaNode())(TableName.AppConnection) + .leftJoin(TableName.Project, `${TableName.AppConnection}.projectId`, `${TableName.Project}.id`) + .select(selectAllTableCols(TableName.AppConnection)) + .select( + // project + db.ref("name").withSchema(TableName.Project).as("projectName"), + db.ref("type").withSchema(TableName.Project).as("projectType"), + db.ref("slug").withSchema(TableName.Project).as("projectSlug") + ); + + if (filter) { + /* eslint-disable @typescript-eslint/no-misused-promises */ + void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.AppConnection, filter))); + } + + const connections = await query; + + return connections.map(({ projectName, projectSlug, projectType, projectId, ...connection }) => ({ + ...connection, + projectId, + project: projectId + ? { + name: projectName, + type: projectType, + slug: projectSlug, + id: projectId + } + : null + })); + }; + + const findAppConnectionUsageById = async (connectionId: string, tx?: Knex) => { + const secretSyncs = await (tx || db.replicaNode())(TableName.SecretSync) + .where(`${TableName.SecretSync}.connectionId`, connectionId) + .join(TableName.Project, `${TableName.SecretSync}.projectId`, `${TableName.Project}.id`) + .select( + db.ref("name").withSchema(TableName.SecretSync), + db.ref("id").withSchema(TableName.SecretSync), + db.ref("projectId").withSchema(TableName.SecretSync), + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("slug").as("projectSlug").withSchema(TableName.Project), + db.ref("type").as("projectType").withSchema(TableName.Project) + ); + + const secretRotations = await (tx || db.replicaNode())(TableName.SecretRotationV2) + .where(`${TableName.SecretRotationV2}.connectionId`, connectionId) + .join(TableName.SecretFolder, `${TableName.SecretRotationV2}.folderId`, `${TableName.SecretFolder}.id`) + .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) + .select( + db.ref("name").withSchema(TableName.SecretRotationV2), + db.ref("id").withSchema(TableName.SecretRotationV2), + db.ref("id").as("projectId").withSchema(TableName.Project), + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("slug").as("projectSlug").withSchema(TableName.Project), + db.ref("type").as("projectType").withSchema(TableName.Project) + ); + + const externalCas = await (tx || db.replicaNode())(TableName.ExternalCertificateAuthority) + .where(`${TableName.ExternalCertificateAuthority}.appConnectionId`, connectionId) + .orWhere(`${TableName.ExternalCertificateAuthority}.dnsAppConnectionId`, connectionId) + .join( + TableName.CertificateAuthority, + `${TableName.ExternalCertificateAuthority}.caId`, + `${TableName.CertificateAuthority}.id` + ) + .join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`) + .select( + db.ref("name").withSchema(TableName.CertificateAuthority), + db.ref("id").withSchema(TableName.ExternalCertificateAuthority), + db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority), + db.ref("dnsAppConnectionId").withSchema(TableName.ExternalCertificateAuthority), + db.ref("id").as("projectId").withSchema(TableName.Project), + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("slug").as("projectSlug").withSchema(TableName.Project), + db.ref("type").as("projectType").withSchema(TableName.Project) + ); + + const dataSources = await (tx || db.replicaNode())(TableName.SecretScanningDataSource) + .where(`${TableName.SecretScanningDataSource}.connectionId`, connectionId) + .join(TableName.Project, `${TableName.SecretScanningDataSource}.projectId`, `${TableName.Project}.id`) + .select( + db.ref("name").withSchema(TableName.SecretScanningDataSource), + db.ref("id").withSchema(TableName.SecretScanningDataSource), + db.ref("id").as("projectId").withSchema(TableName.Project), + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("slug").as("projectSlug").withSchema(TableName.Project), + db.ref("type").as("projectType").withSchema(TableName.Project) + ); + + return transformUsageToProjects({ + secretSyncs, + secretRotations, + dataSources, + externalCas + }); + }; + + return { ...appConnectionOrm, findAppConnectionUsageById, findWithProjectDetails }; }; diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 5aacab1b1..f88b5a357 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -1,3 +1,4 @@ +import { ProjectType } from "@app/db/schemas"; import { TAppConnections } from "@app/db/schemas/app-connections"; import { getOCIConnectionListItem, @@ -8,6 +9,8 @@ import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; +import { SECRET_ROTATION_CONNECTION_MAP } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-maps"; +import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-maps"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps"; @@ -16,6 +19,7 @@ import { validateSqlConnectionCredentials } from "@app/services/app-connection/shared/sql"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { SECRET_SYNC_CONNECTION_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { getOnePassConnectionListItem, @@ -133,7 +137,22 @@ import { } from "./windmill"; import { getZabbixConnectionListItem, validateZabbixConnectionCredentials, ZabbixConnectionMethod } from "./zabbix"; -export const listAppConnectionOptions = () => { +const SECRET_SYNC_APP_CONNECTION_MAP = Object.fromEntries( + Object.entries(SECRET_SYNC_CONNECTION_MAP).map(([key, value]) => [value, key]) +); + +const SECRET_ROTATION_APP_CONNECTION_MAP = Object.fromEntries( + Object.entries(SECRET_ROTATION_CONNECTION_MAP).map(([key, value]) => [value, key]) +); + +const SECRET_SCANNING_APP_CONNECTION_MAP = Object.fromEntries( + Object.entries(SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP).map(([key, value]) => [value, key]) +); + +// scott: ideally this would be derived from a utilized map like the above +const PKI_APP_CONNECTIONS = [AppConnection.AWS, AppConnection.Cloudflare, AppConnection.AzureADCS]; + +export const listAppConnectionOptions = (projectType?: ProjectType) => { return [ getAwsConnectionListItem(), getGitHubConnectionListItem(), @@ -173,22 +192,51 @@ export const listAppConnectionOptions = () => { getDigitalOceanConnectionListItem(), getNetlifyConnectionListItem(), getOktaConnectionListItem() - ].sort((a, b) => a.name.localeCompare(b.name)); + ] + .filter((option) => { + switch (projectType) { + case ProjectType.SecretManager: + return ( + Boolean(SECRET_SYNC_APP_CONNECTION_MAP[option.app]) || + Boolean(SECRET_ROTATION_APP_CONNECTION_MAP[option.app]) + ); + case ProjectType.SecretScanning: + return Boolean(SECRET_SCANNING_APP_CONNECTION_MAP[option.app]); + case ProjectType.CertificateManager: + return PKI_APP_CONNECTIONS.includes(option.app); + case ProjectType.KMS: + return false; + case ProjectType.SSH: + return false; + default: + return true; + } + }) + .sort((a, b) => a.name.localeCompare(b.name)); }; export const encryptAppConnectionCredentials = async ({ orgId, credentials, - kmsService + kmsService, + projectId }: { orgId: string; credentials: TAppConnection["credentials"]; kmsService: TAppConnectionServiceFactoryDep["kmsService"]; + projectId: string | null | undefined; }) => { - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }); + const { encryptor } = await kmsService.createCipherPairWithDataKey( + projectId + ? { + type: KmsDataKey.SecretManager, + projectId + } + : { + type: KmsDataKey.Organization, + orgId + } + ); const { cipherTextBlob: encryptedCredentialsBlob } = encryptor({ plainText: Buffer.from(JSON.stringify(credentials)) @@ -200,16 +248,22 @@ export const encryptAppConnectionCredentials = async ({ export const decryptAppConnectionCredentials = async ({ orgId, encryptedCredentials, - kmsService + kmsService, + projectId }: { orgId: string; encryptedCredentials: Buffer; kmsService: TAppConnectionServiceFactoryDep["kmsService"]; + projectId: string | null | undefined; }) => { - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }); + const { decryptor } = await kmsService.createCipherPairWithDataKey( + projectId + ? { type: KmsDataKey.SecretManager, projectId } + : { + type: KmsDataKey.Organization, + orgId + } + ); const decryptedPlainTextBlob = decryptor({ cipherTextBlob: encryptedCredentials @@ -343,6 +397,7 @@ export const decryptAppConnection = async ( credentials: await decryptAppConnectionCredentials({ encryptedCredentials: appConnection.encryptedCredentials, orgId: appConnection.orgId, + projectId: appConnection.projectId, kmsService }), credentialsHash: crypto.nativeCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex") @@ -413,3 +468,73 @@ export const enterpriseAppCheck = async ( }); } }; + +type Resource = { + name: string; + id: string; + projectId: string; + projectName: string; + projectSlug: string; + projectType: string; +}; + +type UsageData = { + secretSyncs: Resource[]; + secretRotations: Resource[]; + dataSources: Resource[]; + externalCas: Resource[]; +}; + +type ResourceSummary = { + name: string; + id: string; +}; + +type ProjectWithResources = { + id: string; + name: string; + slug: string; + type: ProjectType; + resources: { + secretSyncs: ResourceSummary[]; + secretRotations: ResourceSummary[]; + dataSources: ResourceSummary[]; + externalCas: (ResourceSummary & { appConnectionId?: string; dnsAppConnectionId?: string })[]; + }; +}; + +export const transformUsageToProjects = (data: UsageData): ProjectWithResources[] => { + const projectMap = new Map(); + + Object.entries(data).forEach(([resourceType, resources]) => { + resources.forEach((resource) => { + const { projectId, projectName, projectSlug, projectType, name, id, ...rest } = resource; + + const projectKey = projectId; + + if (!projectMap.has(projectKey)) { + projectMap.set(projectKey, { + id: projectId, + name: projectName, + slug: projectSlug, + type: projectType as ProjectType, + resources: { + secretSyncs: [], + secretRotations: [], + dataSources: [], + externalCas: [] + } + }); + } + + const project = projectMap.get(projectKey)!; + project.resources[resourceType as keyof ProjectWithResources["resources"]].push({ + name, + id, + ...rest + }); + }); + }); + + return Array.from(projectMap.values()); +}; diff --git a/backend/src/services/app-connection/app-connection-schemas.ts b/backend/src/services/app-connection/app-connection-schemas.ts index d0dcb1a54..3f6e3914a 100644 --- a/backend/src/services/app-connection/app-connection-schemas.ts +++ b/backend/src/services/app-connection/app-connection-schemas.ts @@ -13,7 +13,15 @@ export const BaseAppConnectionSchema = AppConnectionsSchema.omit({ app: true, method: true }).extend({ - credentialsHash: z.string().optional() + credentialsHash: z.string().optional(), + project: z + .object({ + name: z.string(), + id: z.string(), + type: z.string(), + slug: z.string() + }) + .nullish() }); export const GenericCreateAppConnectionFieldsSchema = ( @@ -28,6 +36,7 @@ export const GenericCreateAppConnectionFieldsSchema = ( .max(256, "Description cannot exceed 256 characters") .nullish() .describe(AppConnections.CREATE(app).description), + projectId: z.string().optional().describe(AppConnections.CREATE(app).projectId), isPlatformManagedCredentials: supportsPlatformManagedCredentials ? z.boolean().optional().default(false).describe(AppConnections.CREATE(app).isPlatformManagedCredentials) : z diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index f5654d2fb..19d66b9fd 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -1,5 +1,6 @@ import { ForbiddenError, subject } from "@casl/ability"; +import { ActionProjectType, TAppConnections } from "@app/db/schemas"; import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci"; import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service"; import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb"; @@ -14,6 +15,10 @@ import { OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; +import { + ProjectPermissionAppConnectionActions, + ProjectPermissionSub +} from "@app/ee/services/permission/project-permission"; import { crypto } from "@app/lib/crypto/cryptography"; import { DatabaseErrorCode } from "@app/lib/error-codes"; import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; @@ -27,9 +32,8 @@ import { TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM, validateAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; -import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service"; -import { githubRadarConnectionService } from "@app/services/app-connection/github-radar/github-radar-connection-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; import { ValidateOnePassConnectionCredentialsSchema } from "./1password"; import { onePassConnectionService } from "./1password/1password-connection-service"; @@ -41,10 +45,13 @@ import { TAppConnectionConfig, TAppConnectionRaw, TCreateAppConnectionDTO, + TGetAppConnectionByNameDTO, TUpdateAppConnectionDTO, - TValidateAppConnectionCredentialsSchema + TValidateAppConnectionCredentialsSchema, + TValidateAppConnectionUsageByIdDTO } from "./app-connection-types"; import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0"; +import { auth0ConnectionService } from "./auth0/auth0-connection-service"; import { ValidateAwsConnectionCredentialsSchema } from "./aws"; import { awsConnectionService } from "./aws/aws-connection-service"; import { ValidateAzureADCSConnectionCredentialsSchema } from "./azure-adcs/azure-adcs-connection-schemas"; @@ -73,6 +80,7 @@ import { gcpConnectionService } from "./gcp/gcp-connection-service"; import { ValidateGitHubConnectionCredentialsSchema } from "./github"; import { githubConnectionService } from "./github/github-connection-service"; import { ValidateGitHubRadarConnectionCredentialsSchema } from "./github-radar"; +import { githubRadarConnectionService } from "./github-radar/github-radar-connection-service"; import { ValidateGitLabConnectionCredentialsSchema } from "./gitlab"; import { gitlabConnectionService } from "./gitlab/gitlab-connection-service"; import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault"; @@ -108,13 +116,14 @@ import { zabbixConnectionService } from "./zabbix/zabbix-connection-service"; export type TAppConnectionServiceFactoryDep = { appConnectionDAL: TAppConnectionDALFactory; - permissionService: Pick; + permissionService: Pick; kmsService: Pick; licenseService: Pick; gatewayService: Pick; gatewayV2Service: Pick; gatewayDAL: Pick; gatewayV2DAL: Pick; + projectDAL: Pick; }; export type TAppConnectionServiceFactory = ReturnType; @@ -168,29 +177,64 @@ export const appConnectionServiceFactory = ({ gatewayService, gatewayV2Service, gatewayDAL, - gatewayV2DAL + gatewayV2DAL, + projectDAL }: TAppConnectionServiceFactoryDep) => { - const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => { - const { permission } = await permissionService.getOrgPermission( - actor.type, - actor.id, - actor.orgId, - actor.authMethod, - actor.orgId - ); + const listAppConnections = async (actor: OrgServiceActor, app?: AppConnection, projectId?: string) => { + let appConnections: TAppConnections[]; - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Read, - OrgPermissionSubjects.AppConnections - ); + if (projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - const appConnections = await appConnectionDAL.find( - app - ? { orgId: actor.orgId, app } - : { - orgId: actor.orgId - } - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Read, + ProjectPermissionSub.AppConnections + ); + + appConnections = ( + await appConnectionDAL.findWithProjectDetails({ + projectId, + ...(app ? { app } : {}) + }) + ).filter((appConnection) => + permission.can( + ProjectPermissionAppConnectionActions.Read, + subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id }) + ) + ); + } else { + const { permission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + actor.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionAppConnectionActions.Read, + OrgPermissionSubjects.AppConnections + ); + + appConnections = ( + await appConnectionDAL.findWithProjectDetails({ + orgId: actor.orgId, + ...(app ? { app } : {}) + }) + ).filter((appConnection) => + permission.can( + OrgPermissionAppConnectionActions.Read, + subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id }) + ) + ); + } return Promise.all( appConnections @@ -204,18 +248,34 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); - const { permission } = await permissionService.getOrgPermission( - actor.type, - actor.id, - actor.orgId, - actor.authMethod, - appConnection.orgId - ); + if (appConnection.projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId: appConnection.projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Read, - OrgPermissionSubjects.AppConnections - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Read, + subject(ProjectPermissionSub.AppConnections, { connectionId }) + ); + } else { + const { permission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + appConnection.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionAppConnectionActions.Read, + subject(OrgPermissionSubjects.AppConnections, { connectionId }) + ); + } if (appConnection.app !== app) throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` }); @@ -223,24 +283,49 @@ export const appConnectionServiceFactory = ({ return decryptAppConnection(appConnection, kmsService); }; - const findAppConnectionByName = async (app: AppConnection, connectionName: string, actor: OrgServiceActor) => { - const appConnection = await appConnectionDAL.findOne({ name: connectionName, orgId: actor.orgId }); + const findAppConnectionByName = async ( + app: AppConnection, + { connectionName, projectId }: TGetAppConnectionByNameDTO, + actor: OrgServiceActor + ) => { + const appConnection = await appConnectionDAL.findOne({ + name: connectionName, + ...(projectId ? { projectId } : { orgId: actor.orgId, projectId: null }) + }); if (!appConnection) - throw new NotFoundError({ message: `Could not find App Connection with name ${connectionName}` }); + throw new NotFoundError({ + message: `Could not find App Connection with name ${connectionName} in ${projectId ? "project" : "organization"} scope.` + }); - const { permission } = await permissionService.getOrgPermission( - actor.type, - actor.id, - actor.orgId, - actor.authMethod, - appConnection.orgId - ); + if (appConnection.projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId: appConnection.projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Read, - OrgPermissionSubjects.AppConnections - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Read, + subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id }) + ); + } else { + const { permission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + appConnection.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionAppConnectionActions.Read, + subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id }) + ); + } if (appConnection.app !== app) throw new BadRequestError({ message: `App Connection with name ${connectionName} is not for App "${app}"` }); @@ -249,10 +334,10 @@ export const appConnectionServiceFactory = ({ }; const createAppConnection = async ( - { method, app, credentials, gatewayId, ...params }: TCreateAppConnectionDTO, + { method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO, actor: OrgServiceActor ) => { - const { permission } = await permissionService.getOrgPermission( + const { permission: orgPermission } = await permissionService.getOrgPermission( actor.type, actor.id, actor.orgId, @@ -260,13 +345,33 @@ export const appConnectionServiceFactory = ({ actor.orgId ); - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Create, - OrgPermissionSubjects.AppConnections - ); + if (projectId) { + const project = await projectDAL.findProjectById(projectId); + + if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` }); + + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Create, + ProjectPermissionSub.AppConnections + ); + } else { + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionAppConnectionActions.Create, + OrgPermissionSubjects.AppConnections + ); + } if (gatewayId) { - ForbiddenError.from(permission).throwUnlessCan( + ForbiddenError.from(orgPermission).throwUnlessCan( OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway ); @@ -304,7 +409,8 @@ export const appConnectionServiceFactory = ({ const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: connectionCredentials, orgId: actor.orgId, - kmsService + kmsService, + projectId }); return appConnectionDAL.create({ @@ -313,6 +419,7 @@ export const appConnectionServiceFactory = ({ method, app, gatewayId, + projectId, ...params }); }; @@ -365,7 +472,7 @@ export const appConnectionServiceFactory = ({ "Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections." ); - const { permission } = await permissionService.getOrgPermission( + const { permission: orgPermission } = await permissionService.getOrgPermission( actor.type, actor.id, actor.orgId, @@ -373,13 +480,29 @@ export const appConnectionServiceFactory = ({ appConnection.orgId ); - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Edit, - OrgPermissionSubjects.AppConnections - ); + if (appConnection.projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId: appConnection.projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - if (gatewayId !== appConnection.gatewayId) { ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Edit, + subject(ProjectPermissionSub.AppConnections, { connectionId }) + ); + } else { + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionAppConnectionActions.Edit, + subject(OrgPermissionSubjects.AppConnections, { connectionId }) + ); + } + + if (gatewayId !== undefined && gatewayId !== appConnection.gatewayId) { + ForbiddenError.from(orgPermission).throwUnlessCan( OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway ); @@ -441,7 +564,8 @@ export const appConnectionServiceFactory = ({ ? await encryptAppConnectionCredentials({ credentials: connectionCredentials, orgId: actor.orgId, - kmsService + kmsService, + projectId: appConnection.projectId }) : undefined; @@ -491,18 +615,34 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); - const { permission } = await permissionService.getOrgPermission( - actor.type, - actor.id, - actor.orgId, - actor.authMethod, - appConnection.orgId - ); + if (appConnection.projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId: appConnection.projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionAppConnectionActions.Delete, - OrgPermissionSubjects.AppConnections - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Delete, + subject(ProjectPermissionSub.AppConnections, { connectionId }) + ); + } else { + const { permission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + appConnection.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionAppConnectionActions.Delete, + subject(OrgPermissionSubjects.AppConnections, { connectionId }) + ); + } if (appConnection.app !== app) throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` }); @@ -544,18 +684,34 @@ export const appConnectionServiceFactory = ({ "Failed to connect app due to plan restriction. Upgrade plan to access enterprise app connections." ); - const { permission: orgPermission } = await permissionService.getOrgPermission( - actor.type, - actor.id, - appConnection.orgId, - actor.authMethod, - actor.orgId - ); + if (appConnection.projectId) { + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId: appConnection.projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); - ForbiddenError.from(orgPermission).throwUnlessCan( - OrgPermissionAppConnectionActions.Connect, - subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id }) - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Connect, + subject(ProjectPermissionSub.AppConnections, { connectionId }) + ); + } else { + const { permission: orgPermission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + appConnection.orgId, + actor.authMethod, + actor.orgId + ); + + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionAppConnectionActions.Connect, + subject(OrgPermissionSubjects.AppConnections, { connectionId }) + ); + } if (appConnection.app !== app) throw new BadRequestError({ @@ -569,7 +725,23 @@ export const appConnectionServiceFactory = ({ return connection as T; }; - const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor) => { + const validateAppConnectionUsageById = async ( + app: AppConnection, + { connectionId, projectId }: TValidateAppConnectionUsageByIdDTO, + actor: OrgServiceActor + ) => { + const appConnection = await connectAppConnectionById(app, connectionId, actor); + + if (appConnection.projectId && appConnection.projectId !== projectId) { + throw new BadRequestError({ + message: `You cannot connect project App Connection with ID "${appConnection.id}" from project with ID "${appConnection.projectId}" to project with ID "${projectId}"` + }); + } + + return appConnection; + }; + + const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId?: string) => { const { permission: orgPermission } = await permissionService.getOrgPermission( actor.type, actor.id, @@ -578,28 +750,89 @@ export const appConnectionServiceFactory = ({ actor.orgId ); - const appConnections = await appConnectionDAL.find({ app, orgId: actor.orgId }); + let availableProjectConnections: TAppConnections[] = []; - const availableConnections = appConnections.filter((connection) => + if (projectId) { + const project = await projectDAL.findProjectById(projectId); + + if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` }); + + const { permission: projectPermission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + projectId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.Any + }); + + ForbiddenError.from(projectPermission).throwUnlessCan( + ProjectPermissionAppConnectionActions.Connect, + ProjectPermissionSub.AppConnections + ); + + const projectAppConnections = await appConnectionDAL.find({ app, projectId }); + + availableProjectConnections = projectAppConnections.filter((connection) => + projectPermission.can( + ProjectPermissionAppConnectionActions.Connect, + subject(ProjectPermissionSub.AppConnections, { connectionId: connection.id }) + ) + ); + } + + const orgAppConnections = await appConnectionDAL.find({ app, orgId: actor.orgId, projectId: null }); + + const availableOrgConnections = orgAppConnections.filter((connection) => orgPermission.can( OrgPermissionAppConnectionActions.Connect, subject(OrgPermissionSubjects.AppConnections, { connectionId: connection.id }) ) ); - return availableConnections as Omit[]; + return [...availableOrgConnections, ...availableProjectConnections].sort((a, b) => + a.name.toLowerCase().localeCompare(b.name.toLowerCase()) + ) as Omit[]; + }; + + const findAppConnectionUsageById = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => { + const appConnection = await appConnectionDAL.findById(connectionId); + + if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); + + const { permission } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + appConnection.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionAppConnectionActions.Read, + OrgPermissionSubjects.AppConnections + ); + + if (appConnection.app !== app) + throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` }); + + const projectUsage = await appConnectionDAL.findAppConnectionUsageById(connectionId); + + return projectUsage; }; return { listAppConnectionOptions, - listAppConnectionsByOrg, + listAppConnections, findAppConnectionById, findAppConnectionByName, createAppConnection, updateAppConnection, deleteAppConnection, connectAppConnectionById, + validateAppConnectionUsageById, listAvailableAppConnectionsForUser, + findAppConnectionUsageById, github: githubConnectionService(connectAppConnectionById, gatewayService, gatewayV2Service), githubRadar: githubRadarConnectionService(connectAppConnectionById), gcp: gcpConnectionService(connectAppConnectionById), diff --git a/backend/src/services/app-connection/app-connection-types.ts b/backend/src/services/app-connection/app-connection-types.ts index e4af79926..600438fc9 100644 --- a/backend/src/services/app-connection/app-connection-types.ts +++ b/backend/src/services/app-connection/app-connection-types.ts @@ -316,13 +316,23 @@ export type TSqlConnectionInput = export type TCreateAppConnectionDTO = Pick< TAppConnectionInput, - "credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId" + "credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId" | "projectId" >; -export type TUpdateAppConnectionDTO = Partial> & { +export type TUpdateAppConnectionDTO = Partial> & { connectionId: string; }; +export type TGetAppConnectionByNameDTO = { + connectionName: string; + projectId?: string; +}; + +export type TValidateAppConnectionUsageByIdDTO = { + connectionId: string; + projectId: string; +}; + export type TAppConnectionConfig = | TAwsConnectionConfig | TGitHubConnectionConfig diff --git a/backend/src/services/app-connection/auth0/auth0-connection-fns.ts b/backend/src/services/app-connection/auth0/auth0-connection-fns.ts index de4faf683..944b1f69a 100644 --- a/backend/src/services/app-connection/auth0/auth0-connection-fns.ts +++ b/backend/src/services/app-connection/auth0/auth0-connection-fns.ts @@ -51,7 +51,7 @@ const authorizeAuth0Connection = async ({ }; export const getAuth0ConnectionAccessToken = async ( - { id, orgId, credentials }: TAuth0Connection, + { id, orgId, credentials, projectId }: TAuth0Connection, appConnectionDAL: Pick, kmsService: Pick ) => { @@ -72,7 +72,8 @@ export const getAuth0ConnectionAccessToken = async ( const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: updatedCredentials, orgId, - kmsService + kmsService, + projectId }); await appConnectionDAL.updateById(id, { encryptedCredentials }); diff --git a/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-fns.ts b/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-fns.ts index 552bd89f5..5e86f6740 100644 --- a/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-fns.ts +++ b/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-fns.ts @@ -352,7 +352,8 @@ export const getAzureADCSConnectionCredentials = async ( const credentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as { username: string; password: string; diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index 2614cfd12..22cec0ae7 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -57,6 +57,7 @@ export const getAzureConnectionAccessToken = async ( const credentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, + projectId: appConnection.projectId, encryptedCredentials: appConnection.encryptedCredentials })) as TAzureClientSecretsConnectionCredentials; @@ -93,6 +94,7 @@ export const getAzureConnectionAccessToken = async ( const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: updatedCredentials, orgId: appConnection.orgId, + projectId: appConnection.projectId, kmsService }); @@ -102,6 +104,7 @@ export const getAzureConnectionAccessToken = async ( case AzureClientSecretsConnectionMethod.ClientSecret: const accessTokenCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, + projectId: appConnection.projectId, kmsService, encryptedCredentials: appConnection.encryptedCredentials })) as TAzureClientSecretsConnectionClientSecretCredentials; @@ -129,6 +132,7 @@ export const getAzureConnectionAccessToken = async ( const encryptedClientCredentials = await encryptAppConnectionCredentials({ credentials: updatedClientCredentials, orgId: appConnection.orgId, + projectId: appConnection.projectId, kmsService }); diff --git a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts index a3a9f10bd..0bd2188ac 100644 --- a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts +++ b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts @@ -70,7 +70,8 @@ export const getAzureDevopsConnection = async ( const oauthCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as TAzureDevOpsConnectionCredentials; if (!("refreshToken" in oauthCredentials)) { @@ -100,7 +101,8 @@ export const getAzureDevopsConnection = async ( const encryptedOAuthCredentials = await encryptAppConnectionCredentials({ credentials: updatedOAuthCredentials, orgId: appConnection.orgId, - kmsService + kmsService, + projectId: appConnection.projectId }); await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials }); @@ -111,7 +113,8 @@ export const getAzureDevopsConnection = async ( const accessTokenCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as { accessToken: string }; if (!("accessToken" in accessTokenCredentials)) { @@ -124,7 +127,8 @@ export const getAzureDevopsConnection = async ( const clientSecretCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as TAzureDevOpsConnectionClientSecretCredentials; const { accessToken, expiresAt, clientId, clientSecret, tenantId: clientTenantId } = clientSecretCredentials; @@ -153,7 +157,8 @@ export const getAzureDevopsConnection = async ( const encryptedClientCredentials = await encryptAppConnectionCredentials({ credentials: updatedClientCredentials, orgId: appConnection.orgId, - kmsService + kmsService, + projectId: appConnection.projectId }); await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials }); diff --git a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts index d6a260050..cd3583800 100644 --- a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts +++ b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts @@ -58,7 +58,8 @@ export const getAzureConnectionAccessToken = async ( const oauthCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as TAzureKeyVaultConnectionCredentials; const { data } = await request.post( @@ -82,7 +83,8 @@ export const getAzureConnectionAccessToken = async ( const encryptedOAuthCredentials = await encryptAppConnectionCredentials({ credentials: updatedOAuthCredentials, orgId: appConnection.orgId, - kmsService + kmsService, + projectId: appConnection.projectId }); await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials }); @@ -95,7 +97,8 @@ export const getAzureConnectionAccessToken = async ( const clientSecretCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, kmsService, - encryptedCredentials: appConnection.encryptedCredentials + encryptedCredentials: appConnection.encryptedCredentials, + projectId: appConnection.projectId })) as TAzureKeyVaultConnectionClientSecretCredentials; const { accessToken, expiresAt, clientId, clientSecret, tenantId } = clientSecretCredentials; @@ -124,7 +127,8 @@ export const getAzureConnectionAccessToken = async ( const encryptedClientCredentials = await encryptAppConnectionCredentials({ credentials: updatedClientCredentials, orgId: appConnection.orgId, - kmsService + kmsService, + projectId: appConnection.projectId }); await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials }); diff --git a/backend/src/services/app-connection/camunda/camunda-connection-fns.ts b/backend/src/services/app-connection/camunda/camunda-connection-fns.ts index 91a033c0e..b764da336 100644 --- a/backend/src/services/app-connection/camunda/camunda-connection-fns.ts +++ b/backend/src/services/app-connection/camunda/camunda-connection-fns.ts @@ -40,7 +40,7 @@ const authorizeCamundaConnection = async ({ }; export const getCamundaConnectionAccessToken = async ( - { id, orgId, credentials }: TCamundaConnection, + { id, orgId, credentials, projectId }: TCamundaConnection, appConnectionDAL: Pick, kmsService: Pick ) => { @@ -61,7 +61,8 @@ export const getCamundaConnectionAccessToken = async ( const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: updatedCredentials, orgId, - kmsService + kmsService, + projectId }); await appConnectionDAL.updateById(id, { encryptedCredentials }); diff --git a/backend/src/services/app-connection/databricks/databricks-connection-fns.ts b/backend/src/services/app-connection/databricks/databricks-connection-fns.ts index 8912ad936..a9128ec51 100644 --- a/backend/src/services/app-connection/databricks/databricks-connection-fns.ts +++ b/backend/src/services/app-connection/databricks/databricks-connection-fns.ts @@ -47,7 +47,7 @@ const authorizeDatabricksConnection = async ({ }; export const getDatabricksConnectionAccessToken = async ( - { id, orgId, credentials }: TDatabricksConnection, + { id, orgId, credentials, projectId }: TDatabricksConnection, appConnectionDAL: Pick, kmsService: Pick ) => { @@ -68,7 +68,8 @@ export const getDatabricksConnectionAccessToken = async ( const encryptedCredentials = await encryptAppConnectionCredentials({ credentials: updatedCredentials, orgId, - kmsService + kmsService, + projectId }); await appConnectionDAL.updateById(id, { encryptedCredentials }); diff --git a/backend/src/services/app-connection/gitlab/gitlab-connection-fns.ts b/backend/src/services/app-connection/gitlab/gitlab-connection-fns.ts index cb4e27e94..9499d6bf2 100644 --- a/backend/src/services/app-connection/gitlab/gitlab-connection-fns.ts +++ b/backend/src/services/app-connection/gitlab/gitlab-connection-fns.ts @@ -64,6 +64,7 @@ export const refreshGitLabToken = async ( refreshToken: string, appId: string, orgId: string, + projectId: string | undefined | null, appConnectionDAL: Pick, kmsService: Pick, instanceUrl?: string @@ -105,7 +106,8 @@ export const refreshGitLabToken = async ( expiresAt }, orgId, - kmsService + kmsService, + projectId }); await appConnectionDAL.updateById(appId, { encryptedCredentials }); @@ -238,6 +240,7 @@ export const getGitLabConnectionClient = async ( appConnection.credentials.refreshToken, appConnection.id, appConnection.orgId, + appConnection.projectId, appConnectionDAL, kmsService, appConnection.credentials.instanceUrl @@ -273,6 +276,7 @@ export const listGitLabProjects = async ({ appConnection.credentials.refreshToken, appConnection.id, appConnection.orgId, + appConnection.projectId, appConnectionDAL, kmsService, appConnection.credentials.instanceUrl @@ -341,6 +345,7 @@ export const listGitLabGroups = async ({ appConnection.credentials.refreshToken, appConnection.id, appConnection.orgId, + appConnection.projectId, appConnectionDAL, kmsService, appConnection.credentials.instanceUrl diff --git a/backend/src/services/app-connection/heroku/heroku-connection-fns.ts b/backend/src/services/app-connection/heroku/heroku-connection-fns.ts index 5a8533c83..adbc5cd2b 100644 --- a/backend/src/services/app-connection/heroku/heroku-connection-fns.ts +++ b/backend/src/services/app-connection/heroku/heroku-connection-fns.ts @@ -36,6 +36,7 @@ export const refreshHerokuToken = async ( refreshToken: string, appId: string, orgId: string, + projectId: string | null | undefined, appConnectionDAL: Pick, kmsService: Pick ): Promise => { @@ -64,7 +65,8 @@ export const refreshHerokuToken = async ( expiresAt: new Date(Date.now() + data.expires_in * 1000 - 60000) }, orgId, - kmsService + kmsService, + projectId }); await appConnectionDAL.updateById(appId, { encryptedCredentials }); @@ -186,6 +188,7 @@ export const listHerokuApps = async ({ appConnection.credentials.refreshToken, appConnection.id, appConnection.orgId, + appConnection.projectId, appConnectionDAL, kmsService ); diff --git a/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts index 830378ca8..b725e5584 100644 --- a/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts @@ -42,10 +42,10 @@ import { route53DeleteTxtRecord, route53InsertTxtRecord } from "./dns-providers/ type TAcmeCertificateAuthorityFnsDeps = { appConnectionDAL: Pick; - appConnectionService: Pick; + appConnectionService: Pick; certificateAuthorityDAL: Pick< TCertificateAuthorityDALFactory, - "create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" + "create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" | "findById" >; externalCertificateAuthorityDAL: Pick; certificateDAL: Pick; @@ -152,7 +152,11 @@ export const AcmeCertificateAuthorityFns = ({ } // validates permission to connect - await appConnectionService.connectAppConnectionById(appConnection.app as AppConnection, dnsAppConnectionId, actor); + await appConnectionService.validateAppConnectionUsageById( + appConnection.app as AppConnection, + { connectionId: dnsAppConnectionId, projectId }, + actor + ); const caEntity = await certificateAuthorityDAL.transaction(async (tx) => { try { @@ -242,10 +246,16 @@ export const AcmeCertificateAuthorityFns = ({ }); } + const ca = await certificateAuthorityDAL.findById(id); + + if (!ca) { + throw new NotFoundError({ message: `Could not find Certificate Authority with ID "${id}"` }); + } + // validates permission to connect - await appConnectionService.connectAppConnectionById( + await appConnectionService.validateAppConnectionUsageById( appConnection.app as AppConnection, - dnsAppConnectionId, + { connectionId: dnsAppConnectionId, projectId: ca.projectId }, actor ); diff --git a/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts b/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts index 0e2619a27..25c5590eb 100644 --- a/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts @@ -41,10 +41,10 @@ import { type TAzureAdCsCertificateAuthorityFnsDeps = { appConnectionDAL: Pick; - appConnectionService: Pick; + appConnectionService: Pick; certificateAuthorityDAL: Pick< TCertificateAuthorityDALFactory, - "create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" + "create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" | "findById" >; externalCertificateAuthorityDAL: Pick; certificateDAL: Pick; @@ -621,9 +621,9 @@ export const AzureAdCsCertificateAuthorityFns = ({ }); } - await appConnectionService.connectAppConnectionById( + await appConnectionService.validateAppConnectionUsageById( appConnection.app as AppConnection, - azureAdcsConnectionId, + { connectionId: azureAdcsConnectionId, projectId }, actor ); @@ -705,9 +705,15 @@ export const AzureAdCsCertificateAuthorityFns = ({ }); } - await appConnectionService.connectAppConnectionById( + const ca = await certificateAuthorityDAL.findById(id); + + if (!ca) { + throw new NotFoundError({ message: `Could not find Certificate Authority with ID "${id}"` }); + } + + await appConnectionService.validateAppConnectionUsageById( appConnection.app as AppConnection, - azureAdcsConnectionId, + { connectionId: azureAdcsConnectionId, projectId: ca.projectId }, actor ); diff --git a/backend/src/services/certificate-authority/certificate-authority-queue.ts b/backend/src/services/certificate-authority/certificate-authority-queue.ts index 0e015da03..21f7b71e6 100644 --- a/backend/src/services/certificate-authority/certificate-authority-queue.ts +++ b/backend/src/services/certificate-authority/certificate-authority-queue.ts @@ -35,7 +35,7 @@ import { type TCertificateAuthorityQueueFactoryDep = { certificateAuthorityDAL: TCertificateAuthorityDALFactory; appConnectionDAL: Pick; - appConnectionService: Pick; + appConnectionService: Pick; externalCertificateAuthorityDAL: Pick; keyStore: Pick; certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory; diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 6cf55fc52..02c5a488a 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -43,7 +43,7 @@ import { TCreateInternalCertificateAuthorityDTO } from "./internal/internal-cert type TCertificateAuthorityServiceFactoryDep = { appConnectionDAL: Pick; - appConnectionService: Pick; + appConnectionService: Pick; certificateAuthorityDAL: Pick< TCertificateAuthorityDALFactory, | "transaction" diff --git a/backend/src/services/external-migration/external-migration-fns/import.ts b/backend/src/services/external-migration/external-migration-fns/import.ts index 5728bf1c0..62888c5bf 100644 --- a/backend/src/services/external-migration/external-migration-fns/import.ts +++ b/backend/src/services/external-migration/external-migration-fns/import.ts @@ -55,7 +55,7 @@ export const importDataIntoInfisicalFn = async ({ actorId, actorOrgId, actorAuthMethod, - workspaceName: project.name, + projectName: project.name, createDefaultEnvs: false, tx }) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 2766db379..d64977f8b 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -399,6 +399,7 @@ export const projectDALFactory = (db: TDbClient) => { name?: string; sortBy?: SearchProjectSortBy; sortDir?: SortDirection; + projectIds?: string[]; }) => { const { limit = 20, offset = 0, sortBy = SearchProjectSortBy.NAME, sortDir = SortDirection.ASC } = dto; @@ -454,6 +455,11 @@ export const projectDALFactory = (db: TDbClient) => { if (dto.name) { void query.whereILike(`${TableName.Project}.name`, `%${dto.name}%`); } + + if (dto.projectIds?.length) { + void query.whereIn(`${TableName.Project}.id`, dto.projectIds); + } + const docs = await query; return { docs, totalCount: Number(docs?.[0]?.count ?? 0) }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index d59f20bc6..57539f002 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -237,8 +237,8 @@ export const projectServiceFactory = ({ actorId, actorOrgId, actorAuthMethod, - workspaceName, - workspaceDescription, + projectName: workspaceName, + projectDescription: workspaceDescription, slug: projectSlug, kmsKeyId, tx: trx, @@ -254,7 +254,13 @@ export const projectServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); + + if ( + permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace) && + permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Project) + ) { + throw new ForbiddenRequestError({ message: "You don't have permission to create a project" }); + } const results = await (trx || projectDAL).transaction(async (tx) => { await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.CreateProject(organization.id)]); @@ -591,7 +597,8 @@ export const projectServiceFactory = ({ secretSharing: update.secretSharing, defaultProduct: update.defaultProduct, showSnapshotsLegacy: update.showSnapshotsLegacy, - secretDetectionIgnoreValues: update.secretDetectionIgnoreValues + secretDetectionIgnoreValues: update.secretDetectionIgnoreValues, + pitVersionLimit: update.pitVersionLimit }); return updatedProject; @@ -684,19 +691,21 @@ export const projectServiceFactory = ({ actorOrgId, actorAuthMethod, auditLogsRetentionDays, - workspaceSlug + filter }: TUpdateAuditLogsRetentionDTO) => { - const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId); + const project = await projectDAL.findProjectByFilter(filter); + const projectId = project.id; + if (!project) { throw new NotFoundError({ - message: `Project with slug '${workspaceSlug}' not found` + message: `Project not found` }); } const { hasRole } = await permissionService.getProjectPermission({ actor, actorId, - projectId: project.id, + projectId, actorAuthMethod, actorOrgId, actionProjectType: ActionProjectType.Any @@ -1806,7 +1815,8 @@ export const projectServiceFactory = ({ limit, type, orderBy, - orderDirection + orderDirection, + projectIds }: TSearchProjectsDTO) => { // check user belong to org await permissionService.getOrgPermission( @@ -1822,6 +1832,7 @@ export const projectServiceFactory = ({ offset, name, type, + projectIds, orgId: permission.orgId, actor: permission.type, actorId: permission.id, diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index ceef78f6a..74c7e95f4 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -42,12 +42,13 @@ export type TCreateProjectDTO = { actorAuthMethod: ActorAuthMethod; actorId: string; actorOrgId?: string; - workspaceName: string; - workspaceDescription?: string; + projectName: string; + projectDescription?: string; slug?: string; kmsKeyId?: string; createDefaultEnvs?: boolean; template?: string; + pitVersionLimit?: number; tx?: Knex; type?: ProjectType; }; @@ -78,7 +79,7 @@ export type TUpdateProjectVersionLimitDTO = { export type TUpdateAuditLogsRetentionDTO = { auditLogsRetentionDays: number; - workspaceSlug: string; + filter: Filter; } & Omit; export type TUpdateProjectNameDTO = { @@ -90,6 +91,7 @@ export type TUpdateProjectDTO = { update: { name?: string; description?: string; + pitVersionLimit?: number; autoCapitalization?: boolean; hasDeleteProtection?: boolean; defaultProduct?: ProjectType; @@ -221,6 +223,7 @@ export type TSearchProjectsDTO = { limit?: number; offset?: number; orderBy?: SearchProjectSortBy; + projectIds?: string[]; orderDirection?: SortDirection; }; diff --git a/backend/src/services/secret-sync/gitlab/gitlab-sync-fns.ts b/backend/src/services/secret-sync/gitlab/gitlab-sync-fns.ts index 3c152d853..7bf45d47c 100644 --- a/backend/src/services/secret-sync/gitlab/gitlab-sync-fns.ts +++ b/backend/src/services/secret-sync/gitlab/gitlab-sync-fns.ts @@ -53,6 +53,7 @@ const getValidAccessToken = async ( connection.credentials.refreshToken, connection.id, connection.orgId, + connection.projectId, appConnectionDAL, kmsService, connection.credentials.instanceUrl diff --git a/backend/src/services/secret-sync/heroku/heroku-sync-fns.ts b/backend/src/services/secret-sync/heroku/heroku-sync-fns.ts index d2f0817db..5f2375979 100644 --- a/backend/src/services/secret-sync/heroku/heroku-sync-fns.ts +++ b/backend/src/services/secret-sync/heroku/heroku-sync-fns.ts @@ -32,6 +32,7 @@ const getValidAuthToken = async ( connection.credentials.refreshToken, connection.id, connection.orgId, + connection.projectId, appConnectionDAL, kmsService ); diff --git a/backend/src/services/secret-sync/secret-sync-dal.ts b/backend/src/services/secret-sync/secret-sync-dal.ts index e50593f10..57c6581ce 100644 --- a/backend/src/services/secret-sync/secret-sync-dal.ts +++ b/backend/src/services/secret-sync/secret-sync-dal.ts @@ -31,6 +31,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"), db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"), db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"), + db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"), db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"), db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"), db @@ -67,6 +68,7 @@ const expandSecretSync = ( connectionVersion, connectionIsPlatformManagedCredentials, connectionGatewayId, + connectionProjectId, ...el } = secretSync; @@ -86,7 +88,8 @@ const expandSecretSync = ( updatedAt: connectionUpdatedAt, version: connectionVersion, isPlatformManagedCredentials: connectionIsPlatformManagedCredentials, - gatewayId: connectionGatewayId + gatewayId: connectionGatewayId, + projectId: connectionProjectId }, folder: folder ? { diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index f75d84eda..a31bb202d 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -484,13 +484,14 @@ export const secretSyncQueueFactory = ({ try { const { - connection: { orgId, encryptedCredentials } + connection: { orgId, encryptedCredentials, projectId } } = secretSync; const credentials = await decryptAppConnectionCredentials({ orgId, encryptedCredentials, - kmsService + kmsService, + projectId }); const secretSyncWithCredentials = { @@ -624,13 +625,14 @@ export const secretSyncQueueFactory = ({ try { const { - connection: { orgId, encryptedCredentials } + connection: { orgId, encryptedCredentials, projectId } } = secretSync; const credentials = await decryptAppConnectionCredentials({ orgId, encryptedCredentials, - kmsService + kmsService, + projectId }); await $importSecrets( @@ -744,13 +746,14 @@ export const secretSyncQueueFactory = ({ try { const { - connection: { orgId, encryptedCredentials } + connection: { orgId, encryptedCredentials, projectId } } = secretSync; const credentials = await decryptAppConnectionCredentials({ orgId, encryptedCredentials, - kmsService + kmsService, + projectId }); const secretMap = await $getInfisicalSecrets(secretSync); diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 3fdb7fea6..ecd7d04a5 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -41,7 +41,7 @@ import { TSecretSyncQueueFactory } from "./secret-sync-queue"; type TSecretSyncServiceFactoryDep = { secretSyncDAL: TSecretSyncDALFactory; secretImportDAL: TSecretImportDALFactory; - appConnectionService: Pick; + appConnectionService: Pick; permissionService: Pick; projectBotService: Pick; folderDAL: Pick; @@ -267,7 +267,11 @@ export const secretSyncServiceFactory = ({ const destinationApp = SECRET_SYNC_CONNECTION_MAP[params.destination]; // validates permission to connect and app is valid for sync destination - await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); + await appConnectionService.validateAppConnectionUsageById( + destinationApp, + { connectionId: params.connectionId, projectId }, + actor + ); try { const secretSync = await secretSyncDAL.create({ @@ -362,7 +366,11 @@ export const secretSyncServiceFactory = ({ const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync]; // validates permission to connect and app is valid for sync destination - await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor); + await appConnectionService.validateAppConnectionUsageById( + destinationApp, + { connectionId: params.connectionId, projectId: secretSync.projectId }, + actor + ); } if ( diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 6bafa3ba6..8758a992e 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -2970,14 +2970,23 @@ export const secretServiceFactory = ({ actor, actorId, actorAuthMethod, - actorOrgId + actorOrgId, + projectId: inputProjectId }: TMoveSecretsDTO) => { - const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + let project; + if (projectSlug) { + project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + } else if (inputProjectId) { + project = await projectDAL.findById(inputProjectId); + } + if (!project) { throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); } + + const projectId = project.id; if (project.version === ProjectVersion.V3) { return secretV2BridgeService.moveSecrets({ sourceEnvironment, @@ -3170,7 +3179,7 @@ export const secretServiceFactory = ({ }); } const destinationFolderPolicy = await secretApprovalPolicyService.getSecretApprovalPolicy( - project.id, + projectId, destinationFolder.environment.slug, destinationFolder.path ); @@ -3257,7 +3266,7 @@ export const secretServiceFactory = ({ } if (locallyUpdatedSecrets.length) { await fnSecretBulkUpdate({ - projectId: project.id, + projectId, folderId: destinationFolder.id, secretVersionDAL, secretDAL, @@ -3300,7 +3309,7 @@ export const secretServiceFactory = ({ const locallyDeletedSecrets = decryptedSourceSecrets.map((el) => ({ ...el, operation: SecretOperations.Delete })); const sourceFolderPolicy = await secretApprovalPolicyService.getSecretApprovalPolicy( - project.id, + projectId, sourceFolder.environment.slug, sourceFolder.path ); diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index cd341a8b5..07f46bf1e 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -534,7 +534,8 @@ export type TSyncSecretsDTO = { }); export type TMoveSecretsDTO = { - projectSlug: string; + projectId?: string; + projectSlug?: string; sourceEnvironment: string; sourceSecretPath: string; destinationEnvironment: string; diff --git a/backend/src/services/telemetry/telemetry-types.ts b/backend/src/services/telemetry/telemetry-types.ts index 7e2027f25..a43a2f746 100644 --- a/backend/src/services/telemetry/telemetry-types.ts +++ b/backend/src/services/telemetry/telemetry-types.ts @@ -46,7 +46,7 @@ export type TSecretModifiedEvent = { properties: { numberOfSecrets: number; environment: string; - workspaceId: string; + projectId: string; secretPath: string; channel?: string; userAgent?: string; diff --git a/docs/api-reference/endpoints/deprecated/environments/create.mdx b/docs/api-reference/endpoints/deprecated/environments/create.mdx new file mode 100644 index 000000000..826dcce3d --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/environments/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/workspace/{workspaceId}/environments" +--- diff --git a/docs/api-reference/endpoints/deprecated/environments/delete.mdx b/docs/api-reference/endpoints/deprecated/environments/delete.mdx new file mode 100644 index 000000000..903e58d2a --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/environments/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/workspace/{workspaceId}/environments/{id}" +--- diff --git a/docs/api-reference/endpoints/deprecated/environments/update.mdx b/docs/api-reference/endpoints/deprecated/environments/update.mdx new file mode 100644 index 000000000..f93968668 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/environments/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/workspace/{workspaceId}/environments/{id}" +--- diff --git a/docs/api-reference/endpoints/deprecated/folders/create.mdx b/docs/api-reference/endpoints/deprecated/folders/create.mdx new file mode 100644 index 000000000..e1ff3004a --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/folders/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/folders" +--- diff --git a/docs/api-reference/endpoints/deprecated/folders/delete.mdx b/docs/api-reference/endpoints/deprecated/folders/delete.mdx new file mode 100644 index 000000000..a106cc2eb --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/folders/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/folders/{folderIdOrName}" +--- diff --git a/docs/api-reference/endpoints/deprecated/folders/get-by-id.mdx b/docs/api-reference/endpoints/deprecated/folders/get-by-id.mdx new file mode 100644 index 000000000..db3c4d0cc --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/folders/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/folders/{id}" +--- diff --git a/docs/api-reference/endpoints/deprecated/folders/list.mdx b/docs/api-reference/endpoints/deprecated/folders/list.mdx new file mode 100644 index 000000000..f40f93273 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/folders/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/folders" +--- diff --git a/docs/api-reference/endpoints/deprecated/folders/update.mdx b/docs/api-reference/endpoints/deprecated/folders/update.mdx new file mode 100644 index 000000000..c54778e94 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/folders/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/folders/{folderId}" +--- diff --git a/docs/api-reference/endpoints/organizations/workspaces.mdx b/docs/api-reference/endpoints/deprecated/organizations/projects.mdx similarity index 100% rename from docs/api-reference/endpoints/organizations/workspaces.mdx rename to docs/api-reference/endpoints/deprecated/organizations/projects.mdx diff --git a/docs/api-reference/endpoints/deprecated/project-groups/create.mdx b/docs/api-reference/endpoints/deprecated/project-groups/create.mdx new file mode 100644 index 000000000..6dd7f1a4f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-groups/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create Project Membership" +openapi: "POST /api/v2/workspace/{projectId}/groups/{groupIdOrName}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-groups/delete.mdx b/docs/api-reference/endpoints/deprecated/project-groups/delete.mdx new file mode 100644 index 000000000..07db40d7f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-groups/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete Project Membership" +openapi: "DELETE /api/v2/workspace/{projectId}/groups/{groupId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-groups/get-by-id.mdx b/docs/api-reference/endpoints/deprecated/project-groups/get-by-id.mdx new file mode 100644 index 000000000..611f39059 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-groups/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Project Membership" +openapi: "GET /api/v2/workspace/{projectId}/groups/{groupId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-groups/list.mdx b/docs/api-reference/endpoints/deprecated/project-groups/list.mdx new file mode 100644 index 000000000..1488fb6ae --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-groups/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List Project Memberships" +openapi: "GET /api/v2/workspace/{projectId}/groups" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-groups/update.mdx b/docs/api-reference/endpoints/deprecated/project-groups/update.mdx new file mode 100644 index 000000000..8b963a1d8 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-groups/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Project Membership" +openapi: "PATCH /api/v2/workspace/{projectId}/groups/{groupId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-identities/add-identity-membership.mdx b/docs/api-reference/endpoints/deprecated/project-identities/add-identity-membership.mdx new file mode 100644 index 000000000..285b1d1c4 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-identities/add-identity-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Create Identity Membership" +openapi: "POST /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-identities/delete-identity-membership.mdx b/docs/api-reference/endpoints/deprecated/project-identities/delete-identity-membership.mdx new file mode 100644 index 000000000..e2b266626 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-identities/delete-identity-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete Identity Membership" +openapi: "DELETE /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-identities/get-by-id.mdx b/docs/api-reference/endpoints/deprecated/project-identities/get-by-id.mdx new file mode 100644 index 000000000..37f4192d7 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-identities/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Identity by ID" +openapi: "GET /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-identities/list-identity-memberships.mdx b/docs/api-reference/endpoints/deprecated/project-identities/list-identity-memberships.mdx new file mode 100644 index 000000000..e5162e693 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-identities/list-identity-memberships.mdx @@ -0,0 +1,4 @@ +--- +title: "List Identity Memberships" +openapi: "GET /api/v2/workspace/{projectId}/identity-memberships" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-identities/update-identity-membership.mdx b/docs/api-reference/endpoints/deprecated/project-identities/update-identity-membership.mdx new file mode 100644 index 000000000..667cf7eb3 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-identities/update-identity-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Identity Membership" +openapi: "PATCH /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-roles/create.mdx b/docs/api-reference/endpoints/deprecated/project-roles/create.mdx new file mode 100644 index 000000000..97570ec36 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-roles/create.mdx @@ -0,0 +1,10 @@ +--- +title: "Create" +openapi: "POST /api/v2/workspace/{projectId}/roles" +--- + + + You can read more about the permissions field in the [permissions + documentation](/internals/permissions). + + diff --git a/docs/api-reference/endpoints/deprecated/project-roles/delete.mdx b/docs/api-reference/endpoints/deprecated/project-roles/delete.mdx new file mode 100644 index 000000000..41edfa7c3 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-roles/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v2/workspace/{projectId}/roles/{roleId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-roles/get-by-slug.mdx b/docs/api-reference/endpoints/deprecated/project-roles/get-by-slug.mdx new file mode 100644 index 000000000..dfc5c582a --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-roles/get-by-slug.mdx @@ -0,0 +1,4 @@ +--- +title: "Get By Slug" +openapi: "GET /api/v2/workspace/{projectId}/roles/slug/{roleSlug}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-roles/list.mdx b/docs/api-reference/endpoints/deprecated/project-roles/list.mdx new file mode 100644 index 000000000..8d8dc10c1 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-roles/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v2/workspace/{projectId}/roles" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-roles/update.mdx b/docs/api-reference/endpoints/deprecated/project-roles/update.mdx new file mode 100644 index 000000000..662d5e617 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-roles/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v2/workspace/{projectId}/roles/{roleId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-users/delete-membership.mdx b/docs/api-reference/endpoints/deprecated/project-users/delete-membership.mdx new file mode 100644 index 000000000..1995e4726 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-users/delete-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete User Membership" +openapi: "DELETE /api/v1/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/project-users/get-by-username.mdx b/docs/api-reference/endpoints/deprecated/project-users/get-by-username.mdx new file mode 100644 index 000000000..ec69d4947 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-users/get-by-username.mdx @@ -0,0 +1,4 @@ +--- +title: "Get By Username" +openapi: "POST /api/v1/workspace/{workspaceId}/memberships/details" +--- diff --git a/docs/api-reference/endpoints/project-users/invite-member-to-workspace.mdx b/docs/api-reference/endpoints/deprecated/project-users/invite-member-to-project.mdx similarity index 100% rename from docs/api-reference/endpoints/project-users/invite-member-to-workspace.mdx rename to docs/api-reference/endpoints/deprecated/project-users/invite-member-to-project.mdx diff --git a/docs/api-reference/endpoints/deprecated/project-users/memberships.mdx b/docs/api-reference/endpoints/deprecated/project-users/memberships.mdx new file mode 100644 index 000000000..3c4735f94 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-users/memberships.mdx @@ -0,0 +1,4 @@ +--- +title: "Get User Memberships" +openapi: "GET /api/v1/workspace/{workspaceId}/memberships" +--- diff --git a/docs/api-reference/endpoints/project-users/remove-member-from-workspace.mdx b/docs/api-reference/endpoints/deprecated/project-users/remove-member-from-project.mdx similarity index 100% rename from docs/api-reference/endpoints/project-users/remove-member-from-workspace.mdx rename to docs/api-reference/endpoints/deprecated/project-users/remove-member-from-project.mdx diff --git a/docs/api-reference/endpoints/deprecated/project-users/update-membership.mdx b/docs/api-reference/endpoints/deprecated/project-users/update-membership.mdx new file mode 100644 index 000000000..9a7aa600f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/project-users/update-membership.mdx @@ -0,0 +1,4 @@ +--- +title: "Update User Membership" +openapi: "PATCH /api/v1/workspace/{workspaceId}/memberships/{membershipId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/create-workspace.mdx b/docs/api-reference/endpoints/deprecated/projects/create-project.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/create-workspace.mdx rename to docs/api-reference/endpoints/deprecated/projects/create-project.mdx diff --git a/docs/api-reference/endpoints/workspaces/delete-workspace.mdx b/docs/api-reference/endpoints/deprecated/projects/delete-project.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/delete-workspace.mdx rename to docs/api-reference/endpoints/deprecated/projects/delete-project.mdx diff --git a/docs/api-reference/endpoints/workspaces/get-workspace-by-slug.mdx b/docs/api-reference/endpoints/deprecated/projects/get-project-by-slug.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/get-workspace-by-slug.mdx rename to docs/api-reference/endpoints/deprecated/projects/get-project-by-slug.mdx diff --git a/docs/api-reference/endpoints/workspaces/get-workspace.mdx b/docs/api-reference/endpoints/deprecated/projects/get-project.mdx similarity index 94% rename from docs/api-reference/endpoints/workspaces/get-workspace.mdx rename to docs/api-reference/endpoints/deprecated/projects/get-project.mdx index edd0a0276..a27d41b43 100644 --- a/docs/api-reference/endpoints/workspaces/get-workspace.mdx +++ b/docs/api-reference/endpoints/deprecated/projects/get-project.mdx @@ -1,4 +1,4 @@ --- title: "Get Project" openapi: "GET /api/v1/workspace/{workspaceId}" ---- \ No newline at end of file +--- diff --git a/docs/api-reference/endpoints/workspaces/workspace-key.mdx b/docs/api-reference/endpoints/deprecated/projects/project-key.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/workspace-key.mdx rename to docs/api-reference/endpoints/deprecated/projects/project-key.mdx diff --git a/docs/api-reference/endpoints/workspaces/update-workspace.mdx b/docs/api-reference/endpoints/deprecated/projects/project-workspace.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/update-workspace.mdx rename to docs/api-reference/endpoints/deprecated/projects/project-workspace.mdx diff --git a/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx b/docs/api-reference/endpoints/deprecated/projects/rollback-snapshot.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx rename to docs/api-reference/endpoints/deprecated/projects/rollback-snapshot.mdx diff --git a/docs/api-reference/endpoints/workspaces/secret-snapshots.mdx b/docs/api-reference/endpoints/deprecated/projects/secret-snapshots.mdx similarity index 100% rename from docs/api-reference/endpoints/workspaces/secret-snapshots.mdx rename to docs/api-reference/endpoints/deprecated/projects/secret-snapshots.mdx diff --git a/docs/api-reference/endpoints/deprecated/projects/update-project.mdx b/docs/api-reference/endpoints/deprecated/projects/update-project.mdx new file mode 100644 index 000000000..699e3e3af --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/projects/update-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Project" +openapi: "PATCH /api/v1/workspace/{workspaceId}" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/deprecated/secret-imports/create.mdx b/docs/api-reference/endpoints/deprecated/secret-imports/create.mdx new file mode 100644 index 000000000..3abfb320f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-imports/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/secret-imports" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-imports/delete.mdx b/docs/api-reference/endpoints/deprecated/secret-imports/delete.mdx new file mode 100644 index 000000000..cfa5960b1 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-imports/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/secret-imports/{secretImportId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-imports/list.mdx b/docs/api-reference/endpoints/deprecated/secret-imports/list.mdx new file mode 100644 index 000000000..580d4be8d --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-imports/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/secret-imports" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-imports/update.mdx b/docs/api-reference/endpoints/deprecated/secret-imports/update.mdx new file mode 100644 index 000000000..f21133223 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-imports/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/secret-imports/{secretImportId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/create.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/create.mdx new file mode 100644 index 000000000..82d0eed17 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/workspace/{projectId}/tags" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/delete.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/delete.mdx new file mode 100644 index 000000000..cc98f03c2 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/workspace/{projectId}/tags/{tagId}" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/get-by-id.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/get-by-id.mdx new file mode 100644 index 000000000..de02fe133 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get By ID" +openapi: "GET /api/v1/workspace/{projectId}/tags/{tagId}" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/get-by-slug.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/get-by-slug.mdx new file mode 100644 index 000000000..91eab730f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/get-by-slug.mdx @@ -0,0 +1,4 @@ +--- +title: "Get By Slug" +openapi: "GET /api/v1/workspace/{projectId}/tags/slug/{tagSlug}" +--- diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/list.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/list.mdx new file mode 100644 index 000000000..c4a940f77 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/workspace/{projectId}/tags" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/deprecated/secret-tags/update.mdx b/docs/api-reference/endpoints/deprecated/secret-tags/update.mdx new file mode 100644 index 000000000..b9c290db8 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secret-tags/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/workspace/{projectId}/tags/{tagId}" +--- diff --git a/docs/api-reference/endpoints/secrets/attach-tags.mdx b/docs/api-reference/endpoints/deprecated/secrets/attach-tags.mdx similarity index 100% rename from docs/api-reference/endpoints/secrets/attach-tags.mdx rename to docs/api-reference/endpoints/deprecated/secrets/attach-tags.mdx diff --git a/docs/api-reference/endpoints/deprecated/secrets/create-many.mdx b/docs/api-reference/endpoints/deprecated/secrets/create-many.mdx new file mode 100644 index 000000000..227c5470d --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/create-many.mdx @@ -0,0 +1,5 @@ +--- +title: "Bulk Create" +openapi: "POST /api/v3/secrets/batch/raw" +--- + diff --git a/docs/api-reference/endpoints/deprecated/secrets/create.mdx b/docs/api-reference/endpoints/deprecated/secrets/create.mdx new file mode 100644 index 000000000..afee0a719 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/create.mdx @@ -0,0 +1,5 @@ +--- +title: "Create" +openapi: "POST /api/v3/secrets/raw/{secretName}" +--- + diff --git a/docs/api-reference/endpoints/deprecated/secrets/delete-many.mdx b/docs/api-reference/endpoints/deprecated/secrets/delete-many.mdx new file mode 100644 index 000000000..57c8588d8 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/delete-many.mdx @@ -0,0 +1,5 @@ +--- +title: "Bulk Delete" +openapi: "DELETE /api/v3/secrets/batch/raw" +--- + diff --git a/docs/api-reference/endpoints/deprecated/secrets/delete.mdx b/docs/api-reference/endpoints/deprecated/secrets/delete.mdx new file mode 100644 index 000000000..ef3abc722 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/delete.mdx @@ -0,0 +1,5 @@ +--- +title: "Delete" +openapi: "DELETE /api/v3/secrets/raw/{secretName}" +--- + diff --git a/docs/api-reference/endpoints/secrets/detach-tags.mdx b/docs/api-reference/endpoints/deprecated/secrets/detach-tags.mdx similarity index 100% rename from docs/api-reference/endpoints/secrets/detach-tags.mdx rename to docs/api-reference/endpoints/deprecated/secrets/detach-tags.mdx diff --git a/docs/api-reference/endpoints/deprecated/secrets/list.mdx b/docs/api-reference/endpoints/deprecated/secrets/list.mdx new file mode 100644 index 000000000..4808f6690 --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v3/secrets/raw" +--- diff --git a/docs/api-reference/endpoints/deprecated/secrets/read.mdx b/docs/api-reference/endpoints/deprecated/secrets/read.mdx new file mode 100644 index 000000000..6af308e0f --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/read.mdx @@ -0,0 +1,5 @@ +--- +title: "Retrieve" +openapi: "GET /api/v3/secrets/raw/{secretName}" +--- + diff --git a/docs/api-reference/endpoints/deprecated/secrets/update-many.mdx b/docs/api-reference/endpoints/deprecated/secrets/update-many.mdx new file mode 100644 index 000000000..7586d91bc --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/update-many.mdx @@ -0,0 +1,5 @@ +--- +title: "Bulk Update" +openapi: "PATCH /api/v3/secrets/batch/raw" +--- + diff --git a/docs/api-reference/endpoints/deprecated/secrets/update.mdx b/docs/api-reference/endpoints/deprecated/secrets/update.mdx new file mode 100644 index 000000000..ce68c492e --- /dev/null +++ b/docs/api-reference/endpoints/deprecated/secrets/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v3/secrets/raw/{secretName}" +--- diff --git a/docs/api-reference/endpoints/environments/create.mdx b/docs/api-reference/endpoints/environments/create.mdx index 826dcce3d..c7f9b0658 100644 --- a/docs/api-reference/endpoints/environments/create.mdx +++ b/docs/api-reference/endpoints/environments/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/workspace/{workspaceId}/environments" +openapi: "POST /api/v1/projects/{projectId}/environments" --- diff --git a/docs/api-reference/endpoints/environments/delete.mdx b/docs/api-reference/endpoints/environments/delete.mdx index 903e58d2a..2d2419255 100644 --- a/docs/api-reference/endpoints/environments/delete.mdx +++ b/docs/api-reference/endpoints/environments/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/workspace/{workspaceId}/environments/{id}" +openapi: "DELETE /api/v1/projects/{projectId}/environments/{id}" --- diff --git a/docs/api-reference/endpoints/environments/update.mdx b/docs/api-reference/endpoints/environments/update.mdx index f93968668..1583f5220 100644 --- a/docs/api-reference/endpoints/environments/update.mdx +++ b/docs/api-reference/endpoints/environments/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v1/workspace/{workspaceId}/environments/{id}" +openapi: "PATCH /api/v1/projects/{projectId}/environments/{id}" --- diff --git a/docs/api-reference/endpoints/folders/create.mdx b/docs/api-reference/endpoints/folders/create.mdx index e1ff3004a..13c4abea2 100644 --- a/docs/api-reference/endpoints/folders/create.mdx +++ b/docs/api-reference/endpoints/folders/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/folders" +openapi: "POST /api/v2/folders" --- diff --git a/docs/api-reference/endpoints/folders/delete.mdx b/docs/api-reference/endpoints/folders/delete.mdx index a106cc2eb..71ba9684c 100644 --- a/docs/api-reference/endpoints/folders/delete.mdx +++ b/docs/api-reference/endpoints/folders/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/folders/{folderIdOrName}" +openapi: "DELETE /api/v2/folders/{folderIdOrName}" --- diff --git a/docs/api-reference/endpoints/folders/get-by-id.mdx b/docs/api-reference/endpoints/folders/get-by-id.mdx index db3c4d0cc..ebfda35da 100644 --- a/docs/api-reference/endpoints/folders/get-by-id.mdx +++ b/docs/api-reference/endpoints/folders/get-by-id.mdx @@ -1,4 +1,4 @@ --- title: "Get by ID" -openapi: "GET /api/v1/folders/{id}" +openapi: "GET /api/v2/folders/{id}" --- diff --git a/docs/api-reference/endpoints/folders/list.mdx b/docs/api-reference/endpoints/folders/list.mdx index f40f93273..1d3a10ed8 100644 --- a/docs/api-reference/endpoints/folders/list.mdx +++ b/docs/api-reference/endpoints/folders/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v1/folders" +openapi: "GET /api/v2/folders" --- diff --git a/docs/api-reference/endpoints/folders/update.mdx b/docs/api-reference/endpoints/folders/update.mdx index c54778e94..969833a04 100644 --- a/docs/api-reference/endpoints/folders/update.mdx +++ b/docs/api-reference/endpoints/folders/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v1/folders/{folderId}" +openapi: "PATCH /api/v2/folders/{folderId}" --- diff --git a/docs/api-reference/endpoints/project-groups/create.mdx b/docs/api-reference/endpoints/project-groups/create.mdx index 6dd7f1a4f..c40c506a6 100644 --- a/docs/api-reference/endpoints/project-groups/create.mdx +++ b/docs/api-reference/endpoints/project-groups/create.mdx @@ -1,4 +1,4 @@ --- title: "Create Project Membership" -openapi: "POST /api/v2/workspace/{projectId}/groups/{groupIdOrName}" +openapi: "POST /api/v1/projects/{projectId}/groups/{groupIdOrName}" --- diff --git a/docs/api-reference/endpoints/project-groups/delete.mdx b/docs/api-reference/endpoints/project-groups/delete.mdx index 07db40d7f..450e05501 100644 --- a/docs/api-reference/endpoints/project-groups/delete.mdx +++ b/docs/api-reference/endpoints/project-groups/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete Project Membership" -openapi: "DELETE /api/v2/workspace/{projectId}/groups/{groupId}" +openapi: "DELETE /api/v1/projects/{projectId}/groups/{groupId}" --- diff --git a/docs/api-reference/endpoints/project-groups/get-by-id.mdx b/docs/api-reference/endpoints/project-groups/get-by-id.mdx index 611f39059..89aa6ebac 100644 --- a/docs/api-reference/endpoints/project-groups/get-by-id.mdx +++ b/docs/api-reference/endpoints/project-groups/get-by-id.mdx @@ -1,4 +1,4 @@ --- title: "Get Project Membership" -openapi: "GET /api/v2/workspace/{projectId}/groups/{groupId}" +openapi: "GET /api/v1/projects/{projectId}/groups/{groupId}" --- diff --git a/docs/api-reference/endpoints/project-groups/list.mdx b/docs/api-reference/endpoints/project-groups/list.mdx index 1488fb6ae..90b4f957a 100644 --- a/docs/api-reference/endpoints/project-groups/list.mdx +++ b/docs/api-reference/endpoints/project-groups/list.mdx @@ -1,4 +1,4 @@ --- title: "List Project Memberships" -openapi: "GET /api/v2/workspace/{projectId}/groups" +openapi: "GET /api/v1/projects/{projectId}/groups" --- diff --git a/docs/api-reference/endpoints/project-groups/update.mdx b/docs/api-reference/endpoints/project-groups/update.mdx index 8b963a1d8..029815673 100644 --- a/docs/api-reference/endpoints/project-groups/update.mdx +++ b/docs/api-reference/endpoints/project-groups/update.mdx @@ -1,4 +1,4 @@ --- title: "Update Project Membership" -openapi: "PATCH /api/v2/workspace/{projectId}/groups/{groupId}" +openapi: "PATCH /api/v1/projects/{projectId}/groups/{groupId}" --- diff --git a/docs/api-reference/endpoints/project-identities/add-identity-membership.mdx b/docs/api-reference/endpoints/project-identities/add-identity-membership.mdx index 285b1d1c4..8b0122efd 100644 --- a/docs/api-reference/endpoints/project-identities/add-identity-membership.mdx +++ b/docs/api-reference/endpoints/project-identities/add-identity-membership.mdx @@ -1,4 +1,4 @@ --- title: "Create Identity Membership" -openapi: "POST /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +openapi: "POST /api/v1/projects/{projectId}/identity-memberships/{identityId}" --- diff --git a/docs/api-reference/endpoints/project-identities/delete-identity-membership.mdx b/docs/api-reference/endpoints/project-identities/delete-identity-membership.mdx index e2b266626..10c56e5ec 100644 --- a/docs/api-reference/endpoints/project-identities/delete-identity-membership.mdx +++ b/docs/api-reference/endpoints/project-identities/delete-identity-membership.mdx @@ -1,4 +1,4 @@ --- title: "Delete Identity Membership" -openapi: "DELETE /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +openapi: "DELETE /api/v1/projects/{projectId}/identity-memberships/{identityId}" --- diff --git a/docs/api-reference/endpoints/project-identities/get-by-id.mdx b/docs/api-reference/endpoints/project-identities/get-by-id.mdx index 37f4192d7..801b0d422 100644 --- a/docs/api-reference/endpoints/project-identities/get-by-id.mdx +++ b/docs/api-reference/endpoints/project-identities/get-by-id.mdx @@ -1,4 +1,4 @@ --- title: "Get Identity by ID" -openapi: "GET /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +openapi: "GET /api/v1/projects/{projectId}/identity-memberships/{identityId}" --- diff --git a/docs/api-reference/endpoints/project-identities/list-identity-memberships.mdx b/docs/api-reference/endpoints/project-identities/list-identity-memberships.mdx index e5162e693..8673350fa 100644 --- a/docs/api-reference/endpoints/project-identities/list-identity-memberships.mdx +++ b/docs/api-reference/endpoints/project-identities/list-identity-memberships.mdx @@ -1,4 +1,4 @@ --- title: "List Identity Memberships" -openapi: "GET /api/v2/workspace/{projectId}/identity-memberships" +openapi: "GET /api/v1/projects/{projectId}/identity-memberships" --- diff --git a/docs/api-reference/endpoints/project-identities/update-identity-membership.mdx b/docs/api-reference/endpoints/project-identities/update-identity-membership.mdx index 667cf7eb3..ef34363d6 100644 --- a/docs/api-reference/endpoints/project-identities/update-identity-membership.mdx +++ b/docs/api-reference/endpoints/project-identities/update-identity-membership.mdx @@ -1,4 +1,4 @@ --- title: "Update Identity Membership" -openapi: "PATCH /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +openapi: "PATCH /api/v1/projects/{projectId}/identity-memberships/{identityId}" --- diff --git a/docs/api-reference/endpoints/project-roles/create.mdx b/docs/api-reference/endpoints/project-roles/create.mdx index 97570ec36..1d3f9eb51 100644 --- a/docs/api-reference/endpoints/project-roles/create.mdx +++ b/docs/api-reference/endpoints/project-roles/create.mdx @@ -1,10 +1,9 @@ --- title: "Create" -openapi: "POST /api/v2/workspace/{projectId}/roles" +openapi: "POST /api/v1/projects/{projectId}/roles" --- You can read more about the permissions field in the [permissions documentation](/internals/permissions). - diff --git a/docs/api-reference/endpoints/project-roles/delete.mdx b/docs/api-reference/endpoints/project-roles/delete.mdx index 41edfa7c3..7b162c6da 100644 --- a/docs/api-reference/endpoints/project-roles/delete.mdx +++ b/docs/api-reference/endpoints/project-roles/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v2/workspace/{projectId}/roles/{roleId}" +openapi: "DELETE /api/v1/projects/{projectId}/roles/{roleId}" --- diff --git a/docs/api-reference/endpoints/project-roles/get-by-slug.mdx b/docs/api-reference/endpoints/project-roles/get-by-slug.mdx index dfc5c582a..adfac8cd3 100644 --- a/docs/api-reference/endpoints/project-roles/get-by-slug.mdx +++ b/docs/api-reference/endpoints/project-roles/get-by-slug.mdx @@ -1,4 +1,4 @@ --- title: "Get By Slug" -openapi: "GET /api/v2/workspace/{projectId}/roles/slug/{roleSlug}" +openapi: "GET /api/v1/projects/{projectId}/roles/slug/{roleSlug}" --- diff --git a/docs/api-reference/endpoints/project-roles/list.mdx b/docs/api-reference/endpoints/project-roles/list.mdx index 8d8dc10c1..df6164370 100644 --- a/docs/api-reference/endpoints/project-roles/list.mdx +++ b/docs/api-reference/endpoints/project-roles/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v2/workspace/{projectId}/roles" +openapi: "GET /api/v1/projects/{projectId}/roles" --- diff --git a/docs/api-reference/endpoints/project-roles/update.mdx b/docs/api-reference/endpoints/project-roles/update.mdx index 662d5e617..56ac34f8b 100644 --- a/docs/api-reference/endpoints/project-roles/update.mdx +++ b/docs/api-reference/endpoints/project-roles/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v2/workspace/{projectId}/roles/{roleId}" +openapi: "PATCH /api/v1/projects/{projectId}/roles/{roleId}" --- diff --git a/docs/api-reference/endpoints/project-users/delete-membership.mdx b/docs/api-reference/endpoints/project-users/delete-membership.mdx index 1995e4726..5c87e94af 100644 --- a/docs/api-reference/endpoints/project-users/delete-membership.mdx +++ b/docs/api-reference/endpoints/project-users/delete-membership.mdx @@ -1,4 +1,4 @@ --- title: "Delete User Membership" -openapi: "DELETE /api/v1/workspace/{workspaceId}/memberships/{membershipId}" +openapi: "DELETE /api/v1/projects/{projectId}/memberships/{membershipId}" --- diff --git a/docs/api-reference/endpoints/project-users/get-by-username.mdx b/docs/api-reference/endpoints/project-users/get-by-username.mdx index ec69d4947..5e4916b8e 100644 --- a/docs/api-reference/endpoints/project-users/get-by-username.mdx +++ b/docs/api-reference/endpoints/project-users/get-by-username.mdx @@ -1,4 +1,4 @@ --- title: "Get By Username" -openapi: "POST /api/v1/workspace/{workspaceId}/memberships/details" +openapi: "POST /api/v1/projects/{projectId}/memberships/details" --- diff --git a/docs/api-reference/endpoints/project-users/invite-member-to-project.mdx b/docs/api-reference/endpoints/project-users/invite-member-to-project.mdx new file mode 100644 index 000000000..c6fda8a09 --- /dev/null +++ b/docs/api-reference/endpoints/project-users/invite-member-to-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Invite Member" +openapi: "POST /api/v1/projects/{projectId}/memberships" +--- diff --git a/docs/api-reference/endpoints/project-users/memberships.mdx b/docs/api-reference/endpoints/project-users/memberships.mdx index 3c4735f94..dd79667fe 100644 --- a/docs/api-reference/endpoints/project-users/memberships.mdx +++ b/docs/api-reference/endpoints/project-users/memberships.mdx @@ -1,4 +1,4 @@ --- title: "Get User Memberships" -openapi: "GET /api/v1/workspace/{workspaceId}/memberships" +openapi: "GET /api/v1/projects/{projectId}/memberships" --- diff --git a/docs/api-reference/endpoints/project-users/remove-member-from-project.mdx b/docs/api-reference/endpoints/project-users/remove-member-from-project.mdx new file mode 100644 index 000000000..8646aa158 --- /dev/null +++ b/docs/api-reference/endpoints/project-users/remove-member-from-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Remove Member" +openapi: "DELETE /api/v1/projects/{projectId}/memberships" +--- diff --git a/docs/api-reference/endpoints/project-users/update-membership.mdx b/docs/api-reference/endpoints/project-users/update-membership.mdx index 9a7aa600f..5f1edcf0a 100644 --- a/docs/api-reference/endpoints/project-users/update-membership.mdx +++ b/docs/api-reference/endpoints/project-users/update-membership.mdx @@ -1,4 +1,4 @@ --- title: "Update User Membership" -openapi: "PATCH /api/v1/workspace/{workspaceId}/memberships/{membershipId}" +openapi: "PATCH /api/v1/projects/{projectId}/memberships/{membershipId}" --- diff --git a/docs/api-reference/endpoints/projects/create-project.mdx b/docs/api-reference/endpoints/projects/create-project.mdx new file mode 100644 index 000000000..7ee1caff8 --- /dev/null +++ b/docs/api-reference/endpoints/projects/create-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Create Project" +openapi: "POST /api/v1/projects" +--- diff --git a/docs/api-reference/endpoints/projects/delete-project.mdx b/docs/api-reference/endpoints/projects/delete-project.mdx new file mode 100644 index 000000000..eff96ded5 --- /dev/null +++ b/docs/api-reference/endpoints/projects/delete-project.mdx @@ -0,0 +1,8 @@ +--- +title: "Delete Project" +openapi: "DELETE /api/v1/projects/{projectId}" +--- + + + This operation is irreversible. All data associated with the project will be deleted. Please use with caution. + diff --git a/docs/api-reference/endpoints/projects/get-project-by-slug.mdx b/docs/api-reference/endpoints/projects/get-project-by-slug.mdx new file mode 100644 index 000000000..9bcccc31e --- /dev/null +++ b/docs/api-reference/endpoints/projects/get-project-by-slug.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Project By Slug" +openapi: "GET /api/v1/projects/slug/{slug}" +--- diff --git a/docs/api-reference/endpoints/projects/get-project.mdx b/docs/api-reference/endpoints/projects/get-project.mdx new file mode 100644 index 000000000..6ed92833e --- /dev/null +++ b/docs/api-reference/endpoints/projects/get-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Project" +openapi: "GET /api/v1/projects/{projectId}" +--- diff --git a/docs/api-reference/endpoints/projects/list-projects.mdx b/docs/api-reference/endpoints/projects/list-projects.mdx new file mode 100644 index 000000000..5017023a1 --- /dev/null +++ b/docs/api-reference/endpoints/projects/list-projects.mdx @@ -0,0 +1,4 @@ +--- +title: "List Projects" +openapi: "GET /api/v1/projects" +--- diff --git a/docs/api-reference/endpoints/projects/rollback-snapshot.mdx b/docs/api-reference/endpoints/projects/rollback-snapshot.mdx new file mode 100644 index 000000000..527c861b2 --- /dev/null +++ b/docs/api-reference/endpoints/projects/rollback-snapshot.mdx @@ -0,0 +1,4 @@ +--- +title: "Roll Back to Snapshot" +openapi: "POST /api/v1/secret-snapshot/{secretSnapshotId}/rollback" +--- diff --git a/docs/api-reference/endpoints/projects/secret-snapshots.mdx b/docs/api-reference/endpoints/projects/secret-snapshots.mdx new file mode 100644 index 000000000..182e61507 --- /dev/null +++ b/docs/api-reference/endpoints/projects/secret-snapshots.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Snapshots" +openapi: "GET /api/v1/projects/{projectId}/secret-snapshots" +--- diff --git a/docs/api-reference/endpoints/projects/update-project.mdx b/docs/api-reference/endpoints/projects/update-project.mdx new file mode 100644 index 000000000..99fcc7b76 --- /dev/null +++ b/docs/api-reference/endpoints/projects/update-project.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Project" +openapi: "PATCH /api/v1/projects/{projectId}" +--- diff --git a/docs/api-reference/endpoints/secret-imports/create.mdx b/docs/api-reference/endpoints/secret-imports/create.mdx index 3abfb320f..f072e59a4 100644 --- a/docs/api-reference/endpoints/secret-imports/create.mdx +++ b/docs/api-reference/endpoints/secret-imports/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/secret-imports" +openapi: "POST /api/v2/secret-imports" --- diff --git a/docs/api-reference/endpoints/secret-imports/delete.mdx b/docs/api-reference/endpoints/secret-imports/delete.mdx index cfa5960b1..d0b35426b 100644 --- a/docs/api-reference/endpoints/secret-imports/delete.mdx +++ b/docs/api-reference/endpoints/secret-imports/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/secret-imports/{secretImportId}" +openapi: "DELETE /api/v2/secret-imports/{secretImportId}" --- diff --git a/docs/api-reference/endpoints/secret-imports/list.mdx b/docs/api-reference/endpoints/secret-imports/list.mdx index 580d4be8d..1cf3e1386 100644 --- a/docs/api-reference/endpoints/secret-imports/list.mdx +++ b/docs/api-reference/endpoints/secret-imports/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v1/secret-imports" +openapi: "GET /api/v2/secret-imports" --- diff --git a/docs/api-reference/endpoints/secret-imports/update.mdx b/docs/api-reference/endpoints/secret-imports/update.mdx index f21133223..146fcd76b 100644 --- a/docs/api-reference/endpoints/secret-imports/update.mdx +++ b/docs/api-reference/endpoints/secret-imports/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v1/secret-imports/{secretImportId}" +openapi: "PATCH /api/v2/secret-imports/{secretImportId}" --- diff --git a/docs/api-reference/endpoints/secret-tags/create.mdx b/docs/api-reference/endpoints/secret-tags/create.mdx index 82d0eed17..d4c587979 100644 --- a/docs/api-reference/endpoints/secret-tags/create.mdx +++ b/docs/api-reference/endpoints/secret-tags/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/workspace/{projectId}/tags" ---- \ No newline at end of file +openapi: "POST /api/v1/projects/{projectId}/tags" +--- diff --git a/docs/api-reference/endpoints/secret-tags/delete.mdx b/docs/api-reference/endpoints/secret-tags/delete.mdx index cc98f03c2..121d6816c 100644 --- a/docs/api-reference/endpoints/secret-tags/delete.mdx +++ b/docs/api-reference/endpoints/secret-tags/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/workspace/{projectId}/tags/{tagId}" ---- \ No newline at end of file +openapi: "DELETE /api/v1/projects/{projectId}/tags/{tagId}" +--- diff --git a/docs/api-reference/endpoints/secret-tags/get-by-id.mdx b/docs/api-reference/endpoints/secret-tags/get-by-id.mdx index de02fe133..7e1fa8d7f 100644 --- a/docs/api-reference/endpoints/secret-tags/get-by-id.mdx +++ b/docs/api-reference/endpoints/secret-tags/get-by-id.mdx @@ -1,4 +1,4 @@ --- title: "Get By ID" -openapi: "GET /api/v1/workspace/{projectId}/tags/{tagId}" +openapi: "GET /api/v1/projects/{projectId}/tags/{tagId}" --- diff --git a/docs/api-reference/endpoints/secret-tags/get-by-slug.mdx b/docs/api-reference/endpoints/secret-tags/get-by-slug.mdx index 91eab730f..c012c97d4 100644 --- a/docs/api-reference/endpoints/secret-tags/get-by-slug.mdx +++ b/docs/api-reference/endpoints/secret-tags/get-by-slug.mdx @@ -1,4 +1,4 @@ --- title: "Get By Slug" -openapi: "GET /api/v1/workspace/{projectId}/tags/slug/{tagSlug}" +openapi: "GET /api/v1/projects/{projectId}/tags/slug/{tagSlug}" --- diff --git a/docs/api-reference/endpoints/secret-tags/list.mdx b/docs/api-reference/endpoints/secret-tags/list.mdx index c4a940f77..b7e1eca74 100644 --- a/docs/api-reference/endpoints/secret-tags/list.mdx +++ b/docs/api-reference/endpoints/secret-tags/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v1/workspace/{projectId}/tags" ---- \ No newline at end of file +openapi: "GET /api/v1/projects/{projectId}/tags" +--- diff --git a/docs/api-reference/endpoints/secret-tags/update.mdx b/docs/api-reference/endpoints/secret-tags/update.mdx index b9c290db8..b20ce49f4 100644 --- a/docs/api-reference/endpoints/secret-tags/update.mdx +++ b/docs/api-reference/endpoints/secret-tags/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v1/workspace/{projectId}/tags/{tagId}" +openapi: "PATCH /api/v1/projects/{projectId}/tags/{tagId}" --- diff --git a/docs/api-reference/endpoints/secrets/create-many.mdx b/docs/api-reference/endpoints/secrets/create-many.mdx index 227c5470d..e3d30aef8 100644 --- a/docs/api-reference/endpoints/secrets/create-many.mdx +++ b/docs/api-reference/endpoints/secrets/create-many.mdx @@ -1,5 +1,4 @@ --- title: "Bulk Create" -openapi: "POST /api/v3/secrets/batch/raw" +openapi: "POST /api/v4/secrets/batch" --- - diff --git a/docs/api-reference/endpoints/secrets/create.mdx b/docs/api-reference/endpoints/secrets/create.mdx index afee0a719..c04dcf9f4 100644 --- a/docs/api-reference/endpoints/secrets/create.mdx +++ b/docs/api-reference/endpoints/secrets/create.mdx @@ -1,5 +1,4 @@ --- title: "Create" -openapi: "POST /api/v3/secrets/raw/{secretName}" +openapi: "POST /api/v4/secrets/{secretName}" --- - diff --git a/docs/api-reference/endpoints/secrets/delete-many.mdx b/docs/api-reference/endpoints/secrets/delete-many.mdx index 57c8588d8..49491c3b9 100644 --- a/docs/api-reference/endpoints/secrets/delete-many.mdx +++ b/docs/api-reference/endpoints/secrets/delete-many.mdx @@ -1,5 +1,4 @@ --- title: "Bulk Delete" -openapi: "DELETE /api/v3/secrets/batch/raw" +openapi: "DELETE /api/v4/secrets/batch" --- - diff --git a/docs/api-reference/endpoints/secrets/delete.mdx b/docs/api-reference/endpoints/secrets/delete.mdx index ef3abc722..f71a851e8 100644 --- a/docs/api-reference/endpoints/secrets/delete.mdx +++ b/docs/api-reference/endpoints/secrets/delete.mdx @@ -1,5 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v3/secrets/raw/{secretName}" +openapi: "DELETE /api/v4/secrets/{secretName}" --- - diff --git a/docs/api-reference/endpoints/secrets/list.mdx b/docs/api-reference/endpoints/secrets/list.mdx index 4808f6690..6a1f38d68 100644 --- a/docs/api-reference/endpoints/secrets/list.mdx +++ b/docs/api-reference/endpoints/secrets/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v3/secrets/raw" +openapi: "GET /api/v4/secrets" --- diff --git a/docs/api-reference/endpoints/secrets/read.mdx b/docs/api-reference/endpoints/secrets/read.mdx index 6af308e0f..10e725ba5 100644 --- a/docs/api-reference/endpoints/secrets/read.mdx +++ b/docs/api-reference/endpoints/secrets/read.mdx @@ -1,5 +1,4 @@ --- title: "Retrieve" -openapi: "GET /api/v3/secrets/raw/{secretName}" +openapi: "GET /api/v4/secrets/{secretName}" --- - diff --git a/docs/api-reference/endpoints/secrets/update-many.mdx b/docs/api-reference/endpoints/secrets/update-many.mdx index 7586d91bc..9aca17148 100644 --- a/docs/api-reference/endpoints/secrets/update-many.mdx +++ b/docs/api-reference/endpoints/secrets/update-many.mdx @@ -1,5 +1,4 @@ --- title: "Bulk Update" -openapi: "PATCH /api/v3/secrets/batch/raw" +openapi: "PATCH /api/v4/secrets/batch" --- - diff --git a/docs/api-reference/endpoints/secrets/update.mdx b/docs/api-reference/endpoints/secrets/update.mdx index ce68c492e..435f3e0c4 100644 --- a/docs/api-reference/endpoints/secrets/update.mdx +++ b/docs/api-reference/endpoints/secrets/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v3/secrets/raw/{secretName}" +openapi: "PATCH /api/v4/secrets/{secretName}" --- diff --git a/docs/docs.json b/docs/docs.json index 285e11a60..8b64210a0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1009,28 +1009,55 @@ "api-reference/endpoints/organizations/delete-membership", "api-reference/endpoints/organizations/bulk-delete-memberships", "api-reference/endpoints/organizations/list-identity-memberships", - "api-reference/endpoints/organizations/workspaces" + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/organizations/projects" + ] + } ] }, { "group": "Projects", "pages": [ - "api-reference/endpoints/workspaces/get-workspace-by-slug", - "api-reference/endpoints/workspaces/create-workspace", - "api-reference/endpoints/workspaces/delete-workspace", - "api-reference/endpoints/workspaces/get-workspace", - "api-reference/endpoints/workspaces/update-workspace", - "api-reference/endpoints/workspaces/secret-snapshots" + "api-reference/endpoints/projects/get-project-by-slug", + "api-reference/endpoints/projects/create-project", + "api-reference/endpoints/projects/delete-project", + "api-reference/endpoints/projects/get-project", + "api-reference/endpoints/projects/list-projects", + "api-reference/endpoints/projects/update-project", + "api-reference/endpoints/projects/secret-snapshots", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/projects/get-project-by-slug", + "api-reference/endpoints/deprecated/projects/create-project", + "api-reference/endpoints/deprecated/projects/delete-project", + "api-reference/endpoints/deprecated/projects/get-project", + "api-reference/endpoints/deprecated/projects/update-project", + "api-reference/endpoints/deprecated/projects/secret-snapshots" + ] + } ] }, { "group": "Project Users", "pages": [ - "api-reference/endpoints/project-users/invite-member-to-workspace", - "api-reference/endpoints/project-users/remove-member-from-workspace", + "api-reference/endpoints/project-users/invite-member-to-project", + "api-reference/endpoints/project-users/remove-member-from-project", "api-reference/endpoints/project-users/memberships", "api-reference/endpoints/project-users/get-by-username", - "api-reference/endpoints/project-users/update-membership" + "api-reference/endpoints/project-users/update-membership", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/project-users/invite-member-to-project", + "api-reference/endpoints/deprecated/project-users/remove-member-from-project", + "api-reference/endpoints/deprecated/project-users/memberships", + "api-reference/endpoints/deprecated/project-users/get-by-username", + "api-reference/endpoints/deprecated/project-users/update-membership" + ] + } ] }, { @@ -1040,7 +1067,17 @@ "api-reference/endpoints/project-groups/delete", "api-reference/endpoints/project-groups/get-by-id", "api-reference/endpoints/project-groups/list", - "api-reference/endpoints/project-groups/update" + "api-reference/endpoints/project-groups/update", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/project-groups/create", + "api-reference/endpoints/deprecated/project-groups/delete", + "api-reference/endpoints/deprecated/project-groups/get-by-id", + "api-reference/endpoints/deprecated/project-groups/list", + "api-reference/endpoints/deprecated/project-groups/update" + ] + } ] }, { @@ -1050,7 +1087,17 @@ "api-reference/endpoints/project-identities/list-identity-memberships", "api-reference/endpoints/project-identities/get-by-id", "api-reference/endpoints/project-identities/update-identity-membership", - "api-reference/endpoints/project-identities/delete-identity-membership" + "api-reference/endpoints/project-identities/delete-identity-membership", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/project-identities/add-identity-membership", + "api-reference/endpoints/deprecated/project-identities/list-identity-memberships", + "api-reference/endpoints/deprecated/project-identities/get-by-id", + "api-reference/endpoints/deprecated/project-identities/update-identity-membership", + "api-reference/endpoints/deprecated/project-identities/delete-identity-membership" + ] + } ] }, { @@ -1060,7 +1107,17 @@ "api-reference/endpoints/project-roles/update", "api-reference/endpoints/project-roles/delete", "api-reference/endpoints/project-roles/get-by-slug", - "api-reference/endpoints/project-roles/list" + "api-reference/endpoints/project-roles/list", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/project-roles/create", + "api-reference/endpoints/deprecated/project-roles/update", + "api-reference/endpoints/deprecated/project-roles/delete", + "api-reference/endpoints/deprecated/project-roles/get-by-slug", + "api-reference/endpoints/deprecated/project-roles/list" + ] + } ] }, { @@ -1078,7 +1135,15 @@ "pages": [ "api-reference/endpoints/environments/create", "api-reference/endpoints/environments/update", - "api-reference/endpoints/environments/delete" + "api-reference/endpoints/environments/delete", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/environments/create", + "api-reference/endpoints/deprecated/environments/update", + "api-reference/endpoints/deprecated/environments/delete" + ] + } ] }, { @@ -1088,7 +1153,17 @@ "api-reference/endpoints/folders/get-by-id", "api-reference/endpoints/folders/create", "api-reference/endpoints/folders/update", - "api-reference/endpoints/folders/delete" + "api-reference/endpoints/folders/delete", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/folders/list", + "api-reference/endpoints/deprecated/folders/get-by-id", + "api-reference/endpoints/deprecated/folders/create", + "api-reference/endpoints/deprecated/folders/update", + "api-reference/endpoints/deprecated/folders/delete" + ] + } ] }, { @@ -1099,7 +1174,18 @@ "api-reference/endpoints/secret-tags/get-by-slug", "api-reference/endpoints/secret-tags/create", "api-reference/endpoints/secret-tags/update", - "api-reference/endpoints/secret-tags/delete" + "api-reference/endpoints/secret-tags/delete", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/secret-tags/list", + "api-reference/endpoints/deprecated/secret-tags/get-by-id", + "api-reference/endpoints/deprecated/secret-tags/get-by-slug", + "api-reference/endpoints/deprecated/secret-tags/create", + "api-reference/endpoints/deprecated/secret-tags/update", + "api-reference/endpoints/deprecated/secret-tags/delete" + ] + } ] }, { @@ -1113,8 +1199,21 @@ "api-reference/endpoints/secrets/create-many", "api-reference/endpoints/secrets/update-many", "api-reference/endpoints/secrets/delete-many", - "api-reference/endpoints/secrets/attach-tags", - "api-reference/endpoints/secrets/detach-tags" + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/secrets/list", + "api-reference/endpoints/deprecated/secrets/create", + "api-reference/endpoints/deprecated/secrets/read", + "api-reference/endpoints/deprecated/secrets/update", + "api-reference/endpoints/deprecated/secrets/delete", + "api-reference/endpoints/deprecated/secrets/create-many", + "api-reference/endpoints/deprecated/secrets/update-many", + "api-reference/endpoints/deprecated/secrets/delete-many", + "api-reference/endpoints/deprecated/secrets/attach-tags", + "api-reference/endpoints/deprecated/secrets/detach-tags" + ] + } ] }, { @@ -1144,7 +1243,16 @@ "api-reference/endpoints/secret-imports/list", "api-reference/endpoints/secret-imports/create", "api-reference/endpoints/secret-imports/update", - "api-reference/endpoints/secret-imports/delete" + "api-reference/endpoints/secret-imports/delete", + { + "group": "Legacy", + "pages": [ + "api-reference/endpoints/deprecated/secret-imports/list", + "api-reference/endpoints/deprecated/secret-imports/create", + "api-reference/endpoints/deprecated/secret-imports/update", + "api-reference/endpoints/deprecated/secret-imports/delete" + ] + } ] }, { diff --git a/docs/documentation/platform/identities/machine-identities.mdx b/docs/documentation/platform/identities/machine-identities.mdx index b4a18708c..dd5cbbbcf 100644 --- a/docs/documentation/platform/identities/machine-identities.mdx +++ b/docs/documentation/platform/identities/machine-identities.mdx @@ -38,6 +38,16 @@ To interact with various resources in Infisical, Machine Identities can authenti - [GCP Auth](/documentation/platform/identities/gcp-auth): A GCP-native authentication method for GCP resources (e.g. Compute Engine, App Engine, Cloud Run, Google Kubernetes Engine, IAM service accounts, etc.). - [OIDC Auth](/documentation/platform/identities/oidc-auth): A platform-agnostic, JWT-based authentication method for workloads using an OpenID Connect identity provider. +## Identity Lockout + +Lockout is a feature that prevents brute-force attacks on identity login endpoints. Auth methods that support lockout include: [Universal Auth](/documentation/platform/identities/universal-auth). + +Supported auth methods have lockout enabled by default. If triggered, lockout temporarily disables the login endpoint for 5 minutes after 3 consecutive failed login attempts within a 30-second window. Lockout can be configured and disabled in the identity auth method settings. + + + When Lockout is enabled, a rate limit of approximately 10 requests per second is enforced on relevant authentication endpoints. This security measure employs a protective lock to mitigate parallel login attacks. If this rate limitation interferes with your operational requirements, you may consider disabling Lockout. + + ## FAQ @@ -51,15 +61,15 @@ You can learn more about how to do this in the CLI quickstart [here](/cli/usage) A service token is a project-level authentication method that is being deprecated in favor of identities. The service token method will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys). - + Amongst many differences, identities provide broader access over the Infisical API, utilizes the same permission system as user identities, and come with a significantly larger number of configurable authentication and security features. - + If you're looking for a simple authentication method, similar to service tokens, that can be bound onto an identity, we recommend checking out [Token Auth](/documentation/platform/identities/token-auth). There are a few reasons for why this might happen: - + - You have insufficient organization permissions to create, read, update, delete identities. - The identity you are trying to read, update, or delete is more privileged than yourself. - The role you are trying to create an identity for or update an identity to is more privileged than yours. diff --git a/docs/documentation/platform/identities/universal-auth.mdx b/docs/documentation/platform/identities/universal-auth.mdx index a3aba04e2..3a87f6da9 100644 --- a/docs/documentation/platform/identities/universal-auth.mdx +++ b/docs/documentation/platform/identities/universal-auth.mdx @@ -65,25 +65,30 @@ using the Universal Auth authentication method. By default, the identity has been configured with Universal Auth. If you wish, you can edit the Universal Auth configuration details by pressing to edit the **Authentication** section. - ![identities organization create universal auth method 1](/images/platform/identities/identities-org-create-universal-auth-method-1.png) - ![identities organization create universal auth method 2](/images/platform/identities/identities-org-create-universal-auth-method-2.png) - ![identities organization create universal auth method 3](/images/platform/identities/identities-org-create-universal-auth-method-3.png) - - Here's some more guidance on each field: + Here's some guidance on each field: **Configuration Tab** + + ![identities organization create universal auth method 1](/images/platform/identities/identities-org-create-universal-auth-method-1.png) + - Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an access token in seconds. This value will be referenced at renewal time. - Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an access token in seconds. This value will be referenced at renewal time. - Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses. - Access Token Period (optional, default is `0`): If set, the access token becomes a renewable, non-expiring token for the specified period (in seconds). TTL and Max TTL are ignored when this is set. This is ideal for "secret zero" scenarios, where a workload needs to bootstrap itself securely without hard-coded static secrets. **Lockout Tab** + + ![identities organization create universal auth method 2](/images/platform/identities/identities-org-create-universal-auth-method-2.png) + - Lockout (enabled by default): The lockout feature will temporarily block login attempts after X consecutive login failures. - Lockout Threshold (default is `3`): The amount of times login must fail before locking the identity auth method. - Lockout Duration (default is `5 minutes`): How long an identity auth method lockout lasts. - Lockout Counter Reset (default is `30 seconds`): How long to wait from the most recent failed login until resetting the lockout counter. **Advanced Tab** + + ![identities organization create universal auth method 3](/images/platform/identities/identities-org-create-universal-auth-method-3.png) + - Client Secret Trusted IPs: The IPs or CIDR ranges that the **Client Secret** can be used from together with the **Client ID** to get back an access token. By default, **Client Secrets** are given the `0.0.0.0/0`, allowing usage from any network address. - Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address. diff --git a/docs/images/app-connections/general/add-connection.png b/docs/images/app-connections/general/add-connection.png index ad9d54716..b6dce69ac 100644 Binary files a/docs/images/app-connections/general/add-connection.png and b/docs/images/app-connections/general/add-connection.png differ diff --git a/docs/integrations/app-connections/1password.mdx b/docs/integrations/app-connections/1password.mdx index 394d8bc23..bcd7e8ff7 100644 --- a/docs/integrations/app-connections/1password.mdx +++ b/docs/integrations/app-connections/1password.mdx @@ -53,7 +53,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -72,7 +72,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com ![1Password Connection Modal](/images/app-connections/1password/app-connection-modal.png) - After clicking Create, your **1Password Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **1Password Connection** is established and ready to use with your Infisical project. ![1Password Connection Created](/images/app-connections/1password/app-connection-created.png) @@ -90,6 +90,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com --data '{ "name": "my-1password-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "instanceUrl": "https://1pass.example.com", "apiToken": "" @@ -104,6 +105,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-1password-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/auth0.mdx b/docs/integrations/app-connections/auth0.mdx index 42e78cb66..91c5f5e60 100644 --- a/docs/integrations/app-connections/auth0.mdx +++ b/docs/integrations/app-connections/auth0.mdx @@ -42,7 +42,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **Auth0 Connection** option. @@ -67,6 +67,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st --data '{ "name": "my-auth0-connection", "method": "client-credentials", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "domain": "xxx-xxxxxxxxx.us.auth0.com", "clientId": "...", @@ -83,6 +84,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-auth0-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/aws.mdx b/docs/integrations/app-connections/aws.mdx index 195f98247..08ae05be7 100644 --- a/docs/integrations/app-connections/aws.mdx +++ b/docs/integrations/app-connections/aws.mdx @@ -184,7 +184,7 @@ Infisical supports two methods for connecting to AWS. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **AWS Connection** option. @@ -209,6 +209,7 @@ Infisical supports two methods for connecting to AWS. --data '{ "name": "my-aws-connection", "method": "assume-role", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "roleArn": "...", } @@ -222,6 +223,7 @@ Infisical supports two methods for connecting to AWS. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-aws-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 123, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", @@ -361,7 +363,7 @@ Infisical supports two methods for connecting to AWS. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **AWS Connection** option. @@ -386,6 +388,7 @@ Infisical supports two methods for connecting to AWS. --data '{ "name": "my-aws-connection", "method": "access-key", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "accessKeyId": "...", "secretKey": "..." @@ -400,6 +403,7 @@ Infisical supports two methods for connecting to AWS. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-aws-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 123, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/azure-app-configuration.mdx b/docs/integrations/app-connections/azure-app-configuration.mdx index 4efd19f30..cd9c707be 100644 --- a/docs/integrations/app-connections/azure-app-configuration.mdx +++ b/docs/integrations/app-connections/azure-app-configuration.mdx @@ -83,7 +83,7 @@ Infisical currently only supports two methods for connecting to Azure, which are - Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index 37e7d49b0..cb25fb596 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -94,7 +94,7 @@ Infisical currently only supports two methods for connecting to Azure, which are - Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/azure-devops.mdx b/docs/integrations/app-connections/azure-devops.mdx index 4744a35c6..7eabff84b 100644 --- a/docs/integrations/app-connections/azure-devops.mdx +++ b/docs/integrations/app-connections/azure-devops.mdx @@ -117,7 +117,7 @@ Infisical currently supports three methods for connecting to Azure DevOps, which - Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/azure-key-vault.mdx b/docs/integrations/app-connections/azure-key-vault.mdx index 866a1de82..b2989efae 100644 --- a/docs/integrations/app-connections/azure-key-vault.mdx +++ b/docs/integrations/app-connections/azure-key-vault.mdx @@ -83,7 +83,7 @@ Infisical currently only supports two methods for connecting to Azure, which are - Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/bitbucket.mdx b/docs/integrations/app-connections/bitbucket.mdx index be4fdbee7..3b3385202 100644 --- a/docs/integrations/app-connections/bitbucket.mdx +++ b/docs/integrations/app-connections/bitbucket.mdx @@ -78,7 +78,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -95,7 +95,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck ![Bitbucket Connection Modal](/images/app-connections/bitbucket/step-6.png) - After clicking Create, your **Bitbucket Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **Bitbucket Connection** is established and ready to use with your Infisical project. ![Bitbucket Connection Created](/images/app-connections/bitbucket/step-7.png) @@ -113,6 +113,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck --data '{ "name": "my-bitbucket-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "email": "user@example.com", "apiToken": "" @@ -127,6 +128,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-bitbucket-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/camunda.mdx b/docs/integrations/app-connections/camunda.mdx index 68084cea3..7ad0f8ef9 100644 --- a/docs/integrations/app-connections/camunda.mdx +++ b/docs/integrations/app-connections/camunda.mdx @@ -50,8 +50,7 @@ Infisical supports connecting to Camunda APIs using [client credentials](https:/ - Navigate to the **App Connections** tab on the **Organization Settings** - page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/checkly.mdx b/docs/integrations/app-connections/checkly.mdx index 38234744d..943a470c2 100644 --- a/docs/integrations/app-connections/checkly.mdx +++ b/docs/integrations/app-connections/checkly.mdx @@ -37,7 +37,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user - In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -55,7 +55,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user ![Checkly Connection Modal](/images/app-connections/checkly/checkly-app-connection-form.png) - After submitting the form, your **Checkly Connection** will be successfully created and ready to use with your Infisical projects. + After submitting the form, your **Checkly Connection** will be successfully created and ready to use with your Infisical project. ![Checkly Connection Created](/images/app-connections/checkly/checkly-app-connection-generated.png) @@ -75,6 +75,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user --data '{ "name": "my-checkly-connection", "method": "api-key", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "apiKey": "[API KEY]" } @@ -88,6 +89,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-checkly-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/cloudflare.mdx b/docs/integrations/app-connections/cloudflare.mdx index 241c737bc..33a9a992c 100644 --- a/docs/integrations/app-connections/cloudflare.mdx +++ b/docs/integrations/app-connections/cloudflare.mdx @@ -88,8 +88,7 @@ Infisical supports connecting to Cloudflare using API tokens and Account ID for - Navigate to the **App Connections** tab on the **Organization Settings** - page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/databricks.mdx b/docs/integrations/app-connections/databricks.mdx index 38d125ea6..e0b861184 100644 --- a/docs/integrations/app-connections/databricks.mdx +++ b/docs/integrations/app-connections/databricks.mdx @@ -43,8 +43,7 @@ Infisical supports the use of [service principals](https://docs.databricks.com/e - Navigate to the **App Connections** tab on the **Organization Settings** - page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/digital-ocean.mdx b/docs/integrations/app-connections/digital-ocean.mdx index 5b047f017..ff2eccfc1 100644 --- a/docs/integrations/app-connections/digital-ocean.mdx +++ b/docs/integrations/app-connections/digital-ocean.mdx @@ -45,7 +45,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun - In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -63,7 +63,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun ![DigitalOcean Connection Modal](/images/app-connections/digital-ocean/app-connection-form.png) - After submitting the form, your **DigitalOcean Connection** will be successfully created and ready to use with your Infisical projects. + After submitting the form, your **DigitalOcean Connection** will be successfully created and ready to use with your Infisical project. ![DigitalOcean Connection Created](/images/app-connections/digital-ocean/app-connection-generated.png) @@ -82,6 +82,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun --data '{ "name": "my-digitalocean-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "apiToken": "[API TOKEN]" } @@ -95,6 +96,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun "appConnection": { "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "my-digitalocean-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "abcdef12-3456-7890-abcd-ef1234567890", diff --git a/docs/integrations/app-connections/flyio.mdx b/docs/integrations/app-connections/flyio.mdx index e42756254..bf36ccc9b 100644 --- a/docs/integrations/app-connections/flyio.mdx +++ b/docs/integrations/app-connections/flyio.mdx @@ -30,7 +30,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -48,7 +48,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token ![Fly.io Connection Modal](/images/app-connections/flyio/app-connection-modal.png) - After clicking Create, your **Fly.io Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **Fly.io Connection** is established and ready to use with your Infisical project. ![Fly.io Connection Created](/images/app-connections/flyio/app-connection-created.png) @@ -66,6 +66,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token --data '{ "name": "my-flyio-connection", "method": "access-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "accessToken": "[PRIVATE TOKEN]" } @@ -79,6 +80,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-flyio-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/gcp.mdx b/docs/integrations/app-connections/gcp.mdx index 129c26c2a..9458339af 100644 --- a/docs/integrations/app-connections/gcp.mdx +++ b/docs/integrations/app-connections/gcp.mdx @@ -82,8 +82,7 @@ Infisical supports [service account impersonation](https://cloud.google.com/iam/ - Navigate to the **App Connections** tab on the **Organization Settings** - page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/github-radar.mdx b/docs/integrations/app-connections/github-radar.mdx index 376973efd..491070a8b 100644 --- a/docs/integrations/app-connections/github-radar.mdx +++ b/docs/integrations/app-connections/github-radar.mdx @@ -97,7 +97,7 @@ Infisical supports GitHub App installation for creating a GitHub Radar Connectio - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/github.mdx b/docs/integrations/app-connections/github.mdx index 9a952f815..e44fc405a 100644 --- a/docs/integrations/app-connections/github.mdx +++ b/docs/integrations/app-connections/github.mdx @@ -85,7 +85,7 @@ Infisical supports two methods for connecting to GitHub. - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -156,7 +156,7 @@ Infisical supports two methods for connecting to GitHub. - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/gitlab.mdx b/docs/integrations/app-connections/gitlab.mdx index 60e9236ca..4f7223d93 100644 --- a/docs/integrations/app-connections/gitlab.mdx +++ b/docs/integrations/app-connections/gitlab.mdx @@ -70,7 +70,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -193,7 +193,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/hashicorp-vault.mdx b/docs/integrations/app-connections/hashicorp-vault.mdx index c49b53ab8..7d5502fc5 100644 --- a/docs/integrations/app-connections/hashicorp-vault.mdx +++ b/docs/integrations/app-connections/hashicorp-vault.mdx @@ -131,7 +131,7 @@ Infisical supports two methods for connecting to Hashicorp Vault. - In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -184,6 +184,7 @@ Infisical supports two methods for connecting to Hashicorp Vault. --data '{ "name": "my-vault-connection", "method": "app-role", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "instanceUrl": "https://vault.example.com", "roleId": "4797c4fa-7794-71f0-c8b1-7c87759df5bf", @@ -199,6 +200,7 @@ Infisical supports two methods for connecting to Hashicorp Vault. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-vault-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2025-04-01T05:31:56Z", diff --git a/docs/integrations/app-connections/heroku.mdx b/docs/integrations/app-connections/heroku.mdx index fc2d1fbcc..9c3397e02 100644 --- a/docs/integrations/app-connections/heroku.mdx +++ b/docs/integrations/app-connections/heroku.mdx @@ -51,7 +51,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -93,7 +93,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To ![Heroku API Token](/images/app-connections/heroku/heroku-api-token.png) - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/humanitec.mdx b/docs/integrations/app-connections/humanitec.mdx index 570d3ba5d..669798eeb 100644 --- a/docs/integrations/app-connections/humanitec.mdx +++ b/docs/integrations/app-connections/humanitec.mdx @@ -53,7 +53,7 @@ Infisical supports connecting to Humanitec using a service user. ![Humanitec Connection Created](/images/app-connections/humanitec/humanitec-user-added.png) - Navigate to the **App Connections** tab on the **Organization Settings** page. + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/ldap.mdx b/docs/integrations/app-connections/ldap.mdx index db0b596ce..f0afa1157 100644 --- a/docs/integrations/app-connections/ldap.mdx +++ b/docs/integrations/app-connections/ldap.mdx @@ -33,7 +33,7 @@ Depending on how you intend to use your LDAP connection, there may be additional - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **LDAP Connection** option. @@ -58,6 +58,7 @@ Depending on how you intend to use your LDAP connection, there may be additional --data '{ "name": "my-ldap-connection", "method": "simple-bind", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "provider": "active-directory", "url": "ldaps://domain-or-ip:636", @@ -76,6 +77,7 @@ Depending on how you intend to use your LDAP connection, there may be additional "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-ldap-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/mssql.mdx b/docs/integrations/app-connections/mssql.mdx index 7e940804d..77e8e676d 100644 --- a/docs/integrations/app-connections/mssql.mdx +++ b/docs/integrations/app-connections/mssql.mdx @@ -62,7 +62,7 @@ Infisical supports connecting to Microsoft SQL Server using database principals. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **Microsoft SQL Server Connection** option. @@ -96,6 +96,7 @@ Infisical supports connecting to Microsoft SQL Server using database principals. --data '{ "name": "my-mssql-connection", "method": "username-and-password", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "isPlatformManagedCredentials": true, "credentials": { "host": "123.4.5.6", @@ -115,7 +116,8 @@ Infisical supports connecting to Microsoft SQL Server using database principals. { "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "name": "my-pg-connection", + "name": "my-mssql-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/mysql.mdx b/docs/integrations/app-connections/mysql.mdx index 38a8a4e97..6055d77cc 100644 --- a/docs/integrations/app-connections/mysql.mdx +++ b/docs/integrations/app-connections/mysql.mdx @@ -52,7 +52,7 @@ Infisical supports connecting to MySQL using a database role. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **MySQL Connection** option. @@ -88,6 +88,7 @@ Infisical supports connecting to MySQL using a database role. "name": "my-mysql-connection", "method": "username-and-password", "isPlatformManagedCredentials": true, + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "host": "123.4.5.6", "port": 3306, @@ -107,6 +108,7 @@ Infisical supports connecting to MySQL using a database role. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-mysql-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/netlify.mdx b/docs/integrations/app-connections/netlify.mdx index cd4dfb1b5..d2a62113c 100644 --- a/docs/integrations/app-connections/netlify.mdx +++ b/docs/integrations/app-connections/netlify.mdx @@ -35,7 +35,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/ - In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -53,7 +53,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/ ![Netlify Connection Modal](/images/app-connections/netlify/app-connection-form.png) - After submitting the form, your **Netlify Connection** will be successfully created and ready to use with your Infisical projects. + After submitting the form, your **Netlify Connection** will be successfully created and ready to use with your Infisical project. ![Netlify Connection Created](/images/app-connections/netlify/app-connection-generated.png) @@ -72,6 +72,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/ --data '{ "name": "my-netlify-connection", "method": "access-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "accessToken": "[ACCESS TOKEN]" } @@ -86,6 +87,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/ "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "name": "my-netlify-connection", "description": null, + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "abcdef12-3456-7890-abcd-ef1234567890", "createdAt": "2025-07-19T10:15:00.000Z", diff --git a/docs/integrations/app-connections/oci.mdx b/docs/integrations/app-connections/oci.mdx index 58fb3c1d3..10d5fabaa 100644 --- a/docs/integrations/app-connections/oci.mdx +++ b/docs/integrations/app-connections/oci.mdx @@ -117,7 +117,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -139,7 +139,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac ![OCI Connection Modal](/images/app-connections/oci/app-connection-modal.png) - After clicking Create, your **OCI Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **OCI Connection** is established and ready to use with your Infisical project. ![OCI Connection Created](/images/app-connections/oci/app-connection-created.png) @@ -157,6 +157,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac --data '{ "name": "my-oci-connection", "method": "access-key", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "userOcid": "ocid1.user.oc1..aaaaaaaagrp35tbkvvad4y2j7sug7xonua7dl2gfp4at2u5i5xj4ghnitg3a", "tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaotfma465m4zumfe2ua64mj2m5dwmlw2llh4g4dnfttnakiifonta", @@ -174,6 +175,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-oci-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/okta.mdx b/docs/integrations/app-connections/okta.mdx index 3c1295cf8..cb5edecbd 100644 --- a/docs/integrations/app-connections/okta.mdx +++ b/docs/integrations/app-connections/okta.mdx @@ -31,7 +31,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -48,7 +48,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide ![Connection Modal](/images/app-connections/okta/step-4.png) - After clicking Create, your **Okta Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **Okta Connection** is established and ready to use with your Infisical project. ![Connection Created](/images/app-connections/okta/step-5.png) @@ -66,6 +66,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide --data '{ "name": "my-okta-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "instanceUrl": "https://example.okta.com", "apiToken": "" @@ -80,6 +81,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-okta-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/oracledb.mdx b/docs/integrations/app-connections/oracledb.mdx index 8cab6371b..47aa33356 100644 --- a/docs/integrations/app-connections/oracledb.mdx +++ b/docs/integrations/app-connections/oracledb.mdx @@ -62,7 +62,7 @@ Infisical supports connecting to OracleDB using a database user. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **OracleDB Connection** option. @@ -98,6 +98,7 @@ Infisical supports connecting to OracleDB using a database user. "name": "my-oracledb-connection", "method": "username-and-password", "isPlatformManagedCredentials": true, + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "host": "123.4.5.6", "port": 1521, @@ -117,6 +118,7 @@ Infisical supports connecting to OracleDB using a database user. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-oracledb-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/overview.mdx b/docs/integrations/app-connections/overview.mdx index e698c4302..8b1032e7d 100644 --- a/docs/integrations/app-connections/overview.mdx +++ b/docs/integrations/app-connections/overview.mdx @@ -3,12 +3,16 @@ sidebarTitle: "Overview" description: "Learn how to manage and configure third-party app connections with Infisical." --- -App Connections enable your organization to integrate Infisical with third-party services in a secure and versatile way. +App Connections enable you to integrate your Infisical projects with third-party services in a secure and versatile way. + + + App connections can also be created and managed independently in projects now. + ## Concept -App Connections are an organization-level resource used to establish connections with third-party applications -that can be used across Infisical projects. Example use cases include syncing secrets, generating dynamic secrets, and more. +App Connections can be used to establish connections with third-party applications +that can be used across multiple features. Example use cases include syncing secrets, rotating credentials, scanning repositories for secret leaks, and more.
diff --git a/docs/integrations/app-connections/postgres.mdx b/docs/integrations/app-connections/postgres.mdx index 239608905..dcb6c76d8 100644 --- a/docs/integrations/app-connections/postgres.mdx +++ b/docs/integrations/app-connections/postgres.mdx @@ -60,7 +60,7 @@ Infisical supports connecting to PostgreSQL using a database role. - 1. Navigate to the App Connections tab on the Organization Settings page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **PostgreSQL Connection** option. @@ -95,6 +95,7 @@ Infisical supports connecting to PostgreSQL using a database role. "name": "my-pg-connection", "method": "username-and-password", "isPlatformManagedCredentials": true, + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "host": "123.4.5.6", "port": 5432, @@ -114,6 +115,7 @@ Infisical supports connecting to PostgreSQL using a database role. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-pg-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 1, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/railway.mdx b/docs/integrations/app-connections/railway.mdx index 7b53d02ad..b88b3fbf1 100644 --- a/docs/integrations/app-connections/railway.mdx +++ b/docs/integrations/app-connections/railway.mdx @@ -96,7 +96,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi - In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -115,7 +115,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi ![Railway Connection Modal](/images/app-connections/railway/railway-app-connection-form.png)
- After submitting the form, your **Railway Connection** will be successfully created and ready to use with your Infisical projects. + After submitting the form, your **Railway Connection** will be successfully created and ready to use with your Infisical project. ![Railway Connection Created](/images/app-connections/railway/railway-app-connection-generated.png) @@ -134,6 +134,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi --data '{ "name": "my-railway-connection", "method": "team-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "apiToken": "[TEAM TOKEN]" } @@ -147,6 +148,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-railway-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/render.mdx b/docs/integrations/app-connections/render.mdx index 580ec953e..78e050135 100644 --- a/docs/integrations/app-connections/render.mdx +++ b/docs/integrations/app-connections/render.mdx @@ -33,8 +33,7 @@ Infisical supports connecting to Render using API keys for secure access to your - Navigate to the **App Connections** tab on the **Organization Settings** - page. ![App Connections + Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) diff --git a/docs/integrations/app-connections/supabase.mdx b/docs/integrations/app-connections/supabase.mdx index 9716b1526..80290cec9 100644 --- a/docs/integrations/app-connections/supabase.mdx +++ b/docs/integrations/app-connections/supabase.mdx @@ -34,7 +34,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash - In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -53,7 +53,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash ![Supabase Connection Modal](/images/app-connections/supabase/app-connection-form.png) - After submitting the form, your **Supabase Connection** will be successfully created and ready to use with your Infisical projects. + After submitting the form, your **Supabase Connection** will be successfully created and ready to use with your Infisical project. ![Supabase Connection Created](/images/app-connections/supabase/app-connection-generated.png) @@ -73,6 +73,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash --data '{ "name": "my-supabase-connection", "method": "access-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "accessToken": "[Access Token]", "instanceUrl": "https://api.supabase.com" @@ -87,6 +88,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-supabase-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/teamcity.mdx b/docs/integrations/app-connections/teamcity.mdx index 889355954..311326f07 100644 --- a/docs/integrations/app-connections/teamcity.mdx +++ b/docs/integrations/app-connections/teamcity.mdx @@ -51,7 +51,7 @@ Infisical supports connecting to TeamCity using Access Tokens. 1. Navigate to App Connections - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Add Connection @@ -68,7 +68,7 @@ Infisical supports connecting to TeamCity using Access Tokens. ![TeamCity Connection Modal](/images/app-connections/teamcity/teamcity-app-connection-modal.png) 4. Connection Created - After clicking Create, your **TeamCity Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **TeamCity Connection** is established and ready to use with your Infisical project. ![TeamCity Connection Created](/images/app-connections/teamcity/teamcity-app-connection-created.png) @@ -84,6 +84,7 @@ Infisical supports connecting to TeamCity using Access Tokens. --data '{ "name": "my-teamcity-connection", "method": "access-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "accessToken": "...", "instanceUrl": "https://yourcompany.teamcity.com" @@ -98,6 +99,7 @@ Infisical supports connecting to TeamCity using Access Tokens. "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-teamcity-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/integrations/app-connections/terraform-cloud.mdx b/docs/integrations/app-connections/terraform-cloud.mdx index 02deb22cc..bc3da7810 100644 --- a/docs/integrations/app-connections/terraform-cloud.mdx +++ b/docs/integrations/app-connections/terraform-cloud.mdx @@ -30,7 +30,7 @@ Infisical supports connecting to Terraform Cloud using a service user. - 1. Navigate to the **App Connections** tab on the **Organization Settings** page. + 1. Navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Select the **Terraform Cloud Connection** option from the connection options modal. ![Select Terraform Cloud Connection](/images/app-connections/terraform-cloud/terraform-cloud-app-connection-option.png) @@ -52,6 +52,7 @@ Infisical supports connecting to Terraform Cloud using a service user. --data '{ "name": "my-terraform-cloud-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "apiToken": "...", } @@ -65,6 +66,7 @@ Infisical supports connecting to Terraform Cloud using a service user. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-terraform-cloud-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 123, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-11-07T05:31:56Z", diff --git a/docs/integrations/app-connections/vercel.mdx b/docs/integrations/app-connections/vercel.mdx index 7ab7bea1b..17973db9b 100644 --- a/docs/integrations/app-connections/vercel.mdx +++ b/docs/integrations/app-connections/vercel.mdx @@ -37,7 +37,7 @@ Infisical supports connecting to Vercel using API Tokens. 1. Navigate to App Connections - In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) 2. Add Connection @@ -52,7 +52,7 @@ Infisical supports connecting to Vercel using API Tokens. ![Vercel Connection Modal](/images/app-connections/vercel/vercel-app-connection-modal.png) 4. Connection Created - After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical project. ![Vercel Connection Created](/images/app-connections/vercel/vercel-app-connection-created.png) @@ -67,6 +67,7 @@ Infisical supports connecting to Vercel using API Tokens. --header 'Content-Type: application/json' \ --data '{ "name": "my-vercel-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "method": "api-token", "credentials": { "apiToken": "...", @@ -81,6 +82,7 @@ Infisical supports connecting to Vercel using API Tokens. "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-vercel-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 123, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2025-04-01T05:31:56Z", diff --git a/docs/integrations/app-connections/windmill.mdx b/docs/integrations/app-connections/windmill.mdx index 5cab9fa38..d90c83a1b 100644 --- a/docs/integrations/app-connections/windmill.mdx +++ b/docs/integrations/app-connections/windmill.mdx @@ -47,7 +47,8 @@ Ensure the user generating the access token has the required role and permission - In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. + ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -82,6 +83,7 @@ Ensure the user generating the access token has the required role and permission --data '{ "name": "my-windmill-connection", "method": "access-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "token": "...", "instanceUrl": "https://app.windmill.dev" @@ -96,6 +98,7 @@ Ensure the user generating the access token has the required role and permission "appConnection": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "my-windmill-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "version": 123, "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2025-04-01T05:31:56Z", diff --git a/docs/integrations/app-connections/zabbix.mdx b/docs/integrations/app-connections/zabbix.mdx index c4d47b22e..45141e827 100644 --- a/docs/integrations/app-connections/zabbix.mdx +++ b/docs/integrations/app-connections/zabbix.mdx @@ -31,7 +31,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/ - In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab. + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. ![App Connections Tab](/images/app-connections/general/add-connection.png) @@ -50,7 +50,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/ ![Zabbix Connection Modal](/images/app-connections/zabbix/zabbix-app-connection-form.png) - After clicking Create, your **Zabbix Connection** is established and ready to use with your Infisical projects. + After clicking Create, your **Zabbix Connection** is established and ready to use with your Infisical project. ![Zabbix Connection Created](/images/app-connections/zabbix/zabbix-app-connection-generated.png) @@ -68,6 +68,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/ --data '{ "name": "my-zabbix-connection", "method": "api-token", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "credentials": { "apiToken": "[API TOKEN]", "instanceUrl": "https://zabbix.example.com" @@ -82,6 +83,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/ "appConnection": { "id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6", "name": "my-zabbix-connection", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", "description": null, "version": 1, "orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c", diff --git a/docs/internals/permissions/organization-permissions.mdx b/docs/internals/permissions/organization-permissions.mdx index 5f5fc962f..d20ea7def 100644 --- a/docs/internals/permissions/organization-permissions.mdx +++ b/docs/internals/permissions/organization-permissions.mdx @@ -9,7 +9,7 @@ Infisical's organization permissions system follows a role-based access control Each permission consists of: -- **Subject**: The resource the permission applies to (e.g., workspaces, members, billing) +- **Subject**: The resource the permission applies to (e.g., project, members, billing) - **Action**: The operation that can be performed (e.g., read, create, edit, delete) Some organization-level resources—specifically `app-connections`—support conditional permissions and permission inversion for more granular access control. @@ -18,13 +18,13 @@ Some organization-level resources—specifically `app-connections`—support con Below is a comprehensive list of all available organization-level subjects and their supported actions, organized by functional area. -### Workspace Management +### Project Management -#### Subject: `workspace` +#### Subject: `project` (formerly workspace) -| Action | Description | -| -------- | --------------------- | -| `create` | Create new workspaces | +| Action | Description | +| -------- | ------------------ | +| `create` | Create new project | ### Role Management diff --git a/docs/internals/permissions/project-permissions.mdx b/docs/internals/permissions/project-permissions.mdx index c823cf4a7..3a4dd11e6 100644 --- a/docs/internals/permissions/project-permissions.mdx +++ b/docs/internals/permissions/project-permissions.mdx @@ -86,7 +86,7 @@ Below is a comprehensive list of all available project-level subjects and their | `edit` | Modify existing tags | | `delete` | Remove tags from the project | -#### Subject: `workspace` +#### Subject: `project` | Action | Description | | -------- | ------------------------- | @@ -135,6 +135,18 @@ Below is a comprehensive list of all available project-level subjects and their | `edit` | Modify token properties | | `delete` | Revoke or remove service tokens | +#### Subject: `app-connections` + +Supports conditions and permission inversion + +| Action | Description | +| ------------------------- | ---------------------------------- | +| `read-app-connections` | View app connection configurations | +| `create-app-connections` | Create new app connections | +| `edit-app-connections` | Modify existing app connections | +| `delete-app-connections` | Remove app connections | +| `connect-app-connections` | Use app connections | + ### Secrets Management #### Subject: `secrets` diff --git a/frontend/src/components/app-connections/AppConnectionOption.tsx b/frontend/src/components/app-connections/AppConnectionOption.tsx new file mode 100644 index 000000000..2978dc9f0 --- /dev/null +++ b/frontend/src/components/app-connections/AppConnectionOption.tsx @@ -0,0 +1,45 @@ +import { components, OptionProps } from "react-select"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { faBuilding, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Badge, Tooltip } from "@app/components/v2"; +import { TAvailableAppConnection } from "@app/hooks/api/appConnections"; + +export const AppConnectionOption = ({ + isSelected, + children, + ...props +}: OptionProps) => { + const isCreateOption = props.data.id === "_create"; + + return ( + +
+ {isCreateOption ? ( +
+ + Create New Connection +
+ ) : ( + <> +

{children}

+ {!props.data.projectId && ( + +
+ + + Organization + +
+
+ )} + {isSelected && ( + + )} + + )} +
+
+ ); +}; diff --git a/frontend/src/components/app-connections/index.ts b/frontend/src/components/app-connections/index.ts new file mode 100644 index 000000000..7ced7c907 --- /dev/null +++ b/frontend/src/components/app-connections/index.ts @@ -0,0 +1 @@ +export * from "./AppConnectionOption"; diff --git a/frontend/src/components/navigation/NavHeader.tsx b/frontend/src/components/navigation/NavHeader.tsx index 1283c02d1..b16015680 100644 --- a/frontend/src/components/navigation/NavHeader.tsx +++ b/frontend/src/components/navigation/NavHeader.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useParams } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import { useOrganization, useWorkspace } from "@app/context"; +import { useOrganization, useProject } from "@app/context"; import { useToggle } from "@app/hooks"; import { createNotification } from "../notifications"; @@ -51,7 +51,7 @@ export default function NavHeader({ isProtectedBranch = false, protectionPolicyName }: Props): JSX.Element { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { currentOrg } = useOrganization(); const [isCopied, { timedToggle: toggleIsCopied }] = useToggle(false); @@ -79,7 +79,7 @@ export default function NavHeader({ <>
- {currentWorkspace?.name} + {currentProject?.name}
)} @@ -93,7 +93,7 @@ export default function NavHeader({ {pageName === "Secrets" ? ( {pageName} @@ -129,7 +129,7 @@ export default function NavHeader({ {userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name} @@ -192,7 +192,7 @@ export default function NavHeader({ ({ ...query, secretPath: newSecretPath })} diff --git a/frontend/src/components/permissions/AccessTree/hooks/index.ts b/frontend/src/components/permissions/AccessTree/hooks/index.ts index 717bdaf3a..b02e6a9d6 100644 --- a/frontend/src/components/permissions/AccessTree/hooks/index.ts +++ b/frontend/src/components/permissions/AccessTree/hooks/index.ts @@ -3,7 +3,7 @@ import { useFormContext, useWatch } from "react-hook-form"; import { MongoAbility, MongoQuery } from "@casl/ability"; import { Edge, Node, useEdgesState, useNodesState } from "@xyflow/react"; -import { ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { useListProjectEnvironmentsFolders } from "@app/hooks/api/secretFolders/queries"; import { TSecretFolderWithPath } from "@app/hooks/api/secretFolders/types"; @@ -36,15 +36,15 @@ export const useAccessTree = ( searchPath: string, subject: ProjectPermissionSub ) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { secretName, setSecretName, setViewMode, viewMode } = useAccessTreeContext(); const { control } = useFormContext(); const metadata = useWatch({ control, name: "metadata" }); const [nodes, setNodes] = useNodesState([]); const [edges, setEdges] = useEdgesState([]); - const [environment, setEnvironment] = useState(currentWorkspace.environments[0]?.slug ?? ""); + const [environment, setEnvironment] = useState(currentProject.environments[0]?.slug ?? ""); const { data: environmentsFolders, isPending } = useListProjectEnvironmentsFolders( - currentWorkspace.id + currentProject.id ); const [levelFolderMap, setLevelFolderMap] = useState({}); @@ -279,7 +279,7 @@ export const useAccessTree = ( environment, setEnvironment, isLoading: isPending, - environments: currentWorkspace.environments, + environments: currentProject.environments, secretName, setSecretName, viewMode, diff --git a/frontend/src/components/permissions/OrgPermissionCan.tsx b/frontend/src/components/permissions/OrgPermissionCan.tsx index 8e0bf08ad..bdb39ba1b 100644 --- a/frontend/src/components/permissions/OrgPermissionCan.tsx +++ b/frontend/src/components/permissions/OrgPermissionCan.tsx @@ -1,8 +1,10 @@ import { FunctionComponent, ReactNode } from "react"; -import { BoundCanProps, Can } from "@casl/react"; +import { AbilityTuple, MongoAbility } from "@casl/ability"; +import { Can } from "@casl/react"; import { TooltipProps } from "@app/components/v2/Tooltip/Tooltip"; -import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext"; +import { useOrgPermission } from "@app/context/OrgPermissionContext"; +import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types"; import { AccessRestrictedBanner, Tooltip } from "../v2"; @@ -14,7 +16,7 @@ export const OrgPermissionGuardBanner = () => { ); }; -type Props = { +type Props = { label?: ReactNode; // this prop is used when there exist already a tooltip as helper text for users // so when permission is allowed same tooltip will be reused to show helpertext @@ -22,9 +24,18 @@ type Props = { allowedLabel?: string; renderGuardBanner?: boolean; tooltipProps?: Omit; -} & BoundCanProps; + I: T[0]; + ability?: MongoAbility; + children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode); + passThrough?: boolean; +} & ( + | { an: T[1] } + | { + a: T[1]; + } +); -export const OrgPermissionCan: FunctionComponent = ({ +export const OrgPermissionCan: FunctionComponent> = ({ label = "Access restricted", children, passThrough = true, @@ -41,9 +52,7 @@ export const OrgPermissionCan: FunctionComponent = ({ {(isAllowed, ability) => { // akhilmhdh: This is set as type due to error in casl react type. const finalChild = - typeof children === "function" - ? children(isAllowed, ability as TOrgPermission) - : children; + typeof children === "function" ? children(isAllowed, ability as any) : children; if (!isAllowed && passThrough) { return ( diff --git a/frontend/src/components/permissions/VariablePermissionCan.tsx b/frontend/src/components/permissions/VariablePermissionCan.tsx new file mode 100644 index 000000000..a9e24e3c7 --- /dev/null +++ b/frontend/src/components/permissions/VariablePermissionCan.tsx @@ -0,0 +1,17 @@ +import { OrgPermissionCan } from "./OrgPermissionCan"; +import { ProjectPermissionCan } from "./ProjectPermissionCan"; + +interface PermissionCanProps { + type: "project" | "org"; + I: any; + a: any; + children: (isAllowed: boolean, ability?: any) => React.ReactNode; +} + +export const VariablePermissionCan = ({ type, children, ...props }: PermissionCanProps) => { + if (type === "project") { + return {children}; + } + + return {children}; +}; diff --git a/frontend/src/components/permissions/index.tsx b/frontend/src/components/permissions/index.tsx index c40079a4f..db6ca3296 100644 --- a/frontend/src/components/permissions/index.tsx +++ b/frontend/src/components/permissions/index.tsx @@ -3,3 +3,4 @@ export { GlobPermissionInfo } from "./GlobPermissionInfo"; export { OrgPermissionCan } from "./OrgPermissionCan"; export { PermissionDeniedBanner } from "./PermissionDeniedBanner"; export { ProjectPermissionCan } from "./ProjectPermissionCan"; +export * from "./VariablePermissionCan"; diff --git a/frontend/src/components/project/ProjectOverviewChangeSection.tsx b/frontend/src/components/project/ProjectOverviewChangeSection.tsx index 0f88ec2e9..852253bb3 100644 --- a/frontend/src/components/project/ProjectOverviewChangeSection.tsx +++ b/frontend/src/components/project/ProjectOverviewChangeSection.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Input, TextArea } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useUpdateProject } from "@app/hooks/api"; const baseFormSchema = z.object({ @@ -37,35 +37,35 @@ type Props = { }; export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync, isPending } = useUpdateProject(); const { handleSubmit, control, reset, watch } = useForm({ resolver: zodResolver(showSlugField ? formSchemaWithSlug : baseFormSchema) }); - const currentSlug = showSlugField ? watch("slug") : currentWorkspace?.slug; + const currentSlug = showSlugField ? watch("slug") : currentProject?.slug; useEffect(() => { - if (currentWorkspace) { + if (currentProject) { reset({ - name: currentWorkspace.name, - description: currentWorkspace.description ?? "", - ...(showSlugField && { slug: currentWorkspace.slug }) + name: currentProject.name, + description: currentProject.description ?? "", + ...(showSlugField && { slug: currentProject.slug }) }); } - }, [currentWorkspace, showSlugField]); + }, [currentProject, showSlugField]); const onFormSubmit = async (data: BaseFormData | FormDataWithSlug) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await mutateAsync({ - projectID: currentWorkspace.id, + projectId: currentProject.id, newProjectName: data.name, newProjectDescription: data.description, ...(showSlugField && "slug" in data && { - newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined + newSlug: data.slug !== currentProject.slug ? data.slug : undefined }) }); @@ -105,7 +105,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) = variant="outline_bg" size="sm" onClick={() => { - navigator.clipboard.writeText(currentWorkspace?.id || ""); + navigator.clipboard.writeText(currentProject?.id || ""); createNotification({ text: "Copied project ID to clipboard", type: "success" diff --git a/frontend/src/components/projects/NewProjectModal.tsx b/frontend/src/components/projects/NewProjectModal.tsx index 1bbe9f6f1..dcb11d873 100644 --- a/frontend/src/components/projects/NewProjectModal.tsx +++ b/frontend/src/components/projects/NewProjectModal.tsx @@ -34,10 +34,10 @@ import { useUser } from "@app/context"; import { getProjectHomePage, getProjectLottieIcon } from "@app/helpers/project"; -import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } from "@app/hooks/api"; +import { useCreateWorkspace, useGetExternalKmsList, useGetUserProjects } from "@app/hooks/api"; import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates"; -import { ProjectType } from "@app/hooks/api/workspace/types"; const formSchema = z.object({ name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"), @@ -89,7 +89,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { const { permission } = useOrgPermission(); const { user } = useUser(); const createWs = useCreateWorkspace(); - const { refetch: refetchWorkspaces } = useGetUserWorkspaces(); + const { refetch: refetchWorkspaces } = useGetUserProjects(); const { subscription } = useSubscription(); const canReadProjectTemplates = permission.can( diff --git a/frontend/src/components/projects/RequestProjectAccessModal.tsx b/frontend/src/components/projects/RequestProjectAccessModal.tsx new file mode 100644 index 000000000..6d31bf8e6 --- /dev/null +++ b/frontend/src/components/projects/RequestProjectAccessModal.tsx @@ -0,0 +1,88 @@ +import { useForm } from "react-hook-form"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2"; +import { useRequestProjectAccess } from "@app/hooks/api"; +import { Project } from "@app/hooks/api/projects/types"; + +type ContentProps = { + projectId: string; + onComplete: () => void; +}; + +const Content = ({ projectId, onComplete }: ContentProps) => { + const form = useForm<{ note: string }>(); + + const requestProjectAccess = useRequestProjectAccess(); + + const onFormSubmit = ({ note }: { note: string }) => { + if (requestProjectAccess.isPending) return; + requestProjectAccess.mutate( + { + comment: note, + projectId + }, + { + onSuccess: () => { + createNotification({ + type: "success", + title: "Project Access Request Sent", + text: "Project admins will receive an email of your request" + }); + onComplete(); + } + } + ); + }; + + return ( +
+ + + +
+ + + + +
+
+ ); +}; + +type RequestProjectAccessModalProps = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + project?: Project; + onComplete?: () => void; +}; + +export const RequestProjectAccessModal = ({ + isOpen, + onOpenChange, + project, + onComplete +}: RequestProjectAccessModalProps) => { + if (!project) return null; + + return ( + + + { + onOpenChange(false); + if (onComplete) onComplete(); + }} + projectId={project?.id} + /> + + + ); +}; diff --git a/frontend/src/components/projects/index.tsx b/frontend/src/components/projects/index.tsx index 1fb78225d..a2dc754ad 100644 --- a/frontend/src/components/projects/index.tsx +++ b/frontend/src/components/projects/index.tsx @@ -1 +1,2 @@ export { NewProjectModal } from "./NewProjectModal"; +export * from "./RequestProjectAccessModal"; diff --git a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx index f5b9a39b3..41f378b94 100644 --- a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx @@ -1,18 +1,20 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useRouterState } from "@tanstack/react-router"; import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms"; +import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas"; import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader"; import { SecretRotationV2Select } from "@app/components/secret-rotations-v2/SecretRotationV2Select"; import { Modal, ModalContent } from "@app/components/v2"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; import { SecretRotation, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; type SharedProps = { secretPath: string; environment?: string; - environments?: WorkspaceEnv[]; + environments?: ProjectEnv[]; }; type Props = { @@ -24,14 +26,23 @@ type ContentProps = { onComplete: (secretRotation: TSecretRotationV2) => void; selectedRotation: SecretRotation | null; setSelectedRotation: (selectedRotation: SecretRotation | null) => void; + initialFormData?: Partial; + onCancel: () => void; } & SharedProps; -const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentProps) => { +const Content = ({ + setSelectedRotation, + selectedRotation, + initialFormData, + onCancel, + ...props +}: ContentProps) => { if (selectedRotation) { return ( setSelectedRotation(null)} + onCancel={onCancel} type={selectedRotation} + initialFormData={initialFormData} {...props} /> ); @@ -42,12 +53,60 @@ const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentPro export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: Props) => { const [selectedRotation, setSelectedRotation] = useState(null); + const [initialFormData, setInitialFormData] = useState>(); + + const { + location: { + search: { connectionId, connectionName, ...search }, + pathname + } + } = useRouterState(); + + const navigate = useNavigate(); + + useEffect(() => { + if (connectionId && connectionName) { + const storedFormData = localStorage.getItem("secretRotationFormData"); + + if (!storedFormData) return; + + let form: Partial = {}; + try { + form = JSON.parse(storedFormData) as TSecretRotationV2Form; + } catch { + return; + } finally { + localStorage.removeItem("secretRotationFormData"); + } + + onOpenChange(true); + + setSelectedRotation(form.type ?? null); + + setInitialFormData({ + ...form, + connection: { id: connectionId, name: connectionName } + }); + + navigate({ + to: pathname, + search + }); + } + }, [connectionId, connectionName]); + + const handleReset = () => { + setSelectedRotation(null); + setInitialFormData(undefined); + }; return ( { - if (!open) setSelectedRotation(null); + if (!open) { + handleReset(); + } onOpenChange(open); }} > @@ -84,9 +143,11 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: > { - setSelectedRotation(null); + handleReset(); onOpenChange(false); }} + onCancel={handleReset} + initialFormData={initialFormData} selectedRotation={selectedRotation} setSelectedRotation={setSelectedRotation} {...props} diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConfigurationFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConfigurationFields.tsx index 1906b743d..e518752c4 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConfigurationFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConfigurationFields.tsx @@ -9,14 +9,14 @@ import { IS_ROTATION_DUAL_CREDENTIALS, SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2"; -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; import { TSecretRotationV2Form } from "./schemas"; import { SecretRotationV2ConnectionField } from "./SecretRotationV2ConnectionField"; type Props = { isUpdate: boolean; - environments?: WorkspaceEnv[]; + environments?: ProjectEnv[]; }; export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => { diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx index 665ab05a6..c2064071f 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx @@ -1,14 +1,17 @@ import { Controller, useFormContext } from "react-hook-form"; +import { SingleValue } from "react-select"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "@tanstack/react-router"; +import { AppConnectionOption } from "@app/components/app-connections"; import { FilterableSelect, FormControl } from "@app/components/v2"; -import { OrgPermissionSubjects, useOrgPermission } from "@app/context"; -import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; +import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context"; +import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { SECRET_ROTATION_CONNECTION_MAP } from "@app/helpers/secretRotationsV2"; +import { usePopUp } from "@app/hooks"; import { useListAvailableAppConnections } from "@app/hooks/api/appConnections"; +import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; import { TSecretRotationV2Form } from "./schemas"; @@ -18,19 +21,26 @@ type Props = { }; export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }: Props) => { - const { permission } = useOrgPermission(); - const { control, watch } = useFormContext(); + const { permission } = useProjectPermission(); + const { control, watch, setValue } = useFormContext(); + + const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const); const rotationType = watch("type"); const app = SECRET_ROTATION_CONNECTION_MAP[rotationType]; - const { data: availableConnections, isPending } = useListAvailableAppConnections(app); + const { currentProject } = useProject(); + + const { data: availableConnections, isPending } = useListAvailableAppConnections( + app, + currentProject.id + ); const connectionName = APP_CONNECTION_MAP[app].name; const canCreateConnection = permission.can( - OrgPermissionAppConnectionActions.Create, - OrgPermissionSubjects.AppConnections + ProjectPermissionAppConnectionActions.Create, + ProjectPermissionSub.AppConnections ); const appName = APP_CONNECTION_MAP[app].name; @@ -66,37 +76,56 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate } { + if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") { + handlePopUpOpen("addConnection"); + onChange(null); + // store for oauth callback connections + localStorage.setItem("secretRotationFormData", JSON.stringify(watch())); + if (callback) callback(); + return; + } + onChange(newValue); if (callback) callback(); }} isLoading={isPending} - options={availableConnections} + options={[ + ...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []), + ...(availableConnections ?? []) + ]} isDisabled={isUpdate} placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} control={control} name="connection" /> - {!isUpdate && availableConnections?.length === 0 && ( + {!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (

- {canCreateConnection ? ( - <> - You do not have access to any {appName} Connections. Create one from the{" "} - - App Connections - {" "} - page. - - ) : ( - `You do not have access to any ${appName} Connections. Contact an admin to create one.` - )} + You do not have access to any {appName} Connections. Contact an admin to create one.

)} + { + // remove form storage, not oauth connection + localStorage.removeItem("secretRotationFormData"); + handlePopUpToggle("addConnection", isOpen); + }} + projectType={currentProject.type} + projectId={currentProject.id} + app={app} + onComplete={(connection) => { + if (connection) { + setValue("connection", connection); + } + }} + /> ); }; diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx index d931d9d3c..87869e925 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx @@ -11,8 +11,9 @@ import { SecretRotationV2ParametersFields } from "@app/components/secret-rotatio import { SecretRotationV2ReviewFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields"; import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields"; import { Button } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { IS_ROTATION_DUAL_CREDENTIALS, SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; import { SecretRotation, TSecretRotationV2, @@ -22,7 +23,6 @@ import { useCreateSecretRotationV2, useUpdateSecretRotationV2 } from "@app/hooks/api/secretRotationsV2/mutations"; -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; import { SecretRotationV2FormSchema, TSecretRotationV2Form } from "./schemas"; @@ -32,8 +32,9 @@ type Props = { onCancel: () => void; secretPath: string; environment?: string; - environments?: WorkspaceEnv[]; + environments?: ProjectEnv[]; secretRotation?: TSecretRotationV2; + initialFormData?: Partial; }; const FORM_TABS: { name: string; key: string; fields: (keyof TSecretRotationV2Form)[] }[] = [ @@ -64,11 +65,12 @@ export const SecretRotationV2Form = ({ environment: envSlug, secretPath, secretRotation, - environments + environments, + initialFormData }: Props) => { const createSecretRotation = useCreateSecretRotationV2(); const updateSecretRotation = useUpdateSecretRotationV2(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { name: rotationType } = SECRET_ROTATION_MAP[type]; const [selectedTabIndex, setSelectedTabIndex] = useState(0); @@ -80,7 +82,7 @@ export const SecretRotationV2Form = ({ defaultValues: secretRotation ? { ...secretRotation, - environment: currentWorkspace?.environments.find((env) => env.slug === envSlug), + environment: currentProject?.environments.find((env) => env.slug === envSlug), secretPath } : { @@ -91,9 +93,10 @@ export const SecretRotationV2Form = ({ hours: 0, minutes: 0 }, - environment: currentWorkspace?.environments.find((env) => env.slug === envSlug), + environment: currentProject?.environments.find((env) => env.slug === envSlug), secretPath, - ...(rotationOption!.template as object) // can't infer type since we don't know which specific type it is + ...((rotationOption?.template as object) ?? {}), // can't infer type since we don't know which specific type it is + ...(initialFormData as object) }, reValidateMode: "onChange" }); @@ -115,7 +118,7 @@ export const SecretRotationV2Form = ({ connectionId: connection.id, environment: environment.slug, - projectId: currentWorkspace.id + projectId: currentProject.id }); try { const rotation = await mutation; diff --git a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx index c95baaae5..fb1081d02 100644 --- a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx +++ b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx @@ -1,7 +1,9 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useRouterState } from "@tanstack/react-router"; +import { TSecretScanningDataSourceForm } from "@app/components/secret-scanning/forms/schemas"; import { Modal, ModalContent } from "@app/components/v2"; import { SecretScanningDataSource, @@ -21,16 +23,19 @@ type ContentProps = { onComplete: (dataSource: TSecretScanningDataSource) => void; selectedDataSource: SecretScanningDataSource | null; setSelectedDataSource: (selectedDataSource: SecretScanningDataSource | null) => void; + initialFormData?: Partial; + onCancel: () => void; }; -const Content = ({ setSelectedDataSource, selectedDataSource, ...props }: ContentProps) => { +const Content = ({ + setSelectedDataSource, + selectedDataSource, + onCancel, + ...props +}: ContentProps) => { if (selectedDataSource) { return ( - setSelectedDataSource(null)} - type={selectedDataSource} - {...props} - /> + ); } @@ -41,12 +46,60 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p const [selectedDataSource, setSelectedDataSource] = useState( null ); + const [initialFormData, setInitialFormData] = useState>(); + + const { + location: { + search: { connectionId, connectionName, ...search }, + pathname + } + } = useRouterState(); + + const navigate = useNavigate(); + + useEffect(() => { + if (connectionId && connectionName) { + const storedFormData = localStorage.getItem("secretScanningDataSourceFormData"); + + if (!storedFormData) return; + + let form: Partial = {}; + try { + form = JSON.parse(storedFormData) as TSecretScanningDataSourceForm; + } catch { + return; + } finally { + localStorage.removeItem("secretScanningDataSourceFormData"); + } + + onOpenChange(true); + + setSelectedDataSource(form.type ?? null); + + setInitialFormData({ + ...form, + connection: { id: connectionId, name: connectionName } + }); + + navigate({ + to: pathname, + search + }); + } + }, [connectionId, connectionName]); + + const resetModal = () => { + setSelectedDataSource(null); + setInitialFormData(undefined); + }; return ( { - if (!open) setSelectedDataSource(null); + if (!open) { + resetModal(); + } onOpenChange(open); }} > @@ -83,11 +136,13 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p > { - setSelectedDataSource(null); + resetModal(); onOpenChange(false); }} + onCancel={resetModal} selectedDataSource={selectedDataSource} setSelectedDataSource={setSelectedDataSource} + initialFormData={initialFormData} {...props} /> diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx index 3f995e1d0..2e9e4a2a5 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx @@ -1,14 +1,17 @@ import { Controller, useFormContext } from "react-hook-form"; +import { SingleValue } from "react-select"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "@tanstack/react-router"; +import { AppConnectionOption } from "@app/components/app-connections"; import { FilterableSelect, FormControl } from "@app/components/v2"; -import { OrgPermissionSubjects, useOrgPermission } from "@app/context"; -import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; +import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context"; +import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/helpers/secretScanningV2"; +import { usePopUp } from "@app/hooks"; import { useListAvailableAppConnections } from "@app/hooks/api/appConnections"; +import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; import { TSecretScanningDataSourceForm } from "./schemas"; @@ -21,19 +24,26 @@ export const SecretScanningDataSourceConnectionField = ({ onChange: callback, isUpdate }: Props) => { - const { permission } = useOrgPermission(); - const { control, watch } = useFormContext(); + const { permission } = useProjectPermission(); + const { control, watch, setValue } = useFormContext(); + + const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const); const dataSourceType = watch("type"); const app = SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSourceType]; - const { data: availableConnections, isPending } = useListAvailableAppConnections(app); + const { currentProject } = useProject(); + + const { data: availableConnections, isPending } = useListAvailableAppConnections( + app, + currentProject.id + ); const connectionName = APP_CONNECTION_MAP[app].name; const canCreateConnection = permission.can( - OrgPermissionAppConnectionActions.Create, - OrgPermissionSubjects.AppConnections + ProjectPermissionAppConnectionActions.Create, + ProjectPermissionSub.AppConnections ); return ( @@ -67,37 +77,57 @@ export const SecretScanningDataSourceConnectionField = ({ { + if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") { + handlePopUpOpen("addConnection"); + onChange(null); + // store for oauth callback connections + localStorage.setItem("secretScanningDataSourceFormData", JSON.stringify(watch())); + if (callback) callback(); + return; + } + onChange(newValue); if (callback) callback(); }} isLoading={isPending} - options={availableConnections} + options={[ + ...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []), + ...(availableConnections ?? []) + ]} isDisabled={isUpdate} placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} control={control} name="connection" /> - {!isUpdate && availableConnections?.length === 0 && ( + {!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (

- {canCreateConnection ? ( - <> - You do not have access to any {connectionName} Connections. Create one from the{" "} - - App Connections - {" "} - page. - - ) : ( - `You do not have access to any ${connectionName} Connections. Contact an admin to create one.` - )} + You do not have access to any {connectionName} Connections. Contact an admin to create + one.

)} + { + // remove form storage, not oauth connection + localStorage.removeItem("secretScanningDataSourceFormData"); + handlePopUpToggle("addConnection", isOpen); + }} + projectType={currentProject.type} + projectId={currentProject.id} + app={app} + onComplete={(connection) => { + if (connection) { + setValue("connection", connection); + } + }} + /> ); }; diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx index b1ea0b559..515a67436 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx @@ -6,7 +6,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { SECRET_SCANNING_DATA_SOURCE_MAP } from "@app/helpers/secretScanningV2"; import { SecretScanningDataSource, @@ -27,6 +27,7 @@ type Props = { type: SecretScanningDataSource; onCancel: () => void; dataSource?: TSecretScanningDataSource; + initialFormData?: Partial; }; const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningDataSourceForm)[] }[] = @@ -36,10 +37,16 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningData { name: "Review", key: "review", fields: [] } ]; -export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataSource }: Props) => { +export const SecretScanningDataSourceForm = ({ + type, + onComplete, + onCancel, + dataSource, + initialFormData +}: Props) => { const createDataSource = useCreateSecretScanningDataSource(); const updateDataSource = useUpdateSecretScanningDataSource(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { name: sourceType } = SECRET_SCANNING_DATA_SOURCE_MAP[type]; const [selectedTabIndex, setSelectedTabIndex] = useState(0); @@ -48,7 +55,8 @@ export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataS resolver: zodResolver(SecretScanningDataSourceSchema), defaultValues: dataSource ?? { type, - isAutoScanEnabled: true // scott: this may need to be derived from type in the future + isAutoScanEnabled: true, // scott: this may need to be derived from type in the future + ...(initialFormData as object) }, reValidateMode: "onChange" }); @@ -63,7 +71,7 @@ export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataS : createDataSource.mutateAsync({ ...formData, connectionId: connection?.id, - projectId: currentWorkspace.id + projectId: currentProject.id }); try { const source = await mutation; diff --git a/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx b/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx index 5ff72e810..e504389c0 100644 --- a/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx +++ b/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; +import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; import { Modal, ModalContent } from "@app/components/v2"; import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs"; @@ -11,18 +12,21 @@ type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; selectSync?: SecretSync | null; + initialFormData?: Partial; }; type ContentProps = { onComplete: (secretSync: TSecretSync) => void; selectedSync: SecretSync | null; setSelectedSync: (selectedSync: SecretSync | null) => void; + initialFormData?: Partial; }; -const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => { +const Content = ({ onComplete, setSelectedSync, selectedSync, initialFormData }: ContentProps) => { if (selectedSync) { return ( setSelectedSync(null)} destination={selectedSync} @@ -33,7 +37,12 @@ const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => return ; }; -export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...props }: Props) => { +export const CreateSecretSyncModal = ({ + onOpenChange, + selectSync = null, + initialFormData, + ...props +}: Props) => { const [selectedSync, setSelectedSync] = useState(selectSync); useEffect(() => { @@ -67,6 +76,7 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop }} selectedSync={selectedSync} setSelectedSync={setSelectedSync} + initialFormData={initialFormData} />
diff --git a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index 8a1be69c4..4135a9926 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -8,7 +8,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Switch } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync, @@ -29,6 +29,7 @@ type Props = { onComplete: (secretSync: TSecretSync) => void; destination: SecretSync; onCancel: () => void; + initialFormData?: Partial; }; const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[] }[] = [ @@ -39,14 +40,20 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[] { name: "Review", key: "review", fields: [] } ]; -export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Props) => { +export const CreateSecretSyncForm = ({ + destination, + onComplete, + onCancel, + initialFormData +}: Props) => { const createSecretSync = useCreateSecretSync(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { name: destinationName } = SECRET_SYNC_MAP[destination]; const [showConfirmation, setShowConfirmation] = useState(false); - const [selectedTabIndex, setSelectedTabIndex] = useState(0); + // scoot: right now we only do this when creating a connection so we know index 1 + const [selectedTabIndex, setSelectedTabIndex] = useState(initialFormData ? 1 : 0); const { syncOption } = useSecretSyncOption(destination); @@ -59,7 +66,8 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop initialSyncBehavior: syncOption?.canImportSecrets ? undefined : SecretSyncInitialSyncBehavior.OverwriteDestination - } + }, + ...initialFormData } as Partial, reValidateMode: "onChange" }); @@ -70,7 +78,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop ...formData, connectionId: connection.id, environment: environment.slug, - projectId: currentWorkspace.id + projectId: currentProject.id }); createNotification({ diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx index 94e587709..62542a570 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx @@ -1,14 +1,17 @@ import { Controller, useFormContext } from "react-hook-form"; +import { SingleValue } from "react-select"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "@tanstack/react-router"; +import { AppConnectionOption } from "@app/components/app-connections"; import { FilterableSelect, FormControl } from "@app/components/v2"; -import { OrgPermissionSubjects, useOrgPermission } from "@app/context"; -import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; +import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context"; +import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { SECRET_SYNC_CONNECTION_MAP } from "@app/helpers/secretSyncs"; +import { usePopUp } from "@app/hooks"; import { useListAvailableAppConnections } from "@app/hooks/api/appConnections"; +import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; import { TSecretSyncForm } from "./schemas"; @@ -17,19 +20,26 @@ type Props = { }; export const SecretSyncConnectionField = ({ onChange: callback }: Props) => { - const { permission } = useOrgPermission(); - const { control, watch } = useFormContext(); + const { permission } = useProjectPermission(); + const { control, watch, setValue } = useFormContext(); + + const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const); const destination = watch("destination"); const app = SECRET_SYNC_CONNECTION_MAP[destination]; - const { data: availableConnections, isPending } = useListAvailableAppConnections(app); + const { currentProject } = useProject(); + + const { data: availableConnections, isPending } = useListAvailableAppConnections( + app, + currentProject.id + ); const connectionName = APP_CONNECTION_MAP[app].name; const canCreateConnection = permission.can( - OrgPermissionAppConnectionActions.Create, - OrgPermissionSubjects.AppConnections + ProjectPermissionAppConnectionActions.Create, + ProjectPermissionSub.AppConnections ); const appName = APP_CONNECTION_MAP[SECRET_SYNC_CONNECTION_MAP[destination]].name; @@ -51,36 +61,55 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => { { + if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") { + handlePopUpOpen("addConnection"); + onChange(null); + // store for oauth callback connections + localStorage.setItem("secretSyncFormData", JSON.stringify(watch())); + if (callback) callback(); + return; + } + onChange(newValue); if (callback) callback(); }} isLoading={isPending} - options={availableConnections} + options={[ + ...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []), + ...(availableConnections ?? []) + ]} placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} control={control} name="connection" /> - {availableConnections?.length === 0 && ( + {!isPending && !availableConnections?.length && !canCreateConnection && (

- {canCreateConnection ? ( - <> - You do not have access to any {appName} Connections. Create one from the{" "} - - App Connections - {" "} - page. - - ) : ( - `You do not have access to any ${appName} Connections. Contact an admin to create one.` - )} + You do not have access to any {appName} Connections. Contact an admin to create one.

)} + { + // remove form storage, not oauth connection + localStorage.removeItem("secretSyncFormData"); + handlePopUpToggle("addConnection", isOpen); + }} + projectType={currentProject.type} + projectId={currentProject.id} + app={app} + onComplete={(connection) => { + if (connection) { + setValue("connection", connection); + } + }} + /> ); }; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx index 850ebbc80..127ac503c 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncSourceFields.tsx @@ -4,7 +4,7 @@ import { subject } from "@casl/ability"; import { FilterableSelect, FormControl } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { ProjectPermissionSecretSyncActions, ProjectPermissionSub @@ -16,7 +16,7 @@ export const SecretSyncSourceFields = () => { const { control, watch, setError, clearErrors } = useFormContext(); const { permission } = useProjectPermission(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const selectedEnvironment = watch("environment"); const selectedSecretPath = watch("secretPath"); @@ -48,7 +48,7 @@ export const SecretSyncSourceFields = () => {

( @@ -56,7 +56,7 @@ export const SecretSyncSourceFields = () => { option?.name} getOptionValue={(option) => option?.id} diff --git a/frontend/src/components/secrets/SecretReferenceDetails/SecretReferenceDetails.tsx b/frontend/src/components/secrets/SecretReferenceDetails/SecretReferenceDetails.tsx index a9542c90c..ada741145 100644 --- a/frontend/src/components/secrets/SecretReferenceDetails/SecretReferenceDetails.tsx +++ b/frontend/src/components/secrets/SecretReferenceDetails/SecretReferenceDetails.tsx @@ -13,7 +13,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { FormControl, FormLabel, SecretInput, Spinner, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useGetSecretReferenceTree } from "@app/hooks/api"; import { ApiErrorTypes, TApiErrors, TSecretReferenceTraceNode } from "@app/hooks/api/types"; @@ -89,8 +89,8 @@ export const SecretReferenceNode = ({ }; export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data, isPending, isError, error } = useGetSecretReferenceTree({ secretPath, diff --git a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx index b1773b2e3..d48b7d95f 100644 --- a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx +++ b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx @@ -15,7 +15,7 @@ import { ModalContent, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateWsTag } from "@app/hooks/api"; import { slugSchema } from "@app/lib/schemas"; @@ -115,11 +115,10 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => { } }); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { mutateAsync: createWsTag } = useCreateWsTag(); - const [showHexInput, setShowHexInput] = useState(false); const selectedTagColor = watch("color", secretTagsColors[0].hex); @@ -130,7 +129,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => { const onFormSubmit = async ({ slug, color }: FormData) => { try { await createWsTag({ - workspaceID: workspaceId, + projectId, tagColor: color, tagSlug: slug }); diff --git a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx index ee2f196b1..ec09a93eb 100644 --- a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx +++ b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx @@ -3,7 +3,7 @@ import { faFolder, faKey, faLayerGroup } from "@fortawesome/free-solid-svg-icons import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as Popover from "@radix-ui/react-popover"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useDebounce, useToggle } from "@app/hooks"; import { useGetProjectFolders, useGetProjectSecrets } from "@app/hooks/api"; @@ -76,8 +76,8 @@ export const InfisicalSecretInput = forwardRef( }, ref ) => { - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const [debouncedValue] = useDebounce(value, 100); @@ -101,7 +101,7 @@ export const InfisicalSecretInput = forwardRef( let predicate = suggestionSourceValue; if (isDeep) { const [envSlug, ...folderPaths] = suggestionSourceValue.split("."); - const isValidEnvSlug = currentWorkspace?.environments.find((e) => e.slug === envSlug); + const isValidEnvSlug = currentProject?.environments.find((e) => e.slug === envSlug); suggestionSourceEnv = isValidEnvSlug ? envSlug : undefined; suggestionSourceSecretPath = `/${folderPaths.slice(0, -1)?.join("/")}`; predicate = folderPaths[folderPaths.length - 1]; @@ -125,7 +125,7 @@ export const InfisicalSecretInput = forwardRef( viewSecretValue: false, environment: suggestionSource.environment || "", secretPath: suggestionSource.secretPath || "", - workspaceId, + projectId, options: { enabled: isPopupOpen } @@ -133,7 +133,7 @@ export const InfisicalSecretInput = forwardRef( const { data: folders } = useGetProjectFolders({ environment: suggestionSource.environment || "", path: suggestionSource.secretPath || "", - projectId: workspaceId, + projectId, options: { enabled: isPopupOpen } @@ -148,7 +148,7 @@ export const InfisicalSecretInput = forwardRef( if (!suggestionSource.isDeep) { // At first level only environments and secrets - (currentWorkspace?.environments || []).forEach(({ name, slug }) => { + (currentProject?.environments || []).forEach(({ name, slug }) => { if (name.toLowerCase().startsWith(predicate)) suggestionsArr.push({ label: name, @@ -176,7 +176,7 @@ export const InfisicalSecretInput = forwardRef( }); }); return suggestionsArr; - }, [secrets, folders, currentWorkspace?.environments, isPopupOpen, suggestionSource.value]); + }, [secrets, folders, currentProject?.environments, isPopupOpen, suggestionSource.value]); const handleSuggestionSelect = (selectIndex?: number) => { const selectedSuggestion = diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index a1d4fb2a2..a9c08f7ad 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as Popover from "@radix-ui/react-popover"; import { twMerge } from "tailwind-merge"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useDebounce } from "@app/hooks"; import { useGetFoldersByEnv } from "@app/hooks/api"; @@ -35,12 +35,12 @@ export const SecretPathInput = ({ const [highlightedIndex, setHighlightedIndex] = useState(-1); const [debouncedInputValue] = useDebounce(inputValue, 200); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { folderNames: folders } = useGetFoldersByEnv({ path: secretPath, - environments: [environment || currentWorkspace?.environments?.[0].slug || ""], - projectId: workspaceId + environments: [environment || currentProject?.environments?.[0].slug || ""], + projectId }); useEffect(() => { diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 4a569be06..8e4f7514c 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -1,4 +1,4 @@ -import { MongoAbility } from "@casl/ability"; +import { ForcedSubject, MongoAbility } from "@casl/ability"; export enum OrgPermissionActions { Read = "read", @@ -32,6 +32,7 @@ export enum OrgPermissionMachineIdentityAuthTemplateActions { export enum OrgPermissionSubjects { Workspace = "workspace", + Project = "project", Role = "role", Member = "member", Settings = "settings", @@ -109,6 +110,7 @@ export type AppConnectionSubjectFields = { export type OrgPermissionSet = | [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace] + | [OrgPermissionActions.Create, OrgPermissionSubjects.Project] | [OrgPermissionActions.Read, OrgPermissionSubjects.Workspace] | [OrgPermissionActions, OrgPermissionSubjects.Role] | [OrgPermissionActions, OrgPermissionSubjects.Member] @@ -126,7 +128,6 @@ export type OrgPermissionSet = | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] | [OrgPermissionAuditLogsActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] - | [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections] | [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity] | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] | [ @@ -134,14 +135,13 @@ export type OrgPermissionSet = OrgPermissionSubjects.MachineIdentityAuthTemplate ] | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway] - | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]; -// TODO(scott): add back once org UI refactored -// | [ -// OrgPermissionAppConnectionActions, -// ( -// | OrgPermissionSubjects.AppConnections -// | (ForcedSubject & AppConnectionSubjectFields) -// ) -// ]; + | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare] + | [ + OrgPermissionAppConnectionActions, + ( + | OrgPermissionSubjects.AppConnections + | (ForcedSubject & AppConnectionSubjectFields) + ) + ]; export type TOrgPermission = MongoAbility; diff --git a/frontend/src/context/ProjectContext/ProjectContext.tsx b/frontend/src/context/ProjectContext/ProjectContext.tsx new file mode 100644 index 000000000..7256741ef --- /dev/null +++ b/frontend/src/context/ProjectContext/ProjectContext.tsx @@ -0,0 +1,22 @@ +import { useSuspenseQuery } from "@tanstack/react-query"; +import { useParams } from "@tanstack/react-router"; + +import { projectKeys } from "@app/hooks/api"; +import { fetchProjectById } from "@app/hooks/api/projects/queries"; + +export const useProject = () => { + const params = useParams({ + strict: false + }); + if (!params.projectId) { + throw new Error("Missing project id"); + } + + const { data: currentProject } = useSuspenseQuery({ + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId as string), + staleTime: Infinity + }); + + return { currentProject, projectId: currentProject.id }; +}; diff --git a/frontend/src/context/ProjectContext/index.tsx b/frontend/src/context/ProjectContext/index.tsx new file mode 100644 index 000000000..f87ed0a3d --- /dev/null +++ b/frontend/src/context/ProjectContext/index.tsx @@ -0,0 +1 @@ +export { useProject } from "./ProjectContext"; diff --git a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx index a824f6fa6..871d5c338 100644 --- a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx +++ b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx @@ -22,8 +22,8 @@ export const useProjectPermission = () => { const { data: { permission, membership, assumedPrivilegeDetails } } = useSuspenseQuery({ - queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId: projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: projectId }), + queryKey: roleQueryKeys.getUserProjectPermissions({ projectId }), + queryFn: () => fetchUserProjectPermissions({ projectId }), staleTime: Infinity, select: (data) => { const rule = unpackRules>>(data.permissions); diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index acfab612f..ad6b5bb0a 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -154,6 +154,14 @@ export enum ProjectPermissionAuditLogsActions { Read = "read" } +export enum ProjectPermissionAppConnectionActions { + Read = "read-app-connections", + Create = "create-app-connections", + Edit = "edit-app-connections", + Delete = "delete-app-connections", + Connect = "connect-app-connections" +} + export enum PermissionConditionOperators { $IN = "$in", $ALL = "$all", @@ -173,6 +181,10 @@ export type IdentityManagementSubjectFields = { identityId: string; }; +export type AppConnectionSubjectFields = { + connectionId: string; +}; + export type ConditionalProjectPermissionSubject = | ProjectPermissionSub.SecretSyncs | ProjectPermissionSub.Secrets @@ -184,7 +196,8 @@ export type ConditionalProjectPermissionSubject = | ProjectPermissionSub.SecretFolders | ProjectPermissionSub.SecretImports | ProjectPermissionSub.SecretRotation - | ProjectPermissionSub.SecretEvents; + | ProjectPermissionSub.SecretEvents + | ProjectPermissionSub.AppConnections; export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = { [PermissionConditionOperators.$EQ]: "equal to", @@ -263,7 +276,8 @@ export enum ProjectPermissionSub { SecretScanningDataSources = "secret-scanning-data-sources", SecretScanningFindings = "secret-scanning-findings", SecretScanningConfigs = "secret-scanning-configs", - SecretEvents = "secret-events" + SecretEvents = "secret-events", + AppConnections = "app-connections" } export type SecretSubjectFields = { @@ -431,6 +445,13 @@ export type ProjectPermissionSet = | ProjectPermissionSub.SecretEvents | (ForcedSubject & SecretEventSubjectFields) ) + ] + | [ + ProjectPermissionAppConnectionActions, + ( + | ProjectPermissionSub.AppConnections + | (ForcedSubject & AppConnectionSubjectFields) + ) ]; export type TProjectPermission = MongoAbility; diff --git a/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx b/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx deleted file mode 100644 index 8bef13b31..000000000 --- a/frontend/src/context/WorkspaceContext/WorkspaceContext.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useSuspenseQuery } from "@tanstack/react-query"; -import { useParams } from "@tanstack/react-router"; - -import { workspaceKeys } from "@app/hooks/api"; -import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; - -export const useWorkspace = () => { - const params = useParams({ - strict: false - }); - if (!params.projectId) { - throw new Error("Missing project id"); - } - - const { data: currentWorkspace } = useSuspenseQuery({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId as string), - staleTime: Infinity - }); - - return { currentWorkspace }; -}; diff --git a/frontend/src/context/WorkspaceContext/index.tsx b/frontend/src/context/WorkspaceContext/index.tsx deleted file mode 100644 index b0c25d4da..000000000 --- a/frontend/src/context/WorkspaceContext/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { useWorkspace } from "./WorkspaceContext"; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index bfb504664..fff370269 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -9,6 +9,7 @@ export { OrgPermissionSubjects, useOrgPermission } from "./OrgPermissionContext"; +export { useProject } from "./ProjectContext"; export type { TProjectPermission } from "./ProjectPermissionContext"; export { ProjectPermissionActions, @@ -29,4 +30,3 @@ export { export { useServerConfig } from "./ServerConfigContext"; export { useSubscription } from "./SubscriptionContext"; export { useUser } from "./UserContext"; -export { useWorkspace } from "./WorkspaceContext"; diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 88d9bb0d1..99103c794 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -8,6 +8,7 @@ import { faServer, faUser } from "@fortawesome/free-solid-svg-icons"; +import { useRouterState } from "@tanstack/react-router"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { @@ -221,3 +222,11 @@ export const AWS_REGIONS = [ { name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" }, { name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" } ]; + +export const useGetAppConnectionOauthReturnUrl = () => { + const { + location: { pathname } + } = useRouterState(); + + return pathname; +}; diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 7c9185556..6227b9696 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -1,6 +1,6 @@ import { apiRequest } from "@app/config/request"; -import { createWorkspace } from "@app/hooks/api/workspace/queries"; -import { ProjectType, WorkspaceEnv } from "@app/hooks/api/workspace/types"; +import { createWorkspace } from "@app/hooks/api/projects/queries"; +import { ProjectEnv, ProjectType } from "@app/hooks/api/projects/types"; const secretsToBeAdded = [ { @@ -47,8 +47,8 @@ export const initProjectHelper = async ({ projectName }: { projectName: string } }); try { - const { data } = await apiRequest.post("/api/v3/secrets/batch/raw", { - workspaceId: project.id, + const { data } = await apiRequest.post("/api/v4/secrets/batch", { + projectId: project.id, environment: "dev", secretPath: "/", secrets: secretsToBeAdded @@ -74,7 +74,7 @@ export const getProjectBaseURL = (type: ProjectType) => { // @ts-expect-error akhilmhdh: will remove this later // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const getProjectHomePage = (type: ProjectType, environments: WorkspaceEnv[]) => { +export const getProjectHomePage = (type: ProjectType, environments: ProjectEnv[]) => { switch (type) { case ProjectType.SecretManager: return "/projects/secret-management/$projectId/overview" as const; diff --git a/frontend/src/hooks/api/accessApproval/queries.tsx b/frontend/src/hooks/api/accessApproval/queries.tsx index 44f260f66..2be6c9535 100644 --- a/frontend/src/hooks/api/accessApproval/queries.tsx +++ b/frontend/src/hooks/api/accessApproval/queries.tsx @@ -16,8 +16,8 @@ import { export const accessApprovalKeys = { getAccessApprovalPolicies: (projectSlug: string) => [{ projectSlug }, "access-approval-policies"] as const, - getAccessApprovalPolicyOfABoard: (workspaceId: string, environment: string) => - [{ workspaceId, environment }, "access-approval-policy"] as const, + getAccessApprovalPolicyOfABoard: (projectId: string, environment: string) => + [{ projectId, environment }, "access-approval-policy"] as const, getAccessApprovalRequests: ( projectSlug: string, diff --git a/frontend/src/hooks/api/accessApproval/types.ts b/frontend/src/hooks/api/accessApproval/types.ts index 5063f4fff..be698e469 100644 --- a/frontend/src/hooks/api/accessApproval/types.ts +++ b/frontend/src/hooks/api/accessApproval/types.ts @@ -1,7 +1,7 @@ import { EnforcementLevel, PolicyType } from "../policies/enums"; +import { ProjectEnv } from "../projects/types"; import { TProjectPermission } from "../roles/types"; import { ApprovalStatus } from "../secretApprovalRequest/types"; -import { WorkspaceEnv } from "../workspace/types"; export type TAccessApprovalPolicy = { id: string; @@ -9,7 +9,7 @@ export type TAccessApprovalPolicy = { approvals: number; secretPath: string; workspace: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; projectId: string; policyType: PolicyType; approversRequired: boolean; diff --git a/frontend/src/hooks/api/appConnections/mutations.tsx b/frontend/src/hooks/api/appConnections/mutations.tsx index bb8831342..cee433462 100644 --- a/frontend/src/hooks/api/appConnections/mutations.tsx +++ b/frontend/src/hooks/api/appConnections/mutations.tsx @@ -20,7 +20,10 @@ export const useCreateAppConnection = () => { return data.appConnection; }, - onSuccess: () => queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() }) + onSuccess: ({ projectId, app }) => { + queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) }); + queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) }); + } }); }; @@ -35,9 +38,10 @@ export const useUpdateAppConnection = () => { return data.appConnection; }, - onSuccess: (_, { connectionId, app }) => { - queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() }); - queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) }); + onSuccess: ({ projectId, app }) => { + queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) }); + queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) }); + // queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) }); } }); }; @@ -50,9 +54,10 @@ export const useDeleteAppConnection = () => { return data; }, - onSuccess: (_, { connectionId, app }) => { - queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() }); - queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) }); + onSuccess: ({ projectId, app }) => { + queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) }); + queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) }); + // queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) }); } }); }; diff --git a/frontend/src/hooks/api/appConnections/queries.tsx b/frontend/src/hooks/api/appConnections/queries.tsx index dbccade88..a27ffdc10 100644 --- a/frontend/src/hooks/api/appConnections/queries.tsx +++ b/frontend/src/hooks/api/appConnections/queries.tsx @@ -5,29 +5,36 @@ import { apiRequest } from "@app/config/request"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { TAppConnection, - TAppConnectionMap, TAppConnectionOptions, TAvailableAppConnection, TAvailableAppConnectionsResponse, - TGetAppConnection, TListAppConnections } from "@app/hooks/api/appConnections/types"; import { TAppConnectionOption, TAppConnectionOptionMap } from "@app/hooks/api/appConnections/types/app-options"; +import { ProjectType } from "@app/hooks/api/projects/types"; export const appConnectionKeys = { all: ["app-connection"] as const, - options: () => [...appConnectionKeys.all, "options"] as const, - list: () => [...appConnectionKeys.all, "list"] as const, - listAvailable: (app: AppConnection) => [...appConnectionKeys.all, app, "list-available"] as const, - listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app], - byId: (app: AppConnection, connectionId: string) => - [...appConnectionKeys.all, app, "by-id", connectionId] as const + options: (projectType?: ProjectType) => + [...appConnectionKeys.all, "options", ...(projectType ? [projectType] : [])] as const, + list: (projectId?: string | null) => + [...appConnectionKeys.all, "list", ...(projectId ? [projectId] : [])] as const, + listAvailable: (app: AppConnection, projectId?: string | null) => + [...appConnectionKeys.all, app, "list-available", ...(projectId ? [projectId] : [])] as const + // scott: may need these in the future but not using now + // getUsage: (app: AppConnection, connectionId: string) => + // [...appConnectionKeys.all, "usage", app, connectionId] as const + // listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app], + // scott: we will need this once we have individual app connection page + // byId: (app: AppConnection, connectionId: string) => + // [...appConnectionKeys.all, app, "by-id", connectionId] as const }; export const useAppConnectionOptions = ( + projectType?: ProjectType, options?: Omit< UseQueryOptions< TAppConnectionOption[], @@ -39,10 +46,11 @@ export const useAppConnectionOptions = ( > ) => { return useQuery({ - queryKey: appConnectionKeys.options(), + queryKey: appConnectionKeys.options(projectType), queryFn: async () => { const { data } = await apiRequest.get( - "/api/v1/app-connections/options" + "/api/v1/app-connections/options", + { params: { projectType } } ); return data.appConnectionOptions; @@ -64,6 +72,7 @@ export const useGetAppConnectionOption = (app: T) => { }; export const useListAppConnections = ( + projectId?: string, options?: Omit< UseQueryOptions< TAppConnection[], @@ -75,10 +84,12 @@ export const useListAppConnections = ( > ) => { return useQuery({ - queryKey: appConnectionKeys.list(), + queryKey: appConnectionKeys.list(projectId), queryFn: async () => { - const { data } = - await apiRequest.get>("/api/v1/app-connections"); + const { data } = await apiRequest.get>( + "/api/v1/app-connections", + { params: { projectId } } + ); return data.appConnections; }, @@ -88,6 +99,7 @@ export const useListAppConnections = ( export const useListAvailableAppConnections = ( app: AppConnection, + projectId: string, options?: Omit< UseQueryOptions< TAvailableAppConnection[], @@ -99,10 +111,11 @@ export const useListAvailableAppConnections = ( > ) => { return useQuery({ - queryKey: appConnectionKeys.listAvailable(app), + queryKey: appConnectionKeys.listAvailable(app, projectId), queryFn: async () => { const { data } = await apiRequest.get( - `/api/v1/app-connections/${app}/available` + `/api/v1/app-connections/${app}/available`, + { params: { projectId } } ); return data.appConnections; @@ -111,53 +124,82 @@ export const useListAvailableAppConnections = ( }); }; -export const useListAppConnectionsByApp = ( - app: T, - options?: Omit< - UseQueryOptions< - TAppConnectionMap[T][], - unknown, - TAppConnectionMap[T][], - ReturnType - >, - "queryKey" | "queryFn" - > -) => { - return useQuery({ - queryKey: appConnectionKeys.listByApp(app), - queryFn: async () => { - const { data } = await apiRequest.get>( - `/api/v1/app-connections/${app}` - ); +// scott: may need these in the future but not using now +// export const useGetAppConnectionUsageById = ( +// app: AppConnection, +// connectionId: string, +// options?: Omit< +// UseQueryOptions< +// AppConnectionUsage, +// unknown, +// AppConnectionUsage, +// ReturnType +// >, +// "queryKey" | "queryFn" +// > +// ) => { +// return useQuery({ +// queryKey: appConnectionKeys.getUsage(app, connectionId), +// queryFn: async () => { +// const { data } = await apiRequest.get( +// `/api/v1/app-connections/${app}/${connectionId}/usage` +// ); +// +// return data; +// }, +// ...options +// }); +// }; - return data.appConnections; - }, - ...options - }); -}; +// scott: may need these in the future but not using now +// export const useListAppConnectionsByApp = ( +// app: T, +// options?: Omit< +// UseQueryOptions< +// TAppConnectionMap[T][], +// unknown, +// TAppConnectionMap[T][], +// ReturnType +// >, +// "queryKey" | "queryFn" +// > +// ) => { +// return useQuery({ +// queryKey: appConnectionKeys.listByApp(app), +// queryFn: async () => { +// const { data } = await apiRequest.get>( +// `/api/v1/app-connections/${app}` +// ); +// +// return data.appConnections; +// }, +// ...options +// }); +// }; -export const useGetAppConnectionById = ( - app: T, - connectionId: string, - options?: Omit< - UseQueryOptions< - TAppConnectionMap[T], - unknown, - TAppConnectionMap[T], - ReturnType - >, - "queryKey" | "queryFn" - > -) => { - return useQuery({ - queryKey: appConnectionKeys.byId(app, connectionId), - queryFn: async () => { - const { data } = await apiRequest.get>( - `/api/v1/app-connections/${app}/${connectionId}` - ); - - return data.appConnection; - }, - ...options - }); -}; +// scott: we will need this once we have individual app connection page +// export const useGetAppConnectionById = ( +// app: T, +// connectionId: string, +// options?: Omit< +// UseQueryOptions< +// TAppConnectionMap[T], +// unknown, +// TAppConnectionMap[T], +// ReturnType +// >, +// "queryKey" | "queryFn" +// > +// ) => { +// return useQuery({ +// queryKey: appConnectionKeys.byId(app, connectionId), +// queryFn: async () => { +// const { data } = await apiRequest.get>( +// `/api/v1/app-connections/${app}/${connectionId}` +// ); +// +// return data.appConnection; +// }, +// ...options +// }); +// }; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index e63979a8e..8c1d86ca3 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -116,10 +116,11 @@ export type TAppConnection = | TNetlifyConnection | TOktaConnection; -export type TAvailableAppConnection = Pick; +export type TAvailableAppConnection = Pick; export type TListAppConnections = { appConnections: T[] }; -export type TGetAppConnection = { appConnection: T }; +// scott: we will need this once we have individual app connection page +// export type TGetAppConnection = { appConnection: T }; export type TAppConnectionOptions = { appConnectionOptions: TAppConnectionOption[] }; export type TAppConnectionResponse = { appConnection: TAppConnection }; export type TAvailableAppConnectionsResponse = { appConnections: TAvailableAppConnection[] }; @@ -133,6 +134,7 @@ export type TCreateAppConnectionDTO = Pick< | "description" | "isPlatformManagedCredentials" | "gatewayId" + | "projectId" >; export type TUpdateAppConnectionDTO = Partial< @@ -150,43 +152,60 @@ export type TDeleteAppConnectionDTO = { connectionId: string; }; -export type TAppConnectionMap = { - [AppConnection.AWS]: TAwsConnection; - [AppConnection.GitHub]: TGitHubConnection; - [AppConnection.GitHubRadar]: TGitHubRadarConnection; - [AppConnection.GCP]: TGcpConnection; - [AppConnection.AzureKeyVault]: TAzureKeyVaultConnection; - [AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection; - [AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection; - [AppConnection.AzureDevOps]: TAzureDevOpsConnection; - [AppConnection.AzureADCS]: TAzureADCSConnection; - [AppConnection.Databricks]: TDatabricksConnection; - [AppConnection.Humanitec]: THumanitecConnection; - [AppConnection.TerraformCloud]: TTerraformCloudConnection; - [AppConnection.Vercel]: TVercelConnection; - [AppConnection.Postgres]: TPostgresConnection; - [AppConnection.MsSql]: TMsSqlConnection; - [AppConnection.MySql]: TMySqlConnection; - [AppConnection.OracleDB]: TOracleDBConnection; - [AppConnection.Camunda]: TCamundaConnection; - [AppConnection.Windmill]: TWindmillConnection; - [AppConnection.Auth0]: TAuth0Connection; - [AppConnection.HCVault]: THCVaultConnection; - [AppConnection.LDAP]: TLdapConnection; - [AppConnection.TeamCity]: TTeamCityConnection; - [AppConnection.OCI]: TOCIConnection; - [AppConnection.OnePass]: TOnePassConnection; - [AppConnection.Heroku]: THerokuConnection; - [AppConnection.Render]: TRenderConnection; - [AppConnection.Flyio]: TFlyioConnection; - [AppConnection.GitLab]: TGitLabConnection; - [AppConnection.Cloudflare]: TCloudflareConnection; - [AppConnection.Bitbucket]: TBitbucketConnection; - [AppConnection.Zabbix]: TZabbixConnection; - [AppConnection.Railway]: TRailwayConnection; - [AppConnection.Checkly]: TChecklyConnection; - [AppConnection.Supabase]: TSupabaseConnection; - [AppConnection.DigitalOcean]: TDigitalOceanConnection; - [AppConnection.Netlify]: TNetlifyConnection; - [AppConnection.Okta]: TOktaConnection; -}; +// scott: we will need this once we have individual app connection page +// export type TAppConnectionMap = { +// [AppConnection.AWS]: TAwsConnection; +// [AppConnection.GitHub]: TGitHubConnection; +// [AppConnection.GitHubRadar]: TGitHubRadarConnection; +// [AppConnection.GCP]: TGcpConnection; +// [AppConnection.AzureKeyVault]: TAzureKeyVaultConnection; +// [AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection; +// [AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection; +// [AppConnection.AzureDevOps]: TAzureDevOpsConnection; +// [AppConnection.AzureADCS]: TAzureADCSConnection; +// [AppConnection.Databricks]: TDatabricksConnection; +// [AppConnection.Humanitec]: THumanitecConnection; +// [AppConnection.TerraformCloud]: TTerraformCloudConnection; +// [AppConnection.Vercel]: TVercelConnection; +// [AppConnection.Postgres]: TPostgresConnection; +// [AppConnection.MsSql]: TMsSqlConnection; +// [AppConnection.MySql]: TMySqlConnection; +// [AppConnection.OracleDB]: TOracleDBConnection; +// [AppConnection.Camunda]: TCamundaConnection; +// [AppConnection.Windmill]: TWindmillConnection; +// [AppConnection.Auth0]: TAuth0Connection; +// [AppConnection.HCVault]: THCVaultConnection; +// [AppConnection.LDAP]: TLdapConnection; +// [AppConnection.TeamCity]: TTeamCityConnection; +// [AppConnection.OCI]: TOCIConnection; +// [AppConnection.OnePass]: TOnePassConnection; +// [AppConnection.Heroku]: THerokuConnection; +// [AppConnection.Render]: TRenderConnection; +// [AppConnection.Flyio]: TFlyioConnection; +// [AppConnection.GitLab]: TGitLabConnection; +// [AppConnection.Cloudflare]: TCloudflareConnection; +// [AppConnection.Bitbucket]: TBitbucketConnection; +// [AppConnection.Zabbix]: TZabbixConnection; +// [AppConnection.Railway]: TRailwayConnection; +// [AppConnection.Checkly]: TChecklyConnection; +// [AppConnection.Supabase]: TSupabaseConnection; +// [AppConnection.DigitalOcean]: TDigitalOceanConnection; +// [AppConnection.Netlify]: TNetlifyConnection; +// [AppConnection.Okta]: TOktaConnection; +// }; + +// scott: we will need this once we have individual app connection page +// export type AppConnectionUsage = { +// projects: Array<{ +// id: string; +// name: string; +// slug: string; +// type: ProjectType; +// resources: { +// secretSyncs: Array<{ id: string; name: string }>; +// externalCas: Array<{ id: string; name: string }>; +// secretRotations: Array<{ id: string; name: string }>; +// dataSources: Array<{ id: string; name: string }>; +// }; +// }>; +// }; diff --git a/frontend/src/hooks/api/appConnections/types/root-connection.ts b/frontend/src/hooks/api/appConnections/types/root-connection.ts index 5b8f5cd02..96198265e 100644 --- a/frontend/src/hooks/api/appConnections/types/root-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/root-connection.ts @@ -1,3 +1,5 @@ +import { ProjectType } from "@app/hooks/api/projects/types"; + export type TRootAppConnection = { id: string; name: string; @@ -8,4 +10,11 @@ export type TRootAppConnection = { updatedAt: string; isPlatformManagedCredentials?: boolean; gatewayId?: string | null; + projectId?: string | null; + project?: { + name: string; + type: ProjectType; + slug: string; + id: string; + } | null; }; diff --git a/frontend/src/hooks/api/assumePrivileges/mutations.tsx b/frontend/src/hooks/api/assumePrivileges/mutations.tsx index 50e4e5b6c..687882fe8 100644 --- a/frontend/src/hooks/api/assumePrivileges/mutations.tsx +++ b/frontend/src/hooks/api/assumePrivileges/mutations.tsx @@ -8,7 +8,7 @@ export const useAssumeProjectPrivileges = () => useMutation({ mutationFn: async ({ projectId, actorId, actorType }: TProjectAssumePrivilegesDTO) => { const { data } = await apiRequest.post<{ message: string }>( - `/api/v1/workspace/${projectId}/assume-privileges`, + `/api/v1/projects/${projectId}/assume-privileges`, { actorId, actorType } ); @@ -20,7 +20,7 @@ export const useRemoveAssumeProjectPrivilege = () => useMutation({ mutationFn: async ({ projectId }: { projectId: string }) => { const { data } = await apiRequest.delete<{ message: string }>( - `/api/v1/workspace/${projectId}/assume-privileges` + `/api/v1/projects/${projectId}/assume-privileges` ); return data; diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index 9b9280cf0..503264fb2 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -20,7 +20,7 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_SECRET]: "Create secret", [EventType.UPDATE_SECRET]: "Update secret", [EventType.DELETE_SECRET]: "Delete secret", - [EventType.GET_WORKSPACE_KEY]: "Read project key", + [EventType.GET_PROJECT_KEY]: "Read project key", [EventType.AUTHORIZE_INTEGRATION]: "Authorize integration", [EventType.UPDATE_INTEGRATION_AUTH]: "Update integration auth", [EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration", @@ -45,8 +45,8 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_ENVIRONMENT]: "Create environment", [EventType.UPDATE_ENVIRONMENT]: "Update environment", [EventType.DELETE_ENVIRONMENT]: "Delete environment", - [EventType.ADD_WORKSPACE_MEMBER]: "Add member", - [EventType.REMOVE_WORKSPACE_MEMBER]: "Remove member", + [EventType.ADD_PROJECT_MEMBER]: "Add member", + [EventType.REMOVE_PROJECT_MEMBER]: "Remove member", [EventType.CREATE_FOLDER]: "Create folder", [EventType.UPDATE_FOLDER]: "Update folder", [EventType.DELETE_FOLDER]: "Delete folder", @@ -58,8 +58,8 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_SECRET_IMPORT]: "Create secret import", [EventType.UPDATE_SECRET_IMPORT]: "Update secret import", [EventType.DELETE_SECRET_IMPORT]: "Delete secret import", - [EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS]: "Update denied permissions", - [EventType.UPDATE_USER_WORKSPACE_ROLE]: "Update user role", + [EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS]: "Update denied permissions", + [EventType.UPDATE_USER_PROJECT_ROLE]: "Update user role", [EventType.CREATE_CA]: "Create CA", [EventType.GET_CA]: "Get CA", [EventType.UPDATE_CA]: "Update CA", @@ -132,6 +132,8 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_APP_CONNECTION]: "Create App Connection", [EventType.UPDATE_APP_CONNECTION]: "Update App Connection", [EventType.DELETE_APP_CONNECTION]: "Delete App Connection", + [EventType.GET_APP_CONNECTION_USAGE]: "Get App Connection Usage", + [EventType.MIGRATE_APP_CONNECTION]: "Migrate App Connection", [EventType.GET_SECRET_SYNCS]: "List secret syncs", [EventType.GET_SECRET_SYNC]: "Get Secret Sync", [EventType.CREATE_SECRET_SYNC]: "Create Secret Sync", diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 90cb5e237..bfa983188 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -26,7 +26,7 @@ export enum EventType { CREATE_SECRET = "create-secret", UPDATE_SECRET = "update-secret", DELETE_SECRET = "delete-secret", - 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", @@ -59,8 +59,8 @@ export enum EventType { CREATE_ENVIRONMENT = "create-environment", UPDATE_ENVIRONMENT = "update-environment", DELETE_ENVIRONMENT = "delete-environment", - ADD_WORKSPACE_MEMBER = "add-workspace-member", - REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", + ADD_PROJECT_MEMBER = "add-project-member", + REMOVE_PROJECT_MEMBER = "remove-project-member", CREATE_FOLDER = "create-folder", UPDATE_FOLDER = "update-folder", DELETE_FOLDER = "delete-folder", @@ -72,8 +72,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", CREATE_CA = "create-certificate-authority", GET_CA = "get-certificate-authority", UPDATE_CA = "update-certificate-authority", @@ -141,6 +141,8 @@ export enum EventType { CREATE_APP_CONNECTION = "create-app-connection", UPDATE_APP_CONNECTION = "update-app-connection", DELETE_APP_CONNECTION = "delete-app-connection", + GET_APP_CONNECTION_USAGE = "get-app-connection-usage", + MIGRATE_APP_CONNECTION = "migrate-app-connection", GET_SECRET_SYNCS = "get-secret-syncs", GET_SECRET_SYNC = "get-secret-sync", CREATE_SECRET_SYNC = "create-secret-sync", 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/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index a0b1ff50a..1bb321fa2 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -129,7 +129,7 @@ interface DeleteSecretEvent { } interface GetWorkspaceKeyEvent { - type: EventType.GET_WORKSPACE_KEY; + type: EventType.GET_PROJECT_KEY; metadata: { keyId: string; }; @@ -361,7 +361,7 @@ interface DeleteEnvironmentEvent { } interface AddWorkspaceMemberEvent { - type: EventType.ADD_WORKSPACE_MEMBER; + type: EventType.ADD_PROJECT_MEMBER; metadata: { userId: string; email: string; @@ -369,7 +369,7 @@ interface AddWorkspaceMemberEvent { } interface RemoveWorkspaceMemberEvent { - type: EventType.REMOVE_WORKSPACE_MEMBER; + type: EventType.REMOVE_PROJECT_MEMBER; metadata: { userId: string; email: string; @@ -500,7 +500,7 @@ interface DeleteSecretImportEvent { } interface UpdateUserRole { - type: EventType.UPDATE_USER_WORKSPACE_ROLE; + type: EventType.UPDATE_USER_PROJECT_ROLE; metadata: { userId: string; email: string; @@ -510,7 +510,7 @@ interface UpdateUserRole { } interface UpdateUserDeniedPermissions { - type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS; + type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS; metadata: { userId: string; email: string; diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index cb7e3f64c..d41a4d115 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -5,8 +5,8 @@ import { apiRequest } from "@app/config/request"; import { SessionStorageKeys } from "@app/const"; import { organizationKeys } from "../organization/queries"; +import { projectKeys } from "../projects"; import { setAuthToken } from "../reactQuery"; -import { workspaceKeys } from "../workspace"; import { CompleteAccountDTO, CompleteAccountSignupDTO, @@ -101,7 +101,7 @@ export const useSelectOrganization = () => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: [organizationKeys.getUserOrganizations, workspaceKeys.getAllUserWorkspace] + queryKey: [organizationKeys.getUserOrganizations, projectKeys.getAllUserProjects] }); } }); diff --git a/frontend/src/hooks/api/ca/mutations.tsx b/frontend/src/hooks/api/ca/mutations.tsx index f6204d4fc..a14a0244b 100644 --- a/frontend/src/hooks/api/ca/mutations.tsx +++ b/frontend/src/hooks/api/ca/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { CaType } from "./enums"; import { caKeys } from "./queries"; import { @@ -118,8 +118,8 @@ export const useImportCaCertificate = (projectId: string) => { ); return data; }, - onSuccess: (_, { caId, projectSlug }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceCas({ projectSlug }) }); + onSuccess: (_, { caId }) => { + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectCas({ projectId }) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCerts(caId) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCert(caId) }); queryClient.invalidateQueries({ @@ -142,7 +142,7 @@ export const useCreateCertificate = () => { }, onSuccess: (_, { projectSlug }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug) + queryKey: projectKeys.forProjectCertificates(projectSlug) }); } }); @@ -158,8 +158,8 @@ export const useRenewCa = () => { ); return data; }, - onSuccess: (_, { caId, projectSlug }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceCas({ projectSlug }) }); + onSuccess: ({ projectId }, { caId }) => { + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectCas({ projectId }) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaById(caId) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCert(caId) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCerts(caId) }); diff --git a/frontend/src/hooks/api/ca/types.ts b/frontend/src/hooks/api/ca/types.ts index 336688ca4..0443dd5e9 100644 --- a/frontend/src/hooks/api/ca/types.ts +++ b/frontend/src/hooks/api/ca/types.ts @@ -182,4 +182,5 @@ export type TRenewCaResponse = { certificate: string; certificateChain: string; serialNumber: string; + projectId: string; }; diff --git a/frontend/src/hooks/api/certificateTemplates/mutations.tsx b/frontend/src/hooks/api/certificateTemplates/mutations.tsx index d1069e7e6..24a7d0e5f 100644 --- a/frontend/src/hooks/api/certificateTemplates/mutations.tsx +++ b/frontend/src/hooks/api/certificateTemplates/mutations.tsx @@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { caKeys } from "../ca/queries"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { certTemplateKeys } from "./queries"; import { TCertificateTemplate, @@ -29,7 +29,7 @@ export const useCreateCertTemplate = () => { }, onSuccess: ({ caId }, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId) + queryKey: projectKeys.getProjectCertificateTemplates(projectId) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) }); } @@ -49,7 +49,7 @@ export const useUpdateCertTemplate = () => { }, onSuccess: ({ caId }, { projectId, id }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId) + queryKey: projectKeys.getProjectCertificateTemplates(projectId) }); queryClient.invalidateQueries({ queryKey: certTemplateKeys.getCertTemplateById(id) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) }); @@ -68,7 +68,7 @@ export const useDeleteCertTemplate = () => { }, onSuccess: ({ caId }, { projectId, id }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId) + queryKey: projectKeys.getProjectCertificateTemplates(projectId) }); queryClient.invalidateQueries({ queryKey: certTemplateKeys.getCertTemplateById(id) }); queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) }); diff --git a/frontend/src/hooks/api/certificates/mutations.tsx b/frontend/src/hooks/api/certificates/mutations.tsx index 12f8e834b..77a3dab72 100644 --- a/frontend/src/hooks/api/certificates/mutations.tsx +++ b/frontend/src/hooks/api/certificates/mutations.tsx @@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { pkiSubscriberKeys } from "../pkiSubscriber/queries"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { TCertificate, TDeleteCertDTO, @@ -25,7 +25,7 @@ export const useDeleteCert = () => { }, onSuccess: (_, { projectSlug }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug) + queryKey: projectKeys.forProjectCertificates(projectSlug) }); } }); @@ -47,7 +47,7 @@ export const useRevokeCert = () => { }, onSuccess: (_, { projectSlug }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug) + queryKey: projectKeys.forProjectCertificates(projectSlug) }); queryClient.invalidateQueries({ queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates() @@ -68,7 +68,7 @@ export const useImportCertificate = () => { }, onSuccess: (_, { projectSlug }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug) + queryKey: projectKeys.forProjectCertificates(projectSlug) }); } }); diff --git a/frontend/src/hooks/api/folderCommits/queries.tsx b/frontend/src/hooks/api/folderCommits/queries.tsx index 1bca7ce9a..4d37a4f7b 100644 --- a/frontend/src/hooks/api/folderCommits/queries.tsx +++ b/frontend/src/hooks/api/folderCommits/queries.tsx @@ -7,27 +7,27 @@ import { Commit, CommitHistoryItem, CommitWithChanges, RollbackPreview } from ". export const commitKeys = { count: ({ - workspaceId, + projectId, environment, directory }: { - workspaceId: string; + projectId: string; environment: string; directory?: string; - }) => [{ workspaceId, environment, directory }, "folder-commits-count"] as const, + }) => [{ projectId, environment, directory }, "folder-commits-count"] as const, history: ({ - workspaceId, + projectId, environment, directory }: { - workspaceId: string; + projectId: string; environment: string; directory?: string; - }) => [{ workspaceId, environment, directory }, "folder-commits"] as const, + }) => [{ projectId, environment, directory }, "folder-commits"] as const, - details: ({ workspaceId, commitId }: { workspaceId: string; commitId: string }) => - [{ workspaceId, commitId }, "commit-details"] as const, + details: ({ projectId, commitId }: { projectId: string; commitId: string }) => + [{ projectId, commitId }, "commit-details"] as const, rollbackPreview: ({ folderId, @@ -45,11 +45,11 @@ export const commitKeys = { }; const fetchFolderCommitsCount = async ({ - workspaceId, + projectId, environment, directory }: { - workspaceId: string; + projectId: string; environment: string; directory?: string; }) => { @@ -59,7 +59,7 @@ const fetchFolderCommitsCount = async ({ params: { environment, path: directory, - projectId: workspaceId + projectId } } ); @@ -67,7 +67,7 @@ const fetchFolderCommitsCount = async ({ }; const fetchFolderCommitHistory = async ( - workspaceId: string, + projectId: string, environment: string, directory: string, offset: number = 0, @@ -87,7 +87,7 @@ const fetchFolderCommitHistory = async ( params: { environment, path: directory, - projectId: workspaceId, + projectId, offset, limit, search, @@ -97,12 +97,12 @@ const fetchFolderCommitHistory = async ( return res.data; }; -export const fetchCommitDetails = async (workspaceId: string, commitId: string) => { +export const fetchCommitDetails = async (projectId: string, commitId: string) => { const { data } = await apiRequest.get( `/api/v1/pit/commits/${commitId}/changes`, { params: { - projectId: workspaceId + projectId } } ); @@ -113,7 +113,7 @@ export const fetchRollbackPreview = async ( folderId: string, commitId: string, envSlug: string, - workspaceId: string, + projectId: string, deepRollback: boolean, secretPath: string ): Promise => { @@ -125,7 +125,7 @@ export const fetchRollbackPreview = async ( environment: envSlug, deepRollback, secretPath, - projectId: workspaceId + projectId } } ); @@ -135,7 +135,7 @@ export const fetchRollbackPreview = async ( const fetchRollback = async ( folderId: string, commitId: string, - workspaceId: string, + projectId: string, deepRollback: boolean, message?: string, envSlug?: string @@ -147,17 +147,17 @@ const fetchRollback = async ( deepRollback, message, environment: envSlug, - projectId: workspaceId + projectId } ); return data; }; -const fetchRevert = async (commitId: string, workspaceId: string) => { +const fetchRevert = async (commitId: string, projectId: string) => { const { data } = await apiRequest.post<{ success: boolean; message: string }>( `/api/v1/pit/commits/${commitId}/revert`, { - projectId: workspaceId + projectId } ); return data; @@ -180,9 +180,9 @@ export const useCommitRevert = ({ onSuccess: () => { queryClient.invalidateQueries({ queryKey: [ - commitKeys.details({ workspaceId: projectId, commitId }), - commitKeys.history({ workspaceId: projectId, environment, directory }), - commitKeys.count({ workspaceId: projectId, environment, directory }) + commitKeys.details({ projectId, commitId }), + commitKeys.history({ projectId, environment, directory }), + commitKeys.count({ projectId, environment, directory }) ] }); } @@ -190,7 +190,7 @@ export const useCommitRevert = ({ }; export const useCommitRollback = ({ - workspaceId, + projectId, commitId, folderId, deepRollback, @@ -198,7 +198,7 @@ export const useCommitRollback = ({ directory, envSlug }: { - workspaceId: string; + projectId: string; commitId: string; folderId: string; deepRollback: boolean; @@ -209,13 +209,13 @@ export const useCommitRollback = ({ const queryClient = useQueryClient(); return useMutation({ mutationFn: (message: string) => - fetchRollback(folderId, commitId, workspaceId, deepRollback, message, envSlug), + fetchRollback(folderId, commitId, projectId, deepRollback, message, envSlug), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [ - commitKeys.details({ workspaceId, commitId }), - commitKeys.history({ workspaceId, environment, directory }), - commitKeys.count({ workspaceId, environment, directory }) + commitKeys.details({ projectId, commitId }), + commitKeys.history({ projectId, environment, directory }), + commitKeys.count({ projectId, environment, directory }) ] }); } @@ -223,31 +223,31 @@ export const useCommitRollback = ({ }; export const useGetFolderCommitsCount = ({ - workspaceId, + projectId, environment, directory, isPaused }: { - workspaceId: string; + projectId: string; environment: string; directory: string; isPaused?: boolean; }) => useQuery({ - enabled: Boolean(workspaceId && environment) && !isPaused, - queryKey: commitKeys.count({ workspaceId, environment, directory }), - queryFn: () => fetchFolderCommitsCount({ workspaceId, environment, directory }) + enabled: Boolean(projectId && environment) && !isPaused, + queryKey: commitKeys.count({ projectId, environment, directory }), + queryFn: () => fetchFolderCommitsCount({ projectId, environment, directory }) }); export const useGetFolderCommitHistory = ({ - workspaceId, + projectId, environment, directory, limit = 20, search, sort = "desc" }: { - workspaceId: string; + projectId: string; environment: string; directory: string; limit?: number; @@ -256,10 +256,10 @@ export const useGetFolderCommitHistory = ({ }) => { return useInfiniteQuery({ initialPageParam: 0, - queryKey: [commitKeys.history({ workspaceId, environment, directory }), limit, search, sort], + queryKey: [commitKeys.history({ projectId, environment, directory }), limit, search, sort], queryFn: ({ pageParam }) => - fetchFolderCommitHistory(workspaceId, environment, directory, pageParam, limit, search, sort), - enabled: Boolean(workspaceId && environment), + fetchFolderCommitHistory(projectId, environment, directory, pageParam, limit, search, sort), + enabled: Boolean(projectId && environment), select: (data) => { return (data?.pages ?? []) ?.map((page) => page.commits) @@ -280,11 +280,11 @@ export const useGetFolderCommitHistory = ({ }); }; -export const useGetCommitDetails = (workspaceId: string, commitId: string) => { +export const useGetCommitDetails = (projectId: string, commitId: string) => { return useQuery({ - queryKey: commitKeys.details({ workspaceId, commitId }), - queryFn: () => fetchCommitDetails(workspaceId, commitId), - enabled: Boolean(workspaceId) && Boolean(commitId) + queryKey: commitKeys.details({ projectId, commitId }), + queryFn: () => fetchCommitDetails(projectId, commitId), + enabled: Boolean(projectId) && Boolean(commitId) }); }; 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/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index a2fa17acc..99e377143 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -1,7 +1,7 @@ import { OrderByDirection } from "../generic/types"; import { OrgIdentityOrderBy } from "../organization/types"; +import { Project, ProjectUserMembershipTemporaryMode } from "../projects/types"; import { TOrgRole } from "../roles/types"; -import { ProjectUserMembershipTemporaryMode, Workspace } from "../workspace/types"; import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums"; export type IdentityTrustedIp = { @@ -54,7 +54,7 @@ export type IdentityMembershipOrg = { export type IdentityMembership = { id: string; identity: Identity; - project: Pick; + project: Pick; roles: Array< { id: string; diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 78dcd3b79..d65c7e72c 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -20,7 +20,6 @@ export * from "./identityProjectAdditionalPrivilege"; export * from "./incidentContacts"; export * from "./integrationAuth"; export * from "./integrations"; -export * from "./keys"; export * from "./kms"; export * from "./ldapConfig"; export * from "./oidcConfig"; @@ -29,6 +28,7 @@ export * from "./organization"; export * from "./pkiAlerts"; export * from "./pkiCollections"; export * from "./pkiSubscriber"; +export * from "./projects"; export * from "./projectUserAdditionalPrivilege"; export * from "./rateLimit"; export * from "./roles"; @@ -54,4 +54,3 @@ export * from "./trustedIps"; export * from "./users"; export * from "./webhooks"; export * from "./workflowIntegrations"; -export * from "./workspace"; diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 044ec8d2a..f6c936480 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -3,7 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { TReactQueryOptions } from "@app/types/reactQuery"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { App, BitbucketEnvironment, @@ -970,7 +970,7 @@ export const useAuthorizeIntegration = () => { }, onSuccess: (res) => { queryClient.invalidateQueries({ - queryKey: { queryKey: workspaceKeys.getWorkspaceAuthorization(res.workspace) } + queryKey: { queryKey: projectKeys.getProjectAuthorization(res.workspace) } }); } }); @@ -1016,7 +1016,7 @@ export const useSaveIntegrationAccessToken = () => { }, onSuccess: (res) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceAuthorization(res.workspace) + queryKey: projectKeys.getProjectAuthorization(res.workspace) }); } }); @@ -1035,10 +1035,10 @@ export const useDeleteIntegrationAuths = () => { ), onSuccess: (_, { workspaceId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId) + queryKey: projectKeys.getProjectAuthorization(workspaceId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId) + queryKey: projectKeys.getProjectIntegrations(workspaceId) }); } }); @@ -1052,10 +1052,10 @@ export const useDeleteIntegrationAuth = () => { mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration-auth/${id}`), onSuccess: (_, { workspaceId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId) + queryKey: projectKeys.getProjectAuthorization(workspaceId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId) + queryKey: projectKeys.getProjectIntegrations(workspaceId) }); } }); diff --git a/frontend/src/hooks/api/integrations/queries.tsx b/frontend/src/hooks/api/integrations/queries.tsx index a439f8caa..69519a5a4 100644 --- a/frontend/src/hooks/api/integrations/queries.tsx +++ b/frontend/src/hooks/api/integrations/queries.tsx @@ -3,7 +3,7 @@ import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstac import { createNotification } from "@app/components/notifications"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { IntegrationMetadataSyncMode, TCloudIntegration, @@ -121,7 +121,7 @@ export const useCreateIntegration = () => { }, onSuccess: (res) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIntegrations(res.workspace) + queryKey: projectKeys.getProjectIntegrations(res.workspace) }); } }); @@ -141,10 +141,10 @@ export const useDeleteIntegration = () => { ), onSuccess: (_, { workspaceId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId) + queryKey: projectKeys.getProjectIntegrations(workspaceId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId) + queryKey: projectKeys.getProjectAuthorization(workspaceId) }); } }); diff --git a/frontend/src/hooks/api/keys/index.tsx b/frontend/src/hooks/api/keys/index.tsx deleted file mode 100644 index cd4f0aea8..000000000 --- a/frontend/src/hooks/api/keys/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { useGetUserWsKey, useUploadWsKey } from "./queries"; diff --git a/frontend/src/hooks/api/keys/queries.tsx b/frontend/src/hooks/api/keys/queries.tsx deleted file mode 100644 index f902f85d3..000000000 --- a/frontend/src/hooks/api/keys/queries.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; - -import { apiRequest } from "@app/config/request"; - -import { UploadWsKeyDTO, UserWsKeyPair } from "./types"; - -const encKeyKeys = { - getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const -}; - -export const fetchUserWsKey = async (projectId: string) => { - const { data } = await apiRequest.get( - `/api/v2/workspace/${projectId}/encrypted-key` - ); - - return data; -}; - -export const useGetUserWsKey = (workspaceID: string) => - useQuery({ - queryKey: encKeyKeys.getUserWorkspaceKey(workspaceID), - queryFn: () => fetchUserWsKey(workspaceID), - enabled: Boolean(workspaceID) - }); - -// mutations -export const uploadWsKey = async ({ workspaceId, userId, encryptedKey, nonce }: UploadWsKeyDTO) => { - return apiRequest.post(`/api/v1/workspace/${workspaceId}/key`, { - key: { userId, encryptedKey, nonce } - }); -}; - -export const useUploadWsKey = () => - useMutation({ - mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => { - return uploadWsKey({ - workspaceId, - userId, - encryptedKey, - nonce - }); - } - }); diff --git a/frontend/src/hooks/api/keys/types.ts b/frontend/src/hooks/api/keys/types.ts deleted file mode 100644 index fc455deaf..000000000 --- a/frontend/src/hooks/api/keys/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type UserWsKeyPair = { - id: string; - encryptedKey: string; - nonce: string; - sender: Sender; - receiver: string; - workspace: string; - createdAt: string; - updatedAt: string; - __v: number; -}; - -export type Sender = { - id: string; - email: string; - createdAt: string; - updatedAt: string; - __v: number; - firstName: string; - lastName: string; - publicKey: string; -}; - -export type UploadWsKeyDTO = { - userId: string; - encryptedKey: string; - nonce: string; - workspaceId: string; -}; diff --git a/frontend/src/hooks/api/kms/mutations.tsx b/frontend/src/hooks/api/kms/mutations.tsx index 2bf27bf6a..4fb0a5af5 100644 --- a/frontend/src/hooks/api/kms/mutations.tsx +++ b/frontend/src/hooks/api/kms/mutations.tsx @@ -75,7 +75,7 @@ export const useUpdateProjectKms = (projectId: string) => { mutationFn: async ( updatedData: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string } ) => { - const { data } = await apiRequest.patch(`/api/v1/workspace/${projectId}/kms`, { + const { data } = await apiRequest.patch(`/api/v1/projects/${projectId}/kms`, { kms: updatedData }); @@ -91,7 +91,7 @@ export const useLoadProjectKmsBackup = (projectId: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (backup: string) => { - const { data } = await apiRequest.post(`/api/v1/workspace/${projectId}/kms/backup`, { + const { data } = await apiRequest.post(`/api/v1/projects/${projectId}/kms/backup`, { backup }); diff --git a/frontend/src/hooks/api/kms/queries.tsx b/frontend/src/hooks/api/kms/queries.tsx index 9efa9cc51..97d25376c 100644 --- a/frontend/src/hooks/api/kms/queries.tsx +++ b/frontend/src/hooks/api/kms/queries.tsx @@ -49,7 +49,7 @@ export const useGetActiveProjectKms = (projectId: string) => { name: string; isExternal: string; }; - }>(`/api/v1/workspace/${projectId}/kms`); + }>(`/api/v1/projects/${projectId}/kms`); return secretManagerKmsKey; } }); @@ -58,7 +58,7 @@ export const useGetActiveProjectKms = (projectId: string) => { export const fetchProjectKmsBackup = async (projectId: string) => { const { data } = await apiRequest.get<{ secretManager: string; - }>(`/api/v1/workspace/${projectId}/kms/backup`); + }>(`/api/v1/projects/${projectId}/kms/backup`); return data; }; diff --git a/frontend/src/hooks/api/migration/mutations.tsx b/frontend/src/hooks/api/migration/mutations.tsx index 182797954..b2694b458 100644 --- a/frontend/src/hooks/api/migration/mutations.tsx +++ b/frontend/src/hooks/api/migration/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; export const useImportEnvKey = () => { const queryClient = useQueryClient(); @@ -34,7 +34,7 @@ export const useImportEnvKey = () => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); diff --git a/frontend/src/hooks/api/pkiAlerts/mutations.tsx b/frontend/src/hooks/api/pkiAlerts/mutations.tsx index 34397c141..df48a6aab 100644 --- a/frontend/src/hooks/api/pkiAlerts/mutations.tsx +++ b/frontend/src/hooks/api/pkiAlerts/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { pkiAlertKeys } from "./queries"; import { TCreatePkiAlertDTO, TDeletePkiAlertDTO, TPkiAlert, TUpdatePkiAlertDTO } from "./types"; @@ -14,7 +14,7 @@ export const useCreatePkiAlert = () => { return alert; }, onSuccess: (_, { projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiAlerts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectPkiAlerts(projectId) }); } }); }; @@ -30,7 +30,7 @@ export const useUpdatePkiAlert = () => { return alert; }, onSuccess: (_, { projectId, alertId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiAlerts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectPkiAlerts(projectId) }); queryClient.invalidateQueries({ queryKey: pkiAlertKeys.getPkiAlertById(alertId) }); } }); @@ -44,7 +44,7 @@ export const useDeletePkiAlert = () => { return alert; }, onSuccess: (_, { projectId, alertId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiAlerts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectPkiAlerts(projectId) }); queryClient.invalidateQueries({ queryKey: pkiAlertKeys.getPkiAlertById(alertId) }); } }); diff --git a/frontend/src/hooks/api/pkiCollections/mutations.tsx b/frontend/src/hooks/api/pkiCollections/mutations.tsx index af1f0af18..a06afcd09 100644 --- a/frontend/src/hooks/api/pkiCollections/mutations.tsx +++ b/frontend/src/hooks/api/pkiCollections/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { pkiCollectionKeys } from "./queries"; import { TAddItemToPkiCollectionDTO, @@ -26,7 +26,7 @@ export const useCreatePkiCollection = () => { }, onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiCollections(projectId) + queryKey: projectKeys.getProjectPkiCollections(projectId) }); } }); @@ -44,7 +44,7 @@ export const useUpdatePkiCollection = () => { }, onSuccess: (_, { projectId, collectionId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiCollections(projectId) + queryKey: projectKeys.getProjectPkiCollections(projectId) }); queryClient.invalidateQueries({ queryKey: pkiCollectionKeys.getPkiCollectionById(collectionId) @@ -64,7 +64,7 @@ export const useDeletePkiCollection = () => { }, onSuccess: (_, { projectId, collectionId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiCollections(projectId) + queryKey: projectKeys.getProjectPkiCollections(projectId) }); queryClient.invalidateQueries({ queryKey: pkiCollectionKeys.getPkiCollectionById(collectionId) diff --git a/frontend/src/hooks/api/pkiSubscriber/mutations.tsx b/frontend/src/hooks/api/pkiSubscriber/mutations.tsx index b30924f97..57d086780 100644 --- a/frontend/src/hooks/api/pkiSubscriber/mutations.tsx +++ b/frontend/src/hooks/api/pkiSubscriber/mutations.tsx @@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { TCreateCertificateResponse } from "../ca/types"; -import { workspaceKeys } from "../workspace/query-keys"; +import { projectKeys } from "../projects/query-keys"; import { pkiSubscriberKeys } from "./queries"; import { TCreatePkiSubscriberDTO, @@ -22,7 +22,7 @@ export const useCreatePkiSubscriber = () => { }, onSuccess: ({ projectId, name }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) + queryKey: projectKeys.getProjectPkiSubscribers(projectId) }); queryClient.invalidateQueries({ queryKey: pkiSubscriberKeys.getPkiSubscriber({ @@ -46,7 +46,7 @@ export const useUpdatePkiSubscriber = () => { }, onSuccess: ({ projectId, name }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) + queryKey: projectKeys.getProjectPkiSubscribers(projectId) }); queryClient.invalidateQueries({ queryKey: pkiSubscriberKeys.getPkiSubscriber({ @@ -74,7 +74,7 @@ export const useDeletePkiSubscriber = () => { }, onSuccess: ({ name, projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) + queryKey: projectKeys.getProjectPkiSubscribers(projectId) }); queryClient.invalidateQueries({ queryKey: pkiSubscriberKeys.getPkiSubscriber({ diff --git a/frontend/src/hooks/api/projectTemplates/types.ts b/frontend/src/hooks/api/projectTemplates/types.ts index f5ff22dff..d58e80f53 100644 --- a/frontend/src/hooks/api/projectTemplates/types.ts +++ b/frontend/src/hooks/api/projectTemplates/types.ts @@ -1,6 +1,6 @@ import { TProjectRole } from "@app/hooks/api/roles/types"; -import { ProjectType } from "../workspace/types"; +import { ProjectType } from "../projects/types"; export type TProjectTemplate = { id: string; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/projects/index.tsx similarity index 89% rename from frontend/src/hooks/api/workspace/index.tsx rename to frontend/src/hooks/api/projects/index.tsx index df2d55dc3..ed8eba815 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/projects/index.tsx @@ -17,15 +17,14 @@ export { useDeleteWsEnvironment, useGetProjectSshConfig, useGetUpgradeProjectStatus, + useGetUserProjects, useGetUserWorkspaceMemberships, - useGetUserWorkspaces, useGetWorkspaceAuthorizations, useGetWorkspaceById, useGetWorkspaceIdentityMembershipDetails, useGetWorkspaceIdentityMemberships, useGetWorkspaceIndexStatus, useGetWorkspaceIntegrations, - useGetWorkspaceSecrets, useGetWorkspaceUserDetails, useGetWorkspaceUsers, useGetWorkspaceWorkflowIntegrationConfig, @@ -41,13 +40,11 @@ export { useListWorkspaceSshCertificateTemplates, useListWorkspaceSshHostGroups, useListWorkspaceSshHosts, - useNameWorkspaceSecrets, useSearchProjects, - useToggleAutoCapitalization, useUpdateIdentityWorkspaceRole, useUpdateProject, useUpdateUserWorkspaceRole, useUpdateWsEnvironment, useUpgradeProject } from "./queries"; -export { workspaceKeys } from "./query-keys"; +export { projectKeys } from "./query-keys"; diff --git a/frontend/src/hooks/api/workspace/mutations.tsx b/frontend/src/hooks/api/projects/mutations.tsx similarity index 68% rename from frontend/src/hooks/api/workspace/mutations.tsx rename to frontend/src/hooks/api/projects/mutations.tsx index 5cd7e3bbc..e2a80ff53 100644 --- a/frontend/src/hooks/api/workspace/mutations.tsx +++ b/frontend/src/hooks/api/projects/mutations.tsx @@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { userKeys } from "../users/query-keys"; -import { workspaceKeys } from "./query-keys"; +import { projectKeys } from "./query-keys"; import { TProjectSshConfig, TUpdateProjectSshConfigDTO, @@ -24,7 +24,7 @@ export const useAddGroupToWorkspace = () => { }) => { const { data: { groupMembership } - } = await apiRequest.post(`/api/v2/workspace/${projectId}/groups/${groupId}`, { + } = await apiRequest.post(`/api/v1/projects/${projectId}/groups/${groupId}`, { role }); @@ -32,7 +32,7 @@ export const useAddGroupToWorkspace = () => { }, onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId) + queryKey: projectKeys.getProjectGroupMemberships(projectId) }); } }); @@ -44,7 +44,7 @@ export const useUpdateGroupWorkspaceRole = () => { mutationFn: async ({ groupId, projectId, roles }: TUpdateWorkspaceGroupRoleDTO) => { const { data: { groupMembership } - } = await apiRequest.patch(`/api/v2/workspace/${projectId}/groups/${groupId}`, { + } = await apiRequest.patch(`/api/v1/projects/${projectId}/groups/${groupId}`, { roles }); @@ -52,10 +52,10 @@ export const useUpdateGroupWorkspaceRole = () => { }, onSuccess: (_, { projectId, groupId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId) + queryKey: projectKeys.getProjectGroupMemberships(projectId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceGroupMembershipDetails(projectId, groupId) + queryKey: projectKeys.getProjectGroupMembershipDetails(projectId, groupId) }); } }); @@ -74,12 +74,12 @@ export const useDeleteGroupFromWorkspace = () => { }) => { const { data: { groupMembership } - } = await apiRequest.delete(`/api/v2/workspace/${projectId}/groups/${groupId}`); + } = await apiRequest.delete(`/api/v1/projects/${projectId}/groups/${groupId}`); return groupMembership; }, onSuccess: (_, { projectId, username }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId) + queryKey: projectKeys.getProjectGroupMemberships(projectId) }); if (username) { @@ -91,25 +91,25 @@ export const useDeleteGroupFromWorkspace = () => { export const useLeaveProject = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ workspaceId }) => { - return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`); + return useMutation({ + mutationFn: ({ projectId }) => { + return apiRequest.delete(`/api/v1/projects/${projectId}/leave`); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + queryClient.invalidateQueries({ queryKey: projectKeys.getAllUserProjects() }); } }); }; export const useMigrateProjectToV3 = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ workspaceId }) => { - return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`); + return useMutation({ + mutationFn: ({ projectId }) => { + return apiRequest.post(`/api/v1/projects/${projectId}/migrate-v3`); }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); @@ -118,7 +118,7 @@ export const useMigrateProjectToV3 = () => { export const useRequestProjectAccess = () => { return useMutation({ mutationFn: ({ projectId, comment }) => { - return apiRequest.post(`/api/v1/workspace/${projectId}/project-access`, { + return apiRequest.post(`/api/v1/projects/${projectId}/project-access`, { comment }); } @@ -129,14 +129,14 @@ export const useUpdateProjectSshConfig = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ projectId, defaultUserSshCaId, defaultHostSshCaId }) => { - return apiRequest.patch(`/api/v1/workspace/${projectId}/ssh-config`, { + return apiRequest.patch(`/api/v1/projects/${projectId}/ssh-config`, { defaultUserSshCaId, defaultHostSshCaId }); }, onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getProjectSshConfig(projectId) + queryKey: projectKeys.getProjectSshConfig(projectId) }); } }); diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/projects/queries.tsx similarity index 55% rename from frontend/src/hooks/api/workspace/queries.tsx rename to frontend/src/hooks/api/projects/queries.tsx index 3c7a6d30c..0445d5984 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/projects/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"; @@ -26,42 +25,36 @@ import { ProjectWorkflowIntegrationConfig, WorkflowIntegrationPlatform } from "../workflowIntegrations/types"; -import { workspaceKeys } from "./query-keys"; +import { projectKeys } from "./query-keys"; import { CreateEnvironmentDTO, CreateWorkspaceDTO, DeleteEnvironmentDTO, DeleteWorkspaceDTO, - NameWorkspaceSecretsDTO, + Project, + ProjectEnv, ProjectIdentityOrderBy, ProjectType, TGetUpgradeProjectStatusDTO, TListProjectIdentitiesDTO, - ToggleAutoCapitalizationDTO, - ToggleDeleteProjectProtectionDTO, TProjectSshConfig, TSearchProjectsDTO, TUpdateWorkspaceIdentityRoleDTO, TUpdateWorkspaceUserRoleDTO, UpdateAuditLogsRetentionDTO, UpdateEnvironmentDTO, - UpdatePitVersionLimitDTO, - UpdateProjectDTO, - Workspace, - WorkspaceEnv + UpdateProjectDTO } from "./types"; -export const fetchWorkspaceById = async (workspaceId: string) => { - const { data } = await apiRequest.get<{ workspace: Workspace }>( - `/api/v1/workspace/${workspaceId}` - ); +export const fetchProjectById = async (projectId: string) => { + const { data } = await apiRequest.get<{ project: Project }>(`/api/v1/projects/${projectId}`); - return data.workspace; + return data.project; }; -const fetchWorkspaceIndexStatus = async (workspaceId: string) => { +const fetchWorkspaceIndexStatus = async (projectId: string) => { const { data } = await apiRequest.get( - `/api/v3/workspaces/${workspaceId}/secrets/blind-index-status` + `/api/v3/projects/${projectId}/secrets/blind-index-status` ); return data; @@ -69,34 +62,24 @@ const fetchWorkspaceIndexStatus = async (workspaceId: string) => { const fetchProjectUpgradeStatus = async (projectId: string) => { const { data } = await apiRequest.get<{ status: string }>( - `/api/v2/workspace/${projectId}/upgrade/status` + `/api/v1/projects/${projectId}/upgrade/status` ); return data; }; -export const fetchWorkspaceSecrets = async (workspaceId: string) => { - const { - data: { secrets } - } = await apiRequest.get<{ secrets: EncryptedSecret[] }>( - `/api/v3/workspaces/${workspaceId}/secrets` - ); - - return secrets; -}; - export const useUpgradeProject = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ projectId, privateKey }) => { - return apiRequest.post(`/api/v2/workspace/${projectId}/upgrade`, { + return apiRequest.post(`/api/v1/projects/${projectId}/upgrade`, { userPrivateKey: privateKey }); }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); @@ -108,7 +91,7 @@ export const useGetUpgradeProjectStatus = ({ refetchInterval }: TGetUpgradeProjectStatusDTO) => { return useQuery({ - queryKey: workspaceKeys.getProjectUpgradeStatus(projectId), + queryKey: projectKeys.getProjectUpgradeStatus(projectId), queryFn: () => fetchProjectUpgradeStatus(projectId), enabled, refetchInterval @@ -116,44 +99,36 @@ export const useGetUpgradeProjectStatus = ({ }; const fetchUserWorkspaces = async (includeRoles?: boolean, type?: ProjectType | "all") => { - const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", { + const { data } = await apiRequest.get<{ projects: Project[] }>("/api/v1/projects", { params: { includeRoles, type } }); - return data.workspaces; + return data.projects; }; -export const useGetWorkspaceIndexStatus = (workspaceId: string) => { +export const useGetWorkspaceIndexStatus = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceIndexStatus(workspaceId), - queryFn: () => fetchWorkspaceIndexStatus(workspaceId), - enabled: true - }); -}; - -export const useGetWorkspaceSecrets = (workspaceId: string) => { - return useQuery({ - queryKey: workspaceKeys.getWorkspaceSecrets(workspaceId), - queryFn: () => fetchWorkspaceSecrets(workspaceId), + queryKey: projectKeys.getProjectIndexStatus(projectId), + queryFn: () => fetchWorkspaceIndexStatus(projectId), enabled: true }); }; export const useGetWorkspaceById = ( - workspaceId: string, + projectId: string, dto?: { refetchInterval?: number | false } ) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceById(workspaceId), - queryFn: () => fetchWorkspaceById(workspaceId), - enabled: Boolean(workspaceId), + queryKey: projectKeys.getProjectById(projectId), + queryFn: () => fetchProjectById(projectId), + enabled: Boolean(projectId), refetchInterval: dto?.refetchInterval }); }; -export const useGetUserWorkspaces = ({ +export const useGetUserProjects = ({ includeRoles, options = {} }: { @@ -161,19 +136,19 @@ export const useGetUserWorkspaces = ({ options?: { enabled?: boolean }; } = {}) => useQuery({ - queryKey: workspaceKeys.getAllUserWorkspace(), + queryKey: projectKeys.getAllUserProjects(), queryFn: () => fetchUserWorkspaces(includeRoles), ...options }); export const useSearchProjects = ({ options, ...dto }: TSearchProjectsDTO) => useQuery({ - queryKey: workspaceKeys.searchWorkspace(dto), + queryKey: projectKeys.searchProject(dto), queryFn: async () => { const { data } = await apiRequest.post<{ - projects: (Workspace & { isMember: boolean })[]; + projects: (Project & { isMember: boolean })[]; totalCount: number; - }>("/api/v1/workspace/search", dto); + }>("/api/v1/projects/search", dto); return data; }, @@ -181,81 +156,65 @@ export const useSearchProjects = ({ options, ...dto }: TSearchProjectsDTO) => }); const fetchUserWorkspaceMemberships = async (orgId: string) => { - const { data } = await apiRequest.get>( - `/api/v1/organization/${orgId}/workspace-memberships` + const { data } = await apiRequest.get>( + `/api/v1/organization/${orgId}/project-memberships` ); return data; }; -// to get all userids in an org with the workspace they are part of +// to get all userids in an org with the project they are part of export const useGetUserWorkspaceMemberships = (orgId: string) => useQuery({ - queryKey: workspaceKeys.getWorkspaceMemberships(orgId), + queryKey: projectKeys.getProjectMemberships(orgId), queryFn: () => fetchUserWorkspaceMemberships(orgId), enabled: Boolean(orgId) }); -export const useNameWorkspaceSecrets = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ workspaceId, secretsToUpdate }) => - apiRequest.post(`/api/v3/workspaces/${workspaceId}/secrets/names`, { - secretsToUpdate - }), - onSuccess: (_, variables) => { - queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIndexStatus(variables.workspaceId) - }); - } - }); -}; - -const fetchWorkspaceAuthorization = async (workspaceId: string) => { +const fetchWorkspaceAuthorization = async (projectId: string) => { const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>( - `/api/v1/workspace/${workspaceId}/authorizations` + `/api/v1/projects/${projectId}/authorizations` ); return data.authorizations; }; export const useGetWorkspaceAuthorizations = ( - workspaceId: string, + projectId: string, select?: (data: IntegrationAuth[]) => TData ) => useQuery({ - queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId), - queryFn: () => fetchWorkspaceAuthorization(workspaceId), - enabled: Boolean(workspaceId), + queryKey: projectKeys.getProjectAuthorization(projectId), + queryFn: () => fetchWorkspaceAuthorization(projectId), + enabled: Boolean(projectId), select }); -export const fetchWorkspaceIntegrations = async (workspaceId: string) => { +export const fetchWorkspaceIntegrations = async (projectId: string) => { const { data } = await apiRequest.get<{ integrations: TIntegration[] }>( - `/api/v1/workspace/${workspaceId}/integrations` + `/api/v1/projects/${projectId}/integrations` ); return data.integrations; }; -export const useGetWorkspaceIntegrations = (workspaceId: string) => +export const useGetWorkspaceIntegrations = (projectId: string) => useQuery({ - queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId), - queryFn: () => fetchWorkspaceIntegrations(workspaceId), - enabled: Boolean(workspaceId), + queryKey: projectKeys.getProjectIntegrations(projectId), + queryFn: () => fetchWorkspaceIntegrations(projectId), + enabled: Boolean(projectId), refetchInterval: 4000 }); export const createWorkspace = ( dto: CreateWorkspaceDTO -): Promise<{ data: { project: Workspace } }> => { - return apiRequest.post("/api/v2/workspace", dto); +): Promise<{ data: { project: Project } }> => { + return apiRequest.post("/api/v1/projects", dto); }; export const useCreateWorkspace = () => { const queryClient = useQueryClient(); - return useMutation<{ data: { project: Workspace } }, object, CreateWorkspaceDTO>({ + return useMutation<{ data: { project: Project } }, object, CreateWorkspaceDTO>({ mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) => createWorkspace({ projectName, @@ -266,7 +225,7 @@ export const useCreateWorkspace = () => { }), onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); @@ -275,85 +234,37 @@ export const useCreateWorkspace = () => { export const useUpdateProject = () => { const queryClient = useQueryClient(); - return useMutation({ + return useMutation({ mutationFn: async ({ - projectID, + projectId: projectID, newProjectName, + hasDeleteProtection, newProjectDescription, newSlug, secretSharing, showSnapshotsLegacy, - secretDetectionIgnoreValues + secretDetectionIgnoreValues, + autoCapitalization, + pitVersionLimit }) => { - const { data } = await apiRequest.patch<{ workspace: Workspace }>( - `/api/v1/workspace/${projectID}`, + const { data } = await apiRequest.patch<{ project: Project }>( + `/api/v1/projects/${projectID}`, { name: newProjectName, description: newProjectDescription, slug: newSlug, secretSharing, showSnapshotsLegacy, - secretDetectionIgnoreValues + secretDetectionIgnoreValues, + autoCapitalization, + pitVersionLimit, + hasDeleteProtection } ); - return data.workspace; + return data.project; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); - } - }); -}; - -export const useToggleAutoCapitalization = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ workspaceID, state }) => { - const { data } = await apiRequest.post<{ workspace: Workspace }>( - `/api/v1/workspace/${workspaceID}/auto-capitalization`, - { - autoCapitalization: state - } - ); - return data.workspace; - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); - } - }); -}; - -export const useToggleDeleteProjectProtection = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ workspaceID, state }) => { - const { data } = await apiRequest.post<{ workspace: Workspace }>( - `/api/v1/workspace/${workspaceID}/delete-protection`, - { - hasDeleteProtection: state - } - ); - return data.workspace; - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); - } - }); -}; - -export const useUpdateWorkspaceVersionLimit = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ projectSlug, pitVersionLimit }) => { - const { data } = await apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, { - pitVersionLimit - }); - return data.workspace; - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + queryClient.invalidateQueries({ queryKey: projectKeys.getAllUserProjects() }); } }); }; @@ -361,18 +272,18 @@ export const useUpdateWorkspaceVersionLimit = () => { export const useUpdateWorkspaceAuditLogsRetention = () => { const queryClient = useQueryClient(); - return useMutation({ + return useMutation({ mutationFn: async ({ projectSlug, auditLogsRetentionDays }) => { const { data } = await apiRequest.put( - `/api/v1/workspace/${projectSlug}/audit-logs-retention`, + `/api/v1/projects/${projectSlug}/audit-logs-retention`, { auditLogsRetentionDays } ); - return data.workspace; + return data.project; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + queryClient.invalidateQueries({ queryKey: projectKeys.getAllUserProjects() }); } }); }; @@ -380,13 +291,13 @@ export const useUpdateWorkspaceAuditLogsRetention = () => { export const useDeleteWorkspace = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ workspaceID }) => { - const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`); - return data.workspace; + return useMutation({ + mutationFn: async ({ projectID }) => { + const { data } = await apiRequest.delete(`/api/v1/projects/${projectID}`); + return data.project; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + queryClient.invalidateQueries({ queryKey: projectKeys.getAllUserProjects() }); queryClient.invalidateQueries({ queryKey: ["org-admin-projects"] }); @@ -397,10 +308,10 @@ export const useDeleteWorkspace = () => { export const useCreateWsEnvironment = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ workspaceId, name, slug }) => { - const { data } = await apiRequest.post<{ environment: WorkspaceEnv }>( - `/api/v1/workspace/${workspaceId}/environments`, + return useMutation({ + mutationFn: async ({ projectId, name, slug }) => { + const { data } = await apiRequest.post<{ environment: ProjectEnv }>( + `/api/v1/projects/${projectId}/environments`, { name, slug @@ -410,7 +321,7 @@ export const useCreateWsEnvironment = () => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); @@ -420,8 +331,8 @@ export const useUpdateWsEnvironment = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ workspaceId, id, name, slug, position }) => { - return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments/${id}`, { + mutationFn: ({ projectId, id, name, slug, position }) => { + return apiRequest.patch(`/api/v1/projects/${projectId}/environments/${id}`, { name, slug, position @@ -429,7 +340,7 @@ export const useUpdateWsEnvironment = () => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); @@ -439,57 +350,54 @@ export const useDeleteWsEnvironment = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ id, workspaceId }) => { - return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`); + mutationFn: ({ id, projectId }) => { + return apiRequest.delete(`/api/v1/projects/${projectId}/environments/${id}`); }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }); }; export const useGetWorkspaceUsers = ( - workspaceId: string, + projectId: string, includeGroupMembers?: boolean, roles?: string[] ) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceUsers(workspaceId, includeGroupMembers, roles), + queryKey: projectKeys.getProjectUsers(projectId, includeGroupMembers, roles), queryFn: async () => { const { data: { users } - } = await apiRequest.get<{ users: TWorkspaceUser[] }>( - `/api/v1/workspace/${workspaceId}/users`, - { - params: { - includeGroupMembers, - roles: - roles && roles.length > 0 - ? roles.map((role) => encodeURIComponent(role)).join(",") - : undefined - } + } = await apiRequest.get<{ users: TWorkspaceUser[] }>(`/api/v1/projects/${projectId}/users`, { + params: { + includeGroupMembers, + roles: + roles && roles.length > 0 + ? roles.map((role) => encodeURIComponent(role)).join(",") + : undefined } - ); + }); return users; }, enabled: true }); }; -export const useGetWorkspaceUserDetails = (workspaceId: string, membershipId: string) => { +export const useGetWorkspaceUserDetails = (projectId: string, membershipId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceUserDetails(workspaceId, membershipId), + queryKey: projectKeys.getProjectUserDetails(projectId, membershipId), queryFn: async () => { const { data: { membership } } = await apiRequest.get<{ membership: TWorkspaceUser }>( - `/api/v1/workspace/${workspaceId}/memberships/${membershipId}` + `/api/v1/projects/${projectId}/memberships/${membershipId}` ); return membership; }, - enabled: Boolean(workspaceId) && Boolean(membershipId) + enabled: Boolean(projectId) && Boolean(membershipId) }); }; @@ -499,21 +407,21 @@ export const useDeleteUserFromWorkspace = () => { return useMutation({ mutationFn: async ({ usernames, - workspaceId + projectId }: { - workspaceId: string; + projectId: string; usernames: string[]; orgId: string; }) => { const { data: { deletedMembership } - } = await apiRequest.delete(`/api/v2/workspace/${workspaceId}/memberships`, { + } = await apiRequest.delete(`/api/v1/projects/${projectId}/memberships`, { data: { usernames } }); return deletedMembership; }, - onSuccess: (_, { orgId, workspaceId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceUsers(workspaceId) }); + onSuccess: (_, { orgId, projectId }) => { + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectUsers(projectId) }); queryClient.invalidateQueries({ queryKey: userKeys.allOrgMembershipProjectMemberships(orgId) }); @@ -524,21 +432,21 @@ export const useDeleteUserFromWorkspace = () => { export const useUpdateUserWorkspaceRole = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ membershipId, roles, workspaceId }: TUpdateWorkspaceUserRoleDTO) => { + mutationFn: async ({ membershipId, roles, projectId }: TUpdateWorkspaceUserRoleDTO) => { const { data: { membership } } = await apiRequest.patch<{ membership: { projectId: string } }>( - `/api/v1/workspace/${workspaceId}/memberships/${membershipId}`, + `/api/v1/projects/${projectId}/memberships/${membershipId}`, { roles } ); return membership; }, - onSuccess: (_, { workspaceId, membershipId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceUsers(workspaceId) }); + onSuccess: (_, { projectId, membershipId }) => { + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectUsers(projectId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceUserDetails(workspaceId, membershipId) + queryKey: projectKeys.getProjectUserDetails(projectId, membershipId) }); } }); @@ -549,17 +457,17 @@ export const useAddIdentityToWorkspace = () => { return useMutation({ mutationFn: async ({ identityId, - workspaceId, + projectId, role }: { identityId: string; - workspaceId: string; + projectId: string; role?: string; }) => { const { data: { identityMembership } } = await apiRequest.post( - `/api/v2/workspace/${workspaceId}/identity-memberships/${identityId}`, + `/api/v1/projects/${projectId}/identity-memberships/${identityId}`, { role } @@ -567,9 +475,9 @@ export const useAddIdentityToWorkspace = () => { return identityMembership; }, - onSuccess: (_, { identityId, workspaceId }) => { + onSuccess: (_, { identityId, projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIdentityMemberships(workspaceId) + queryKey: projectKeys.getProjectIdentityMemberships(projectId) }); queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityProjectMemberships(identityId) @@ -581,11 +489,11 @@ export const useAddIdentityToWorkspace = () => { export const useUpdateIdentityWorkspaceRole = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ identityId, workspaceId, roles }: TUpdateWorkspaceIdentityRoleDTO) => { + mutationFn: async ({ identityId, projectId, roles }: TUpdateWorkspaceIdentityRoleDTO) => { const { data: { identityMembership } } = await apiRequest.patch( - `/api/v2/workspace/${workspaceId}/identity-memberships/${identityId}`, + `/api/v1/projects/${projectId}/identity-memberships/${identityId}`, { roles } @@ -593,15 +501,15 @@ export const useUpdateIdentityWorkspaceRole = () => { return identityMembership; }, - onSuccess: (_, { identityId, workspaceId }) => { + onSuccess: (_, { identityId, projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIdentityMemberships(workspaceId) + queryKey: projectKeys.getProjectIdentityMemberships(projectId) }); queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityProjectMemberships(identityId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIdentityMembershipDetails(workspaceId, identityId) + queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId) }); } }); @@ -610,23 +518,17 @@ export const useUpdateIdentityWorkspaceRole = () => { export const useDeleteIdentityFromWorkspace = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ - identityId, - workspaceId - }: { - identityId: string; - workspaceId: string; - }) => { + mutationFn: async ({ identityId, projectId }: { identityId: string; projectId: string }) => { const { data: { identityMembership } } = await apiRequest.delete( - `/api/v2/workspace/${workspaceId}/identity-memberships/${identityId}` + `/api/v1/projects/${projectId}/identity-memberships/${identityId}` ); return identityMembership; }, - onSuccess: (_, { identityId, workspaceId }) => { + onSuccess: (_, { identityId, projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceIdentityMemberships(workspaceId) + queryKey: projectKeys.getProjectIdentityMemberships(projectId) }); queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityProjectMemberships(identityId) @@ -637,7 +539,7 @@ export const useDeleteIdentityFromWorkspace = () => { export const useGetWorkspaceIdentityMemberships = ( { - workspaceId, + projectId, offset = 0, limit = 100, orderBy = ProjectIdentityOrderBy.Name, @@ -649,14 +551,14 @@ export const useGetWorkspaceIdentityMemberships = ( TProjectIdentitiesList, unknown, TProjectIdentitiesList, - ReturnType + ReturnType >, "queryKey" | "queryFn" > ) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceIdentityMembershipsWithParams({ - workspaceId, + queryKey: projectKeys.getProjectIdentityMembershipsWithParams({ + projectId, offset, limit, orderBy, @@ -673,7 +575,7 @@ export const useGetWorkspaceIdentityMemberships = ( }); const { data } = await apiRequest.get( - `/api/v2/workspace/${workspaceId}/identity-memberships`, + `/api/v1/projects/${projectId}/identity-memberships`, { params } ); return data; @@ -686,12 +588,12 @@ export const useGetWorkspaceIdentityMemberships = ( export const useGetWorkspaceIdentityMembershipDetails = (projectId: string, identityId: string) => { return useQuery({ enabled: Boolean(projectId && identityId), - queryKey: workspaceKeys.getWorkspaceIdentityMembershipDetails(projectId, identityId), + queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId), queryFn: async () => { const { data: { identityMembership } } = await apiRequest.get<{ identityMembership: IdentityMembership }>( - `/api/v2/workspace/${projectId}/identity-memberships/${identityId}` + `/api/v1/projects/${projectId}/identity-memberships/${identityId}` ); return identityMembership; } @@ -701,12 +603,12 @@ export const useGetWorkspaceIdentityMembershipDetails = (projectId: string, iden export const useGetWorkspaceGroupMembershipDetails = (projectId: string, groupId: string) => { return useQuery({ enabled: Boolean(projectId && groupId), - queryKey: workspaceKeys.getWorkspaceGroupMembershipDetails(projectId, groupId), + queryKey: projectKeys.getProjectGroupMembershipDetails(projectId, groupId), queryFn: async () => { const { data: { groupMembership } } = await apiRequest.get<{ groupMembership: TGroupMembership }>( - `/api/v2/workspace/${projectId}/groups/${groupId}` + `/api/v1/projects/${projectId}/groups/${groupId}` ); return groupMembership; } @@ -715,12 +617,12 @@ export const useGetWorkspaceGroupMembershipDetails = (projectId: string, groupId export const useListWorkspaceGroups = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId), + queryKey: projectKeys.getProjectGroupMemberships(projectId), queryFn: async () => { const { data: { groupMemberships } } = await apiRequest.get<{ groupMemberships: TGroupMembership[] }>( - `/api/v2/workspace/${projectId}/groups` + `/api/v1/projects/${projectId}/groups` ); return groupMemberships; }, @@ -729,15 +631,15 @@ export const useListWorkspaceGroups = (projectId: string) => { }; export const useListWorkspaceCas = ({ - projectSlug, + projectId, status }: { - projectSlug: string; + projectId: string; status?: CaStatus; }) => { return useQuery({ - queryKey: workspaceKeys.specificWorkspaceCas({ - projectSlug, + queryKey: projectKeys.specificProjectCas({ + projectId, status }), queryFn: async () => { @@ -748,29 +650,29 @@ export const useListWorkspaceCas = ({ const { data: { cas } } = await apiRequest.get<{ cas: TCertificateAuthority[] }>( - `/api/v2/workspace/${projectSlug}/cas`, + `/api/v1/projects/${projectId}/cas`, { params } ); return cas; }, - enabled: Boolean(projectSlug) + enabled: Boolean(projectId) }); }; export const useListWorkspaceCertificates = ({ - projectSlug, + projectId, offset, limit }: { - projectSlug: string; + projectId: string; offset: number; limit: number; }) => { return useQuery({ - queryKey: workspaceKeys.specificWorkspaceCertificates({ - slug: projectSlug, + queryKey: projectKeys.specificProjectCertificates({ + projectId, offset, limit }), @@ -783,7 +685,7 @@ export const useListWorkspaceCertificates = ({ const { data: { certificates, totalCount } } = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>( - `/api/v2/workspace/${projectSlug}/certificates`, + `/api/v1/projects/${projectId}/certificates`, { params } @@ -791,55 +693,53 @@ export const useListWorkspaceCertificates = ({ return { certificates, totalCount }; }, - enabled: Boolean(projectSlug) + enabled: Boolean(projectId) }); }; -export const useListWorkspacePkiAlerts = ({ workspaceId }: { workspaceId: string }) => { +export const useListWorkspacePkiAlerts = ({ projectId }: { projectId: string }) => { return useQuery({ - queryKey: workspaceKeys.getWorkspacePkiAlerts(workspaceId), + queryKey: projectKeys.getProjectPkiAlerts(projectId), queryFn: async () => { const { data: { alerts } - } = await apiRequest.get<{ alerts: TPkiAlert[] }>( - `/api/v2/workspace/${workspaceId}/pki-alerts` - ); + } = await apiRequest.get<{ alerts: TPkiAlert[] }>(`/api/v1/projects/${projectId}/pki-alerts`); return { alerts }; }, - enabled: Boolean(workspaceId) + enabled: Boolean(projectId) }); }; -export const useListWorkspacePkiCollections = ({ workspaceId }: { workspaceId: string }) => { +export const useListWorkspacePkiCollections = ({ projectId }: { projectId: string }) => { return useQuery({ - queryKey: workspaceKeys.getWorkspacePkiCollections(workspaceId), + queryKey: projectKeys.getProjectPkiCollections(projectId), queryFn: async () => { const { data: { collections } } = await apiRequest.get<{ collections: TPkiCollection[] }>( - `/api/v2/workspace/${workspaceId}/pki-collections` + `/api/v1/projects/${projectId}/pki-collections` ); return { collections }; }, - enabled: Boolean(workspaceId) + enabled: Boolean(projectId) }); }; -export const useListWorkspaceCertificateTemplates = ({ workspaceId }: { workspaceId: string }) => { +export const useListWorkspaceCertificateTemplates = ({ projectId }: { projectId: string }) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceCertificateTemplates(workspaceId), + queryKey: projectKeys.getProjectCertificateTemplates(projectId), queryFn: async () => { const { data: { certificateTemplates } } = await apiRequest.get<{ certificateTemplates: TCertificateTemplate[] }>( - `/api/v2/workspace/${workspaceId}/certificate-templates` + `/api/v1/projects/${projectId}/certificate-templates` ); return { certificateTemplates }; }, - enabled: Boolean(workspaceId) + enabled: Boolean(projectId) }); }; @@ -853,7 +753,7 @@ export const useListWorkspaceSshCertificates = ({ projectId: string; }) => { return useQuery({ - queryKey: workspaceKeys.specificWorkspaceSshCertificates({ + queryKey: projectKeys.specificProjectSshCertificates({ offset, limit, projectId @@ -867,7 +767,7 @@ export const useListWorkspaceSshCertificates = ({ const { data } = await apiRequest.get<{ certificates: TSshCertificate[]; totalCount: number; - }>(`/api/v2/workspace/${projectId}/ssh-certificates`, { + }>(`/api/v1/projects/${projectId}/ssh-certificates`, { params }); return data; @@ -878,12 +778,12 @@ export const useListWorkspaceSshCertificates = ({ export const useListWorkspaceSshCas = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceSshCas(projectId), + queryKey: projectKeys.getProjectSshCas(projectId), queryFn: async () => { const { data: { cas } } = await apiRequest.get<{ cas: Omit[] }>( - `/api/v2/workspace/${projectId}/ssh-cas` + `/api/v1/projects/${projectId}/ssh-cas` ); return cas; }, @@ -893,11 +793,11 @@ export const useListWorkspaceSshCas = (projectId: string) => { export const useListWorkspaceSshHosts = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceSshHosts(projectId), + queryKey: projectKeys.getProjectSshHosts(projectId), queryFn: async () => { const { data: { hosts } - } = await apiRequest.get<{ hosts: TSshHost[] }>(`/api/v2/workspace/${projectId}/ssh-hosts`); + } = await apiRequest.get<{ hosts: TSshHost[] }>(`/api/v1/projects/${projectId}/ssh-hosts`); return hosts; }, enabled: Boolean(projectId) @@ -906,12 +806,12 @@ export const useListWorkspaceSshHosts = (projectId: string) => { export const useListWorkspacePkiSubscribers = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId), + queryKey: projectKeys.getProjectPkiSubscribers(projectId), queryFn: async () => { const { data: { subscribers } } = await apiRequest.get<{ subscribers: TPkiSubscriber[] }>( - `/api/v2/workspace/${projectId}/pki-subscribers` + `/api/v1/projects/${projectId}/pki-subscribers` ); return subscribers; }, @@ -921,12 +821,12 @@ export const useListWorkspacePkiSubscribers = (projectId: string) => { export const useListWorkspaceSshHostGroups = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId), + queryKey: projectKeys.getProjectSshHostGroups(projectId), queryFn: async () => { const { data: { groups } } = await apiRequest.get<{ groups: (TSshHostGroup & { hostCount: number })[] }>( - `/api/v2/workspace/${projectId}/ssh-host-groups` + `/api/v1/projects/${projectId}/ssh-host-groups` ); return groups; }, @@ -936,10 +836,10 @@ export const useListWorkspaceSshHostGroups = (projectId: string) => { export const useListWorkspaceSshCertificateTemplates = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceSshCertificateTemplates(projectId), + queryKey: projectKeys.getProjectSshCertificateTemplates(projectId), queryFn: async () => { const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>( - `/api/v2/workspace/${projectId}/ssh-certificate-templates` + `/api/v1/projects/${projectId}/ssh-certificate-templates` ); return data; }, @@ -948,18 +848,18 @@ export const useListWorkspaceSshCertificateTemplates = (projectId: string) => { }; export const useGetWorkspaceWorkflowIntegrationConfig = ({ - workspaceId, + projectId, integration }: { - workspaceId: string; + projectId: string; integration: WorkflowIntegrationPlatform; }) => { return useQuery({ - queryKey: workspaceKeys.getWorkspaceWorkflowIntegrationConfig(workspaceId, integration), + queryKey: projectKeys.getProjectWorkflowIntegrationConfig(projectId, integration), queryFn: async () => { const { data } = await apiRequest .get( - `/api/v1/workspace/${workspaceId}/workflow-integration-config/${integration}` + `/api/v1/projects/${projectId}/workflow-integration-config/${integration}` ) .catch((err) => { if (err.response.status === 404) { @@ -971,16 +871,16 @@ export const useGetWorkspaceWorkflowIntegrationConfig = ({ return data; }, - enabled: Boolean(workspaceId) + enabled: Boolean(projectId) }); }; export const useGetProjectSshConfig = (projectId: string) => { return useQuery({ - queryKey: workspaceKeys.getProjectSshConfig(projectId), + queryKey: projectKeys.getProjectSshConfig(projectId), queryFn: async () => { const { data } = await apiRequest.get( - `/api/v1/workspace/${projectId}/ssh-config` + `/api/v1/projects/${projectId}/ssh-config` ); return data; diff --git a/frontend/src/hooks/api/projects/query-keys.tsx b/frontend/src/hooks/api/projects/query-keys.tsx new file mode 100644 index 000000000..51e3db71c --- /dev/null +++ b/frontend/src/hooks/api/projects/query-keys.tsx @@ -0,0 +1,77 @@ +import type { CaStatus } from "../ca"; +import { WorkflowIntegrationPlatform } from "../workflowIntegrations/types"; +import { TListProjectIdentitiesDTO, TSearchProjectsDTO } from "./types"; + +export const projectKeys = { + getProjectById: (projectId: string) => ["projects", { projectId }] as const, + getProjectSecrets: (projectId: string) => [{ projectId }, "project-secrets"] as const, + getProjectIndexStatus: (projectId: string) => [{ projectId }, "project-index-status"] as const, + getProjectUpgradeStatus: (projectId: string) => [{ projectId }, "project-upgrade-status"], + getProjectMemberships: (orgId: string) => [{ orgId }, "project-memberships"], + getProjectAuthorization: (projectId: string) => [{ projectId }, "project-authorizations"], + getProjectIntegrations: (projectId: string) => [{ projectId }, "project-integrations"], + getAllUserProjects: () => ["projects"] as const, + getProjectAuditLogs: (projectId: string) => [{ projectId }, "project-audit-logs"] as const, + getProjectUsers: ( + projectId: string, + includeGroupMembers: boolean = false, + roles: string[] = [] + ) => [{ projectId, includeGroupMembers, roles }, "project-users"] as const, + getProjectUserDetails: (projectId: string, membershipId: string) => + [{ projectId, membershipId }, "project-user-details"] as const, + getProjectIdentityMemberships: (projectId: string) => + [{ projectId }, "project-identity-memberships"] as const, + getProjectIdentityMembershipDetails: (projectId: string, identityId: string) => + [{ projectId, identityId }, "project-identity-membership-details"] as const, + // allows invalidation using above key without knowing params + getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) => + [...projectKeys.getProjectIdentityMemberships(projectId), params] as const, + searchProject: (dto: TSearchProjectsDTO) => ["search-projects", dto] as const, + getProjectGroupMemberships: (projectId: string) => [{ projectId }, "project-groups"] as const, + getProjectGroupMembershipDetails: (projectId: string, groupId: string) => + [{ projectId, groupId }, "project-group-membership-details"] as const, + getProjectCas: ({ projectId }: { projectId: string }) => [{ projectId }, "project-cas"] as const, + specificProjectCas: ({ projectId, status }: { projectId: string; status?: CaStatus }) => + [...projectKeys.getProjectCas({ projectId }), { status }] as const, + allProjectCertificates: () => ["project-certificates"] as const, + forProjectCertificates: (projectId: string) => + [...projectKeys.allProjectCertificates(), projectId] as const, + specificProjectCertificates: ({ + projectId, + offset, + limit + }: { + projectId: string; + offset: number; + limit: number; + }) => [...projectKeys.forProjectCertificates(projectId), { offset, limit }] as const, + getProjectPkiAlerts: (projectId: string) => [{ projectId }, "project-pki-alerts"] as const, + getProjectPkiSubscribers: (projectId: string) => + [{ projectId }, "project-pki-subscribers"] as const, + getProjectPkiCollections: (projectId: string) => + [{ projectId }, "project-pki-collections"] as const, + getProjectCertificateTemplates: (projectId: string) => + [{ projectId }, "project-certificate-templates"] as const, + getProjectWorkflowIntegrationConfig: ( + projectId: string, + integration: WorkflowIntegrationPlatform + ) => [{ projectId, integration }, "project-workflow-integration-config"] as const, + getProjectSshCas: (projectId: string) => [{ projectId }, "project-ssh-cas"] as const, + allProjectSshCertificates: (projectId: string) => + [{ projectId }, "project-ssh-certificates"] as const, + getProjectSshHosts: (projectId: string) => [{ projectId }, "project-ssh-hosts"] as const, + getProjectSshHostGroups: (projectId: string) => + [{ projectId }, "project-ssh-host-groups"] as const, + specificProjectSshCertificates: ({ + offset, + limit, + projectId + }: { + offset: number; + limit: number; + projectId: string; + }) => [...projectKeys.allProjectSshCertificates(projectId), { offset, limit }] as const, + getProjectSshCertificateTemplates: (projectId: string) => + [{ projectId }, "project-ssh-certificate-templates"] as const, + getProjectSshConfig: (projectId: string) => [{ projectId }, "project-ssh-config"] as const +}; diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/projects/types.ts similarity index 84% rename from frontend/src/hooks/api/workspace/types.ts rename to frontend/src/hooks/api/projects/types.ts index 33eb0909f..59b242d05 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/projects/types.ts @@ -20,7 +20,7 @@ export enum ProjectUserMembershipTemporaryMode { Relative = "relative" } -export type Workspace = { +export type Project = { __v: number; id: string; name: string; @@ -31,7 +31,7 @@ export type Workspace = { upgradeStatus: string | null; updatedAt: string; autoCapitalization: boolean; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; pitVersionLimit: number; auditLogsRetentionDays: number; slug: string; @@ -43,16 +43,16 @@ export type Workspace = { secretDetectionIgnoreValues: string[]; }; -export type WorkspaceEnv = { +export type ProjectEnv = { id: string; name: string; slug: string; }; -export type WorkspaceTag = { id: string; name: string; slug: string }; +export type ProjectTag = { id: string; name: string; slug: string }; export type NameWorkspaceSecretsDTO = { - workspaceId: string; + projectId: string; secretsToUpdate: { secretName: string; secretId: string; @@ -76,30 +76,33 @@ export type CreateWorkspaceDTO = { }; export type UpdateProjectDTO = { - projectID: string; + projectId: string; newProjectName?: string; newProjectDescription?: string; newSlug?: string; secretSharing?: boolean; showSnapshotsLegacy?: boolean; secretDetectionIgnoreValues?: string[]; + pitVersionLimit?: number; + autoCapitalization?: boolean; + hasDeleteProtection?: boolean; }; export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number }; export type UpdateAuditLogsRetentionDTO = { projectSlug: string; auditLogsRetentionDays: number }; -export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean }; -export type ToggleDeleteProjectProtectionDTO = { workspaceID: string; state: boolean }; +export type ToggleAutoCapitalizationDTO = { projectID: string; state: boolean }; +export type ToggleDeleteProjectProtectionDTO = { projectID: string; state: boolean }; -export type DeleteWorkspaceDTO = { workspaceID: string }; +export type DeleteWorkspaceDTO = { projectID: string }; export type CreateEnvironmentDTO = { - workspaceId: string; + projectId: string; name: string; slug: string; }; export type ReorderEnvironmentsDTO = { - workspaceId: string; + projectId: string; environmentSlug: string; environmentName: string; otherEnvironmentSlug: string; @@ -107,18 +110,18 @@ export type ReorderEnvironmentsDTO = { }; export type UpdateEnvironmentDTO = { - workspaceId: string; + projectId: string; id: string; name?: string; slug?: string; position?: number; }; -export type DeleteEnvironmentDTO = { workspaceId: string; id: string }; +export type DeleteEnvironmentDTO = { projectId: string; id: string }; export type TUpdateWorkspaceUserRoleDTO = { membershipId: string; - workspaceId: string; + projectId: string; roles: ( | { role: string; @@ -136,7 +139,7 @@ export type TUpdateWorkspaceUserRoleDTO = { export type TUpdateWorkspaceIdentityRoleDTO = { identityId: string; - workspaceId: string; + projectId: string; roles: ( | { role: string; @@ -171,7 +174,7 @@ export type TUpdateWorkspaceGroupRoleDTO = { }; export type TListProjectIdentitiesDTO = { - workspaceId: string; + projectId: string; offset?: number; limit?: number; orderBy?: ProjectIdentityOrderBy; @@ -186,6 +189,7 @@ export type TSearchProjectsDTO = { name?: string; limit?: number; offset?: number; + projectIds?: string[]; type?: ProjectType; options?: { enabled?: boolean }; orderBy?: ProjectIdentityOrderBy; diff --git a/frontend/src/hooks/api/roles/mutation.tsx b/frontend/src/hooks/api/roles/mutation.tsx index 6df9aa937..e9caa68d5 100644 --- a/frontend/src/hooks/api/roles/mutation.tsx +++ b/frontend/src/hooks/api/roles/mutation.tsx @@ -22,7 +22,7 @@ export const useCreateProjectRole = () => { mutationFn: async ({ projectId, ...dto }: TCreateProjectRoleDTO) => { const { data: { role } - } = await apiRequest.post(`/api/v2/workspace/${projectId}/roles`, dto); + } = await apiRequest.post(`/api/v1/projects/${projectId}/roles`, dto); return role; }, onSuccess: (_, { projectId }) => { @@ -38,7 +38,7 @@ export const useUpdateProjectRole = () => { mutationFn: async ({ id, projectId, ...dto }: TUpdateProjectRoleDTO) => { const { data: { role } - } = await apiRequest.patch(`/api/v2/workspace/${projectId}/roles/${id}`, dto); + } = await apiRequest.patch(`/api/v1/projects/${projectId}/roles/${id}`, dto); return role; }, onSuccess: (_, { projectId, slug }) => { @@ -58,7 +58,7 @@ export const useDeleteProjectRole = () => { mutationFn: async ({ projectId, id }: TDeleteProjectRoleDTO) => { const { data: { role } - } = await apiRequest.delete(`/api/v2/workspace/${projectId}/roles/${id}`); + } = await apiRequest.delete(`/api/v1/projects/${projectId}/roles/${id}`); return role; }, onSuccess: (_, { projectId }) => { diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index a406b6d45..fd7b28db9 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -46,13 +46,13 @@ export const roleQueryKeys = { getOrgRole: (orgId: string, roleId: string) => [{ orgId, roleId }, "org-role"] as const, getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) => ["user-permissions", { orgId }] as const, - getUserProjectPermissions: ({ workspaceId }: TGetUserProjectPermissionDTO) => - ["user-project-permissions", { workspaceId }] as const + getUserProjectPermissions: ({ projectId }: TGetUserProjectPermissionDTO) => + ["user-project-permissions", { projectId }] as const }; export const getProjectRoles = async (projectId: string) => { const { data } = await apiRequest.get<{ roles: Array> }>( - `/api/v2/workspace/${projectId}/roles` + `/api/v1/projects/${projectId}/roles` ); return data.roles; }; @@ -69,7 +69,7 @@ export const useGetProjectRoleBySlug = (projectId: string, roleSlug: string) => queryKey: roleQueryKeys.getProjectRoleBySlug(projectId, roleSlug), queryFn: async () => { const { data } = await apiRequest.get<{ role: TProjectRole }>( - `/api/v2/workspace/${projectId}/roles/slug/${roleSlug}` + `/api/v1/projects/${projectId}/roles/slug/${roleSlug}` ); return data.role; }, @@ -131,9 +131,7 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) = } }); -export const fetchUserProjectPermissions = async ({ - workspaceId -}: TGetUserProjectPermissionDTO) => { +export const fetchUserProjectPermissions = async ({ projectId }: TGetUserProjectPermissionDTO) => { const { data } = await apiRequest.get<{ data: { permissions: PackRule>>[]; @@ -145,16 +143,16 @@ export const fetchUserProjectPermissions = async ({ actorName: string; }; }; - }>(`/api/v1/workspace/${workspaceId}/permissions`, {}); + }>(`/api/v1/projects/${projectId}/permissions`, {}); return data.data; }; -export const useGetUserProjectPermissions = ({ workspaceId }: TGetUserProjectPermissionDTO) => +export const useGetUserProjectPermissions = ({ projectId }: TGetUserProjectPermissionDTO) => useQuery({ - queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId }), - enabled: Boolean(workspaceId), + queryKey: roleQueryKeys.getUserProjectPermissions({ projectId }), + queryFn: () => fetchUserProjectPermissions({ projectId }), + enabled: Boolean(projectId), select: (data) => { const rule = unpackRules>>(data.permissions); const negatedRules = groupBy( diff --git a/frontend/src/hooks/api/roles/types.ts b/frontend/src/hooks/api/roles/types.ts index 12286bf9b..e94f13305 100644 --- a/frontend/src/hooks/api/roles/types.ts +++ b/frontend/src/hooks/api/roles/types.ts @@ -9,7 +9,7 @@ export enum ProjectMembershipRole { } export type TGetProjectRolesDTO = { - workspaceId?: string; + projectId?: string; }; export type TProjectRole = { @@ -52,7 +52,7 @@ export type TGetUserOrgPermissionsDTO = { }; export type TGetUserProjectPermissionDTO = { - workspaceId: string; + projectId: string; }; export type TCreateOrgRoleDTO = { diff --git a/frontend/src/hooks/api/secretApproval/mutation.tsx b/frontend/src/hooks/api/secretApproval/mutation.tsx index 8370d0367..c8fe51d19 100644 --- a/frontend/src/hooks/api/secretApproval/mutation.tsx +++ b/frontend/src/hooks/api/secretApproval/mutation.tsx @@ -11,7 +11,7 @@ export const useCreateSecretApprovalPolicy = () => { return useMutation({ mutationFn: async ({ environments, - workspaceId, + projectId, approvals, approvers, bypassers, @@ -20,9 +20,9 @@ export const useCreateSecretApprovalPolicy = () => { enforcementLevel, allowedSelfApprovals }) => { - const { data } = await apiRequest.post("/api/v1/secret-approvals", { + const { data } = await apiRequest.post("/api/v2/secret-approvals", { environments, - workspaceId, + projectId, approvals, approvers, bypassers, @@ -33,9 +33,9 @@ export const useCreateSecretApprovalPolicy = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); @@ -56,7 +56,7 @@ export const useUpdateSecretApprovalPolicy = () => { allowedSelfApprovals, environments }) => { - const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, { + const { data } = await apiRequest.patch(`/api/v2/secret-approvals/${id}`, { approvals, approvers, bypassers, @@ -68,9 +68,9 @@ export const useUpdateSecretApprovalPolicy = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); @@ -81,12 +81,12 @@ export const useDeleteSecretApprovalPolicy = () => { return useMutation({ mutationFn: async ({ id }) => { - const { data } = await apiRequest.delete(`/api/v1/secret-approvals/${id}`); + const { data } = await apiRequest.delete(`/api/v2/secret-approvals/${id}`); return data; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId) + queryKey: secretApprovalKeys.getApprovalPolicies(projectId) }); } }); diff --git a/frontend/src/hooks/api/secretApproval/queries.tsx b/frontend/src/hooks/api/secretApproval/queries.tsx index 52c3743fb..e81af93b8 100644 --- a/frontend/src/hooks/api/secretApproval/queries.tsx +++ b/frontend/src/hooks/api/secretApproval/queries.tsx @@ -10,54 +10,53 @@ import { } from "./types"; export const secretApprovalKeys = { - getApprovalPolicies: (workspaceId: string) => - [{ workspaceId }, "secret-approval-policies"] as const, - getApprovalPolicyOfABoard: (workspaceId: string, environment: string, secretPath: string) => [ - { workspaceId, environment, secretPath }, + getApprovalPolicies: (projectId: string) => [{ projectId }, "secret-approval-policies"] as const, + getApprovalPolicyOfABoard: (projectId: string, environment: string, secretPath: string) => [ + { projectId, environment, secretPath }, "Secret-approval-policy" ] }; -const fetchApprovalPolicies = async (workspaceId: string) => { +const fetchApprovalPolicies = async (projectId: string) => { const { data } = await apiRequest.get<{ approvals: TSecretApprovalPolicy[] }>( - "/api/v1/secret-approvals", - { params: { workspaceId } } + "/api/v2/secret-approvals", + { params: { projectId } } ); return data.approvals; }; export const useGetSecretApprovalPolicies = ({ - workspaceId, + projectId, options = {} }: TGetSecretApprovalPoliciesDTO & TReactQueryOptions) => useQuery({ - queryKey: secretApprovalKeys.getApprovalPolicies(workspaceId), - queryFn: () => fetchApprovalPolicies(workspaceId), + queryKey: secretApprovalKeys.getApprovalPolicies(projectId), + queryFn: () => fetchApprovalPolicies(projectId), ...options, - enabled: Boolean(workspaceId) && (options?.enabled ?? true) + enabled: Boolean(projectId) && (options?.enabled ?? true) }); const fetchApprovalPolicyOfABoard = async ( - workspaceId: string, + projectId: string, environment: string, secretPath: string ) => { const { data } = await apiRequest.get<{ policy: TSecretApprovalPolicy }>( - "/api/v1/secret-approvals/board", - { params: { workspaceId, environment, secretPath } } + "/api/v2/secret-approvals/board", + { params: { projectId, environment, secretPath } } ); return data.policy || ""; }; export const useGetSecretApprovalPolicyOfABoard = ({ - workspaceId, + projectId, secretPath = "/", environment, options = {} }: TGetSecretApprovalPolicyOfBoardDTO & TReactQueryOptions) => useQuery({ - queryKey: secretApprovalKeys.getApprovalPolicyOfABoard(workspaceId, environment, secretPath), - queryFn: () => fetchApprovalPolicyOfABoard(workspaceId, environment, secretPath), + queryKey: secretApprovalKeys.getApprovalPolicyOfABoard(projectId, environment, secretPath), + queryFn: () => fetchApprovalPolicyOfABoard(projectId, environment, secretPath), ...options, - enabled: Boolean(workspaceId && secretPath && environment) && (options?.enabled ?? true) + enabled: Boolean(projectId && secretPath && environment) && (options?.enabled ?? true) }); diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index 1785ffa8a..ac912c401 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -1,11 +1,11 @@ import { EnforcementLevel } from "../policies/enums"; -import { WorkspaceEnv } from "../workspace/types"; +import { ProjectEnv } from "../projects/types"; export type TSecretApprovalPolicy = { id: string; - workspace: string; + project: string; name: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; secretPath?: string; approvals: number; approvers: Approver[]; @@ -36,17 +36,17 @@ export type Bypasser = { }; export type TGetSecretApprovalPoliciesDTO = { - workspaceId: string; + projectId: string; }; export type TGetSecretApprovalPolicyOfBoardDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; }; export type TCreateSecretPolicyDTO = { - workspaceId: string; + projectId: string; name?: string; environments: string[]; secretPath: string; @@ -67,12 +67,12 @@ export type TUpdateSecretPolicyDTO = { allowedSelfApprovals?: boolean; enforcementLevel?: EnforcementLevel; // for invalidating list - workspaceId: string; + projectId: string; environments?: string[]; }; export type TDeleteSecretPolicyDTO = { id: string; // for invalidating list - workspaceId: string; + projectId: string; }; diff --git a/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx b/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx index 78e1b37a1..6a03b431c 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/mutation.tsx @@ -36,9 +36,9 @@ export const useUpdateSecretApprovalRequestStatus = () => { }); return data; }, - onSuccess: (_, { id, workspaceId }) => { + onSuccess: (_, { id, projectId }) => { queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.detail({ id }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); } }); }; @@ -53,10 +53,10 @@ export const usePerformSecretApprovalRequestMerge = () => { }); return data; }, - onSuccess: (_, { id, workspaceId }) => { + onSuccess: (_, { id, projectId }) => { queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.detail({ id }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.list({ workspaceId }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.list({ projectId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); } }); }; diff --git a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx index a1736f70f..63cdd455e 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx @@ -14,7 +14,7 @@ import { export const secretApprovalRequestKeys = { list: ({ - workspaceId, + projectId, environment, status, committer, @@ -23,20 +23,20 @@ export const secretApprovalRequestKeys = { search }: TGetSecretApprovalRequestList) => [ - { workspaceId, environment, status, committer, offset, limit, search }, + { projectId, environment, status, committer, offset, limit, search }, "secret-approval-requests" ] as const, detail: ({ id }: Omit) => [{ id }, "secret-approval-request-detail"] as const, - count: ({ workspaceId, policyId }: TGetSecretApprovalRequestCount) => [ - { workspaceId }, + count: ({ projectId, policyId }: TGetSecretApprovalRequestCount) => [ + { projectId }, "secret-approval-request-count", ...(policyId ? [policyId] : []) ] }; const fetchSecretApprovalRequestList = async ({ - workspaceId, + projectId, environment, committer, status = "open", @@ -49,7 +49,7 @@ const fetchSecretApprovalRequestList = async ({ totalCount: number; }>("/api/v1/secret-approval-requests", { params: { - workspaceId, + projectId, environment, committer, status, @@ -63,7 +63,7 @@ const fetchSecretApprovalRequestList = async ({ }; export const useGetSecretApprovalRequests = ({ - workspaceId, + projectId, environment, options = {}, status, @@ -74,7 +74,7 @@ export const useGetSecretApprovalRequests = ({ }: TGetSecretApprovalRequestList & TReactQueryOptions) => useQuery({ queryKey: secretApprovalRequestKeys.list({ - workspaceId, + projectId, environment, committer, status, @@ -84,7 +84,7 @@ export const useGetSecretApprovalRequests = ({ }), queryFn: () => fetchSecretApprovalRequestList({ - workspaceId, + projectId, environment, status, committer, @@ -92,7 +92,7 @@ export const useGetSecretApprovalRequests = ({ offset, search }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true), + enabled: Boolean(projectId) && (options?.enabled ?? true), placeholderData: (previousData) => previousData }); @@ -127,19 +127,19 @@ export const useGetSecretApprovalRequestDetails = ({ }); const fetchSecretApprovalRequestCount = async ({ - workspaceId, + projectId, policyId }: TGetSecretApprovalRequestCount) => { const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>( "/api/v1/secret-approval-requests/count", - { params: { workspaceId, policyId } } + { params: { projectId, policyId } } ); return data.approvals; }; export const useGetSecretApprovalRequestCount = ({ - workspaceId, + projectId, policyId, options = {} }: TGetSecretApprovalRequestCount & { @@ -154,8 +154,8 @@ export const useGetSecretApprovalRequestCount = ({ >; }) => useQuery({ - queryKey: secretApprovalRequestKeys.count({ workspaceId, policyId }), + queryKey: secretApprovalRequestKeys.count({ projectId, policyId }), refetchInterval: 15000, - queryFn: () => fetchSecretApprovalRequestCount({ workspaceId, policyId }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true) + queryFn: () => fetchSecretApprovalRequestCount({ projectId, policyId }), + enabled: Boolean(projectId) && (options?.enabled ?? true) }); diff --git a/frontend/src/hooks/api/secretApprovalRequest/types.ts b/frontend/src/hooks/api/secretApprovalRequest/types.ts index 0eef3fa3d..5a381e871 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/types.ts +++ b/frontend/src/hooks/api/secretApprovalRequest/types.ts @@ -55,7 +55,7 @@ export type TSecretApprovalRequest = { username: string; isOrgMembershipActive: boolean; }[]; - workspace: string; + project: string; environment: string; folderId: string; secretPath: string; @@ -110,7 +110,7 @@ export type TSecretApprovalRequestCount = { }; export type TGetSecretApprovalRequestList = { - workspaceId: string; + projectId: string; environment?: string; status?: "open" | "close"; committer?: string; @@ -120,7 +120,7 @@ export type TGetSecretApprovalRequestList = { }; export type TGetSecretApprovalRequestCount = { - workspaceId: string; + projectId: string; policyId?: string; }; @@ -137,11 +137,11 @@ export type TUpdateSecretApprovalReviewStatusDTO = { export type TUpdateSecretApprovalRequestStatusDTO = { status: "open" | "close"; id: string; - workspaceId: string; + projectId: string; }; export type TPerformSecretApprovalRequestMerge = { id: string; - workspaceId: string; + projectId: string; bypassReason?: string; }; diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 9e12f329d..3e752eed3 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -30,10 +30,10 @@ export const folderQueryKeys = { ["secret-folders", "environment", projectId] as const }; -const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => { - const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { +const fetchProjectFolders = async (projectId: string, environment: string, path = "/") => { + const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v2/folders", { params: { - workspaceId, + projectId, environment, path } @@ -57,7 +57,7 @@ export const useListProjectEnvironmentsFolders = ( queryKey: folderQueryKeys.getProjectEnvironmentsFolders(projectId), queryFn: async () => { const { data } = await apiRequest.get( - `/api/v1/workspace/${projectId}/environment-folder-tree` + `/api/v1/projects/${projectId}/environment-folder-tree` ); return data; }, @@ -145,9 +145,9 @@ export const useCreateFolder = () => { return useMutation({ mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/folders", { + const { data } = await apiRequest.post("/api/v2/folders", { ...dto, - workspaceId: dto.projectId + projectId: dto.projectId }); return data; }, @@ -162,13 +162,13 @@ export const useCreateFolder = () => { queryKey: folderQueryKeys.getSecretFolders({ projectId, environment, path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.list({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.count({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: commitKeys.count({ projectId, environment, directory: path }) }); } }); @@ -179,10 +179,10 @@ export const useUpdateFolder = () => { return useMutation({ mutationFn: async ({ path = "/", folderId, name, environment, projectId, description }) => { - const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, { + const { data } = await apiRequest.patch(`/api/v2/folders/${folderId}`, { name, environment, - workspaceId: projectId, + projectId, path, description }); @@ -199,16 +199,16 @@ export const useUpdateFolder = () => { queryKey: folderQueryKeys.getSecretFolders({ projectId, environment, path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.list({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.count({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: commitKeys.count({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId: projectId, environment, directory: path }) + queryKey: commitKeys.history({ projectId, environment, directory: path }) }); } }); @@ -219,10 +219,10 @@ export const useDeleteFolder = () => { return useMutation({ mutationFn: async ({ path = "/", folderId, environment, projectId }) => { - const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, { + const { data } = await apiRequest.delete(`/api/v2/folders/${folderId}`, { data: { environment, - workspaceId: projectId, + projectId, path } }); @@ -239,16 +239,16 @@ export const useDeleteFolder = () => { queryKey: folderQueryKeys.getSecretFolders({ projectId, environment, path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.list({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: secretSnapshotKeys.count({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId: projectId, environment, directory: path }) + queryKey: commitKeys.count({ projectId, environment, directory: path }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId: projectId, environment, directory: path }) + queryKey: commitKeys.history({ projectId, environment, directory: path }) }); } }); @@ -258,9 +258,9 @@ export const useUpdateFolderBatch = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ projectSlug, folders }) => { - const { data } = await apiRequest.patch("/api/v1/folders/batch", { - projectSlug, + mutationFn: async ({ projectId, folders }) => { + const { data } = await apiRequest.patch("/api/v2/folders/batch", { + projectId, folders }); @@ -283,28 +283,28 @@ export const useUpdateFolderBatch = () => { }); queryClient.invalidateQueries({ queryKey: secretSnapshotKeys.list({ - workspaceId: projectId, + projectId, environment: folder.environment, directory: folder.path }) }); queryClient.invalidateQueries({ queryKey: secretSnapshotKeys.count({ - workspaceId: projectId, + projectId, environment: folder.environment, directory: folder.path }) }); queryClient.invalidateQueries({ queryKey: commitKeys.count({ - workspaceId: projectId, + projectId, environment: folder.environment, directory: folder.path }) }); queryClient.invalidateQueries({ queryKey: commitKeys.history({ - workspaceId: projectId, + projectId, environment: folder.environment, directory: folder.path }) diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index dc9bce70f..e9ee2ae5d 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -1,4 +1,4 @@ -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; export enum ReservedFolders { SecretReplication = "__reserve_replication_" @@ -22,7 +22,7 @@ export type TSecretFolder = { export type TSecretFolderWithPath = TSecretFolder & { path: string }; export type TProjectEnvironmentsFolders = { - [key: string]: WorkspaceEnv & { folders: TSecretFolderWithPath[] }; + [key: string]: ProjectEnv & { folders: TSecretFolderWithPath[] }; }; export type TGetProjectFoldersDTO = { @@ -63,7 +63,6 @@ export type TDeleteFolderDTO = { export type TUpdateFolderBatchDTO = { projectId: string; - projectSlug: string; folders: { name: string; environment: string; diff --git a/frontend/src/hooks/api/secretImports/mutation.tsx b/frontend/src/hooks/api/secretImports/mutation.tsx index 72254bae2..c9484ccfb 100644 --- a/frontend/src/hooks/api/secretImports/mutation.tsx +++ b/frontend/src/hooks/api/secretImports/mutation.tsx @@ -16,10 +16,10 @@ export const useCreateSecretImport = () => { return useMutation({ mutationFn: async ({ import: secretImport, environment, isReplication, projectId, path }) => { - const { data } = await apiRequest.post("/api/v1/secret-imports", { + const { data } = await apiRequest.post("/api/v2/secret-imports", { import: secretImport, environment, - workspaceId: projectId, + projectId, path, isReplication }); @@ -44,11 +44,11 @@ export const useUpdateSecretImport = () => { return useMutation({ mutationFn: async ({ environment, import: secretImports, projectId, path, id }) => { - const { data } = await apiRequest.patch(`/api/v1/secret-imports/${id}`, { + const { data } = await apiRequest.patch(`/api/v2/secret-imports/${id}`, { import: secretImports, environment, path, - workspaceId: projectId + projectId }); return data; }, @@ -69,10 +69,10 @@ export const useUpdateSecretImport = () => { export const useResyncSecretReplication = () => { return useMutation({ mutationFn: async ({ environment, projectId, path, id }) => { - const { data } = await apiRequest.post(`/api/v1/secret-imports/${id}/replication-resync`, { + const { data } = await apiRequest.post(`/api/v2/secret-imports/${id}/replication-resync`, { environment, path, - workspaceId: projectId + projectId }); return data; } @@ -84,9 +84,9 @@ export const useDeleteSecretImport = () => { return useMutation({ mutationFn: async ({ id, projectId, path, environment }) => { - const { data } = await apiRequest.delete(`/api/v1/secret-imports/${id}`, { + const { data } = await apiRequest.delete(`/api/v2/secret-imports/${id}`, { data: { - workspaceId: projectId, + projectId, path, environment } diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index ea6ea68ef..71716cb7c 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -30,10 +30,10 @@ export const secretImportKeys = { const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSecretImports) => { const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>( - "/api/v1/secret-imports", + "/api/v2/secret-imports", { params: { - workspaceId: projectId, + projectId, environment, path } @@ -65,16 +65,12 @@ export const useGetSecretImports = ({ queryFn: () => fetchSecretImport({ path, projectId, environment }) }); -const fetchImportedSecrets = async ( - workspaceId: string, - environment: string, - directory?: string -) => { +const fetchImportedSecrets = async (projectId: string, environment: string, directory?: string) => { const { data } = await apiRequest.get<{ secrets: TImportedSecrets[] }>( - "/api/v1/secret-imports/secrets/raw", + "/api/v2/secret-imports/secrets", { params: { - workspaceId, + projectId, environment, path: directory } @@ -89,10 +85,10 @@ const fetchImportedFolders = async ({ path }: TGetImportedFoldersByEnvDTO) => { const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>( - "/api/v1/secret-imports", + "/api/v2/secret-imports", { params: { - workspaceId: projectId, + projectId, environment, path } diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index d950c2ca2..23690b33d 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -1,11 +1,11 @@ +import { ProjectEnv } from "../projects/types"; import { SecretV3Raw } from "../secrets/types"; -import { WorkspaceEnv } from "../workspace/types"; export type TSecretImport = { id: string; folderId: string; importPath: string; - importEnv: WorkspaceEnv; + importEnv: ProjectEnv; position: string; createdAt: string; updatedAt: string; @@ -25,7 +25,7 @@ export type TGetImportedFoldersByEnvDTO = { export type TImportedSecrets = { environment: string; - environmentInfo: WorkspaceEnv; + environmentInfo: ProjectEnv; secretPath: string; folderId: string; secrets: SecretV3Raw[]; diff --git a/frontend/src/hooks/api/secretRotation/types.ts b/frontend/src/hooks/api/secretRotation/types.ts index 19c4cdcdf..c5f939c5c 100644 --- a/frontend/src/hooks/api/secretRotation/types.ts +++ b/frontend/src/hooks/api/secretRotation/types.ts @@ -1,4 +1,4 @@ -import { WorkspaceEnv } from "../workspace/types"; +import { ProjectEnv } from "../projects/types"; export enum TProviderFunctionTypes { HTTP = "http", @@ -80,7 +80,7 @@ export type TSecretRotation = { customProvider: string; workspace: string; envId: string; - environment: WorkspaceEnv; + environment: ProjectEnv; secretPath: string; outputs: Array<{ key: string; diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx index daee39054..f5492918c 100644 --- a/frontend/src/hooks/api/secretSnapshots/queries.tsx +++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx @@ -14,25 +14,25 @@ import { } from "./types"; export const secretSnapshotKeys = { - list: ({ workspaceId, environment, directory }: Omit) => - [{ workspaceId, environment, directory }, "secret-snapshot"] as const, + list: ({ projectId, environment, directory }: Omit) => + [{ projectId, environment, directory }, "secret-snapshot"] as const, snapshotData: (snapshotId: string) => [{ snapshotId }, "secret-snapshot"] as const, - count: ({ environment, workspaceId, directory }: Omit) => [ - { workspaceId, environment, directory }, + count: ({ environment, projectId, directory }: Omit) => [ + { projectId, environment, directory }, "count", "secret-snapshot" ] }; const fetchWorkspaceSnaphots = async ({ - workspaceId, + projectId, environment, directory = "/", limit = 10, offset = 0 }: TGetSecretSnapshotsDTO & { offset: number }) => { const res = await apiRequest.get<{ secretSnapshots: TSecretSnapshot[] }>( - `/api/v1/workspace/${workspaceId}/secret-snapshots`, + `/api/v1/projects/${projectId}/secret-snapshots`, { params: { limit, @@ -49,7 +49,7 @@ const fetchWorkspaceSnaphots = async ({ export const useGetWorkspaceSnapshotList = (dto: TGetSecretSnapshotsDTO & { isPaused?: boolean }) => useInfiniteQuery({ initialPageParam: 0, - enabled: Boolean(dto.workspaceId && dto.environment) && !dto.isPaused, + enabled: Boolean(dto.projectId && dto.environment) && !dto.isPaused, queryKey: secretSnapshotKeys.list({ ...dto }), queryFn: ({ pageParam }) => fetchWorkspaceSnaphots({ ...dto, offset: pageParam }), getNextPageParam: (lastPage, pages) => @@ -115,12 +115,12 @@ export const useGetSnapshotSecrets = ({ snapshotId }: TSnapshotDataProps) => }); const fetchWorkspaceSecretSnaphotCount = async ( - workspaceId: string, + projectId: string, environment: string, directory = "/" ) => { const res = await apiRequest.get<{ count: number }>( - `/api/v1/workspace/${workspaceId}/secret-snapshots/count`, + `/api/v1/projects/${projectId}/secret-snapshots/count`, { params: { environment, @@ -132,15 +132,15 @@ const fetchWorkspaceSecretSnaphotCount = async ( }; export const useGetWsSnapshotCount = ({ - workspaceId, + projectId, environment, directory, isPaused }: Omit & { isPaused?: boolean }) => useQuery({ - enabled: Boolean(workspaceId && environment) && !isPaused, - queryKey: secretSnapshotKeys.count({ workspaceId, environment, directory }), - queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, environment, directory) + enabled: Boolean(projectId && environment) && !isPaused, + queryKey: secretSnapshotKeys.count({ projectId, environment, directory }), + queryFn: () => fetchWorkspaceSecretSnaphotCount(projectId, environment, directory) }); export const usePerformSecretRollback = () => { @@ -151,22 +151,22 @@ export const usePerformSecretRollback = () => { const { data } = await apiRequest.post(`/api/v1/secret-snapshot/${snapshotId}/rollback`); return data; }, - onSuccess: (_, { workspaceId, environment, directory }) => { + onSuccess: (_, { projectId, environment, directory }) => { queryClient.invalidateQueries({ - queryKey: [{ workspaceId, environment, secretPath: directory }, "secrets"] + queryKey: [{ projectId, environment, secretPath: directory }, "secrets"] }); queryClient.invalidateQueries({ - queryKey: ["secret-folders", { projectId: workspaceId, environment, path: directory }] + queryKey: ["secret-folders", { projectId, environment, path: directory }] }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ workspaceId, environment, directory }) + queryKey: secretSnapshotKeys.list({ projectId, environment, directory }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ workspaceId, environment, directory }) + queryKey: secretSnapshotKeys.count({ projectId, environment, directory }) }); queryClient.invalidateQueries({ queryKey: dashboardKeys.getDashboardSecrets({ - projectId: workspaceId, + projectId, secretPath: directory ?? "/" }) }); diff --git a/frontend/src/hooks/api/secretSnapshots/types.ts b/frontend/src/hooks/api/secretSnapshots/types.ts index e7ebb18de..9b1f95480 100644 --- a/frontend/src/hooks/api/secretSnapshots/types.ts +++ b/frontend/src/hooks/api/secretSnapshots/types.ts @@ -1,9 +1,9 @@ import { SecretVersions } from "../secrets/types"; -import { WorkspaceEnv } from "../types"; +import { ProjectEnv } from "../types"; export type TSecretSnapshot = { id: string; - workspace: string; + projectId: string; secretVersions: string[]; createdAt: string; updatedAt: string; @@ -13,7 +13,7 @@ export type TSnapshotData = Omit & { id: string; secretVersions: (SecretVersions & { isRotatedSecret?: boolean })[]; folderVersion: Array<{ name: string; id: string }>; - environment: WorkspaceEnv; + environment: ProjectEnv; }; export type TSnapshotDataProps = { @@ -22,7 +22,7 @@ export type TSnapshotDataProps = { }; export type TGetSecretSnapshotsDTO = { - workspaceId: string; + projectId: string; limit: number; environment: string; directory?: string; @@ -30,7 +30,7 @@ export type TGetSecretSnapshotsDTO = { export type TSecretRollbackDTO = { snapshotId: string; - workspaceId: string; + projectId: string; environment: string; directory?: string; }; diff --git a/frontend/src/hooks/api/secrets/mutations.tsx b/frontend/src/hooks/api/secrets/mutations.tsx index 11ebad4b1..de960cd04 100644 --- a/frontend/src/hooks/api/secrets/mutations.tsx +++ b/frontend/src/hooks/api/secrets/mutations.tsx @@ -33,18 +33,18 @@ export const useCreateSecretV3 = ({ secretPath = "/", type, environment, - workspaceId, + projectId, secretKey, secretValue, secretComment, skipMultilineEncoding, tagIds }) => { - const { data } = await apiRequest.post(`/api/v3/secrets/raw/${secretKey}`, { + const { data } = await apiRequest.post(`/api/v4/secrets/${secretKey}`, { secretPath, type, environment, - workspaceId, + projectId, secretValue, secretComment, skipMultilineEncoding, @@ -52,26 +52,26 @@ export const useCreateSecretV3 = ({ }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -88,7 +88,7 @@ export const useUpdateSecretV3 = ({ secretPath = "/", type, environment, - workspaceId, + projectId, secretKey, secretValue, tagIds, @@ -100,8 +100,8 @@ export const useUpdateSecretV3 = ({ skipMultilineEncoding, secretMetadata }) => { - const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, { - workspaceId, + const { data } = await apiRequest.patch(`/api/v4/secrets/${secretKey}`, { + projectId, environment, type, secretReminderNote, @@ -117,26 +117,26 @@ export const useUpdateSecretV3 = ({ }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -150,17 +150,10 @@ export const useDeleteSecretV3 = ({ const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ - secretPath = "/", - type, - environment, - workspaceId, - secretKey, - secretId - }) => { - const { data } = await apiRequest.delete(`/api/v3/secrets/raw/${secretKey}`, { + mutationFn: async ({ secretPath = "/", type, environment, projectId, secretKey, secretId }) => { + const { data } = await apiRequest.delete(`/api/v4/secrets/${secretKey}`, { data: { - workspaceId, + projectId, environment, type, secretPath, @@ -169,26 +162,26 @@ export const useDeleteSecretV3 = ({ }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -202,35 +195,35 @@ export const useCreateSecretBatch = ({ const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { - const { data } = await apiRequest.post("/api/v3/secrets/batch/raw", { - workspaceId, + mutationFn: async ({ secretPath = "/", projectId, environment, secrets }) => { + const { data } = await apiRequest.post("/api/v4/secrets/batch", { + projectId, environment, secretPath, secrets }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -244,35 +237,35 @@ export const useUpdateSecretBatch = ({ const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { - const { data } = await apiRequest.patch("/api/v3/secrets/batch/raw", { - workspaceId, + mutationFn: async ({ secretPath = "/", projectId, environment, secrets }) => { + const { data } = await apiRequest.patch("/api/v4/secrets/batch", { + projectId, environment, secretPath, secrets }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -286,10 +279,10 @@ export const useDeleteSecretBatch = ({ const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { - const { data } = await apiRequest.delete("/api/v3/secrets/batch/raw", { + mutationFn: async ({ secretPath = "/", projectId, environment, secrets }) => { + const { data } = await apiRequest.delete("/api/v4/secrets/batch", { data: { - workspaceId, + projectId, environment, secretPath, secrets @@ -297,26 +290,26 @@ export const useDeleteSecretBatch = ({ }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options }); @@ -340,23 +333,23 @@ export const useMoveSecrets = ({ mutationFn: async ({ sourceEnvironment, sourceSecretPath, - projectSlug, destinationEnvironment, destinationSecretPath, secretIds, - shouldOverwrite + shouldOverwrite, + projectId }) => { const { data } = await apiRequest.post<{ isSourceUpdated: boolean; isDestinationUpdated: boolean; - }>("/api/v3/secrets/move", { + }>("/api/v4/secrets/move", { sourceEnvironment, sourceSecretPath, - projectSlug, destinationEnvironment, destinationSecretPath, secretIds, - shouldOverwrite + shouldOverwrite, + projectId }); return data; @@ -370,7 +363,7 @@ export const useMoveSecrets = ({ }); queryClient.invalidateQueries({ queryKey: secretKeys.getProjectSecret({ - workspaceId: projectId, + projectId, environment: sourceEnvironment, secretPath: sourceSecretPath }) @@ -378,33 +371,33 @@ export const useMoveSecrets = ({ queryClient.invalidateQueries({ queryKey: secretSnapshotKeys.list({ environment: sourceEnvironment, - workspaceId: projectId, + projectId, directory: sourceSecretPath }) }); queryClient.invalidateQueries({ queryKey: secretSnapshotKeys.count({ environment: sourceEnvironment, - workspaceId: projectId, + projectId, directory: sourceSecretPath }) }); queryClient.invalidateQueries({ queryKey: commitKeys.count({ - workspaceId: projectId, + projectId, environment: sourceEnvironment, directory: sourceSecretPath }) }); queryClient.invalidateQueries({ queryKey: commitKeys.history({ - workspaceId: projectId, + projectId, environment: sourceEnvironment, directory: sourceSecretPath }) }); queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ workspaceId: projectId }) + queryKey: secretApprovalRequestKeys.count({ projectId }) }); }, ...options @@ -412,14 +405,14 @@ export const useMoveSecrets = ({ }; export const createSecret = async (dto: TCreateSecretsV3DTO) => { - const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto); + const { data } = await apiRequest.post(`/api/v4/secrets/${dto.secretKey}`, dto); return data; }; export const useBackfillSecretReference = () => useMutation<{ message: string }, object, { projectId: string }>({ mutationFn: async ({ projectId }) => { - const { data } = await apiRequest.post("/api/v3/secrets/backfill-secret-references", { + const { data } = await apiRequest.post("/api/v4/secrets/backfill-secret-references", { projectId }); return data.message; @@ -432,16 +425,16 @@ export const useCreateCommit = () => { object, object, { - workspaceId: string; + projectId: string; environment: string; secretPath: string; pendingChanges: PendingChanges; message: string; } >({ - mutationFn: async ({ workspaceId, environment, secretPath, pendingChanges, message }) => { + mutationFn: async ({ projectId, environment, secretPath, pendingChanges, message }) => { const { data } = await apiRequest.post("/api/v1/pit/batch/commit", { - projectId: workspaceId, + projectId, environment, secretPath, changes: { @@ -501,26 +494,26 @@ export const useCreateCommit = () => { }); return data; }, - onSuccess: (_, { workspaceId, environment, secretPath }) => { + onSuccess: (_, { projectId, environment, secretPath }) => { queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.list({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + queryKey: secretSnapshotKeys.count({ environment, projectId, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.count({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) }); queryClient.invalidateQueries({ - queryKey: commitKeys.history({ workspaceId, environment, directory: secretPath }) + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) }); - queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ workspaceId }) }); + queryClient.invalidateQueries({ queryKey: secretApprovalRequestKeys.count({ projectId }) }); } }); }; diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx index 443b74df8..df3a78c35 100644 --- a/frontend/src/hooks/api/secrets/queries.tsx +++ b/frontend/src/hooks/api/secrets/queries.tsx @@ -27,35 +27,35 @@ import { export const secretKeys = { // this is also used in secretSnapshot part getProjectSecret: ({ - workspaceId, + projectId, environment, secretPath, viewSecretValue }: TGetProjectSecretsKey) => - [{ workspaceId, environment, secretPath, viewSecretValue }, "secrets"] as const, + [{ projectId, environment, secretPath, viewSecretValue }, "secrets"] as const, getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const, getSecretAccessList: ({ - workspaceId, + projectId, environment, secretPath, secretKey }: TGetSecretAccessListDTO) => - ["secret-access-list", { workspaceId, environment, secretPath, secretKey }] as const, + ["secret-access-list", { projectId, environment, secretPath, secretKey }] as const, getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto] }; export const fetchProjectSecrets = async ({ - workspaceId, + projectId, environment, secretPath, includeImports, expandSecretReferences, viewSecretValue }: TGetProjectSecretsKey) => { - const { data } = await apiRequest.get("/api/v3/secrets/raw", { + const { data } = await apiRequest.get("/api/v4/secrets", { params: { environment, - workspaceId, + projectId, secretPath, viewSecretValue, expandSecretReferences, @@ -116,7 +116,7 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => { }; export const useGetProjectSecrets = ({ - workspaceId, + projectId, environment, secretPath, viewSecretValue, @@ -135,14 +135,14 @@ export const useGetProjectSecrets = ({ useQuery({ ...options, // wait for all values to be available - enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true), + enabled: Boolean(projectId && environment) && (options?.enabled ?? true), queryKey: secretKeys.getProjectSecret({ - workspaceId, + projectId, environment, secretPath, viewSecretValue }), - queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath, viewSecretValue }), + queryFn: () => fetchProjectSecrets({ projectId, environment, secretPath, viewSecretValue }), select: useCallback( (data: Awaited>) => mergePersonalSecrets(data.secrets), [] @@ -150,7 +150,7 @@ export const useGetProjectSecrets = ({ }); export const useGetProjectSecretsAllEnv = ({ - workspaceId, + projectId, envs, secretPath }: TGetProjectSecretsAllEnvDTO) => { @@ -159,11 +159,11 @@ export const useGetProjectSecretsAllEnv = ({ const secrets = useQueries({ queries: envs.map((environment) => ({ queryKey: secretKeys.getProjectSecret({ - workspaceId, + projectId, environment, secretPath }), - enabled: Boolean(workspaceId && environment), + enabled: Boolean(projectId && environment), onError: (error: unknown) => { if (axios.isAxiosError(error) && !isErrorHandled) { const { message, requestId } = error.response?.data as { @@ -187,7 +187,7 @@ export const useGetProjectSecretsAllEnv = ({ setIsErrorHandled.on(); } }, - queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath }), + queryFn: () => fetchProjectSecrets({ projectId, environment, secretPath }), staleTime: 60 * 1000, // eslint-disable-next-line react-hooks/rules-of-hooks select: useCallback( @@ -270,7 +270,7 @@ export const useGetSecretAccessList = (dto: TGetSecretAccessListDTO) => users: SecretAccessListEntry[]; }>(`/api/v1/secrets/${dto.secretKey}/access-list`, { params: { - workspaceId: dto.workspaceId, + projectId: dto.projectId, environment: dto.environment, secretPath: dto.secretPath } @@ -287,11 +287,11 @@ const fetchSecretReferenceTree = async ({ environmentSlug }: TGetSecretReferenceTreeDTO) => { const { data } = await apiRequest.get<{ tree: TSecretReferenceTraceNode; value: string }>( - `/api/v3/secrets/raw/${secretKey}/secret-reference-tree`, + `/api/v4/secrets/${secretKey}/secret-reference-tree`, { params: { secretPath, - workspaceId: projectId, + projectId, environment: environmentSlug } } diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index 1b232cfac..327d3659d 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -17,30 +17,6 @@ export type SecretReminderRecipient = { }; id: string; }; -export type EncryptedSecret = { - id: string; - version: number; - workspace: string; - type: SecretType; - environment: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - secretValueHidden: boolean; - __v: number; - createdAt: string; - updatedAt: string; - skipMultilineEncoding?: boolean; - secretCommentCiphertext: string; - secretCommentIV: string; - secretCommentTag: string; - secretReminderRepeatDays?: number | null; - secretReminderNote?: string | null; - tags: WsTag[]; -}; // both personal and shared secret stitched together for dashboard export type SecretV3RawSanitized = { @@ -76,7 +52,7 @@ export type SecretV3RawSanitized = { export type SecretV3Raw = { id: string; _id: string; - workspace: string; + project: string; environment: string; version: number; type: string; @@ -113,7 +89,7 @@ export type SecretVersions = { id: string; secretId: string; version: number; - workspace: string; + project: string; type: SecretType; isDeleted: boolean; envId: string; @@ -136,7 +112,7 @@ export type SecretVersions = { // dto export type TGetProjectSecretsKey = { - workspaceId: string; + projectId: string; environment: string; secretPath?: string; includeImports?: boolean; @@ -148,7 +124,7 @@ export type TGetProjectSecretsKey = { export type TGetProjectSecretsDTO = TGetProjectSecretsKey; export type TGetProjectSecretsAllEnvDTO = { - workspaceId: string; + projectId: string; envs: string[]; folderId?: string; secretPath?: string; @@ -162,7 +138,7 @@ export type GetSecretVersionsDTO = { }; export type TGetSecretAccessListDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; secretKey: string; @@ -174,14 +150,14 @@ export type TCreateSecretsV3DTO = { secretComment: string; skipMultilineEncoding?: boolean; secretPath: string; - workspaceId: string; + projectId: string; environment: string; type: SecretType; tagIds?: string[]; }; export type TUpdateSecretsV3DTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; type: SecretType; @@ -198,7 +174,7 @@ export type TUpdateSecretsV3DTO = { }; export type TDeleteSecretsV3DTO = { - workspaceId: string; + projectId: string; environment: string; type: SecretType; secretPath: string; @@ -207,7 +183,7 @@ export type TDeleteSecretsV3DTO = { }; export type TCreateSecretBatchDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; secrets: Array<{ @@ -224,7 +200,7 @@ export type TCreateSecretBatchDTO = { }; export type TUpdateSecretBatchDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; secrets: Array<{ @@ -241,7 +217,7 @@ export type TUpdateSecretBatchDTO = { }; export type TDeleteSecretBatchDTO = { - workspaceId: string; + projectId: string; environment: string; secretPath: string; secrets: Array<{ @@ -251,7 +227,6 @@ export type TDeleteSecretBatchDTO = { }; export type TMoveSecretsDTO = { - projectSlug: string; projectId: string; sourceEnvironment: string; sourceSecretPath: string; diff --git a/frontend/src/hooks/api/serviceTokens/queries.tsx b/frontend/src/hooks/api/serviceTokens/queries.tsx index 06b9b09c6..41421b279 100644 --- a/frontend/src/hooks/api/serviceTokens/queries.tsx +++ b/frontend/src/hooks/api/serviceTokens/queries.tsx @@ -13,9 +13,9 @@ const serviceTokenKeys = { getAllWorkspaceServiceToken: (workspaceID: string) => [{ workspaceID }, "service-tokens"] as const }; -const fetchWorkspaceServiceTokens = async (workspaceID: string) => { +const fetchWorkspaceServiceTokens = async (projectID: string) => { const { data } = await apiRequest.get<{ serviceTokenData: ServiceToken[] }>( - `/api/v1/workspace/${workspaceID}/service-token-data` + `/api/v1/projects/${projectID}/service-token-data` ); return data.serviceTokenData; diff --git a/frontend/src/hooks/api/sshCa/mutations.tsx b/frontend/src/hooks/api/sshCa/mutations.tsx index c7e90c3b9..fc6c43ef4 100644 --- a/frontend/src/hooks/api/sshCa/mutations.tsx +++ b/frontend/src/hooks/api/sshCa/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace/query-keys"; +import { projectKeys } from "../projects/query-keys"; import { TCreateSshCaDTO, TDeleteSshCaDTO, @@ -28,7 +28,7 @@ export const useCreateSshCa = () => { return ca; }, onSuccess: ({ projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshCas(projectId) }); } }); }; @@ -43,7 +43,7 @@ export const useUpdateSshCa = () => { return ca; }, onSuccess: ({ projectId }, { caId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshCas(projectId) }); queryClient.invalidateQueries({ queryKey: sshCaKeys.getSshCaById(caId) }); } }); @@ -59,7 +59,7 @@ export const useDeleteSshCa = () => { return ca; }, onSuccess: ({ projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshCas(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshCas(projectId) }); } }); }; @@ -76,7 +76,7 @@ export const useSignSshKey = () => { }, onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.allWorkspaceSshCertificates(projectId) + queryKey: projectKeys.allProjectSshCertificates(projectId) }); } }); @@ -94,7 +94,7 @@ export const useIssueSshCreds = () => { }, onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.allWorkspaceSshCertificates(projectId) + queryKey: projectKeys.allProjectSshCertificates(projectId) }); } }); diff --git a/frontend/src/hooks/api/sshHost/mutations.tsx b/frontend/src/hooks/api/sshHost/mutations.tsx index f6b831f3e..0c1b67103 100644 --- a/frontend/src/hooks/api/sshHost/mutations.tsx +++ b/frontend/src/hooks/api/sshHost/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace/query-keys"; +import { projectKeys } from "../projects/query-keys"; import { TCreateSshHostDTO, TDeleteSshHostDTO, TSshHost, TUpdateSshHostDTO } from "./types"; export const useCreateSshHost = () => { @@ -13,7 +13,7 @@ export const useCreateSshHost = () => { return host; }, onSuccess: ({ projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshHosts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshHosts(projectId) }); } }); }; @@ -26,7 +26,7 @@ export const useUpdateSshHost = () => { return host; }, onSuccess: ({ projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshHosts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshHosts(projectId) }); } }); }; @@ -39,7 +39,7 @@ export const useDeleteSshHost = () => { return host; }, onSuccess: ({ projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceSshHosts(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectSshHosts(projectId) }); } }); }; diff --git a/frontend/src/hooks/api/sshHostGroup/mutations.tsx b/frontend/src/hooks/api/sshHostGroup/mutations.tsx index b75cff187..fa9eebb07 100644 --- a/frontend/src/hooks/api/sshHostGroup/mutations.tsx +++ b/frontend/src/hooks/api/sshHostGroup/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace/query-keys"; +import { projectKeys } from "../projects/query-keys"; import { sshHostGroupKeys } from "./queries"; import { TCreateSshHostGroupDTO, @@ -20,7 +20,7 @@ export const useCreateSshHostGroup = () => { }, onSuccess: ({ projectId, id }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId) + queryKey: projectKeys.getProjectSshHostGroups(projectId) }); queryClient.invalidateQueries({ queryKey: sshHostGroupKeys.getSshHostGroupById(id) @@ -41,10 +41,10 @@ export const useUpdateSshHostGroup = () => { }, onSuccess: ({ projectId }, { sshHostGroupId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId) + queryKey: projectKeys.getProjectSshHostGroups(projectId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceSshHosts(projectId) + queryKey: projectKeys.getProjectSshHosts(projectId) }); queryClient.invalidateQueries({ queryKey: sshHostGroupKeys.getSshHostGroupById(sshHostGroupId) @@ -64,10 +64,10 @@ export const useDeleteSshHostGroup = () => { }, onSuccess: ({ projectId }, { sshHostGroupId }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId) + queryKey: projectKeys.getProjectSshHostGroups(projectId) }); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceSshHosts(projectId) + queryKey: projectKeys.getProjectSshHosts(projectId) }); queryClient.invalidateQueries({ queryKey: sshHostGroupKeys.getSshHostGroupById(sshHostGroupId) diff --git a/frontend/src/hooks/api/tags/queries.tsx b/frontend/src/hooks/api/tags/queries.tsx index bdbb9659d..0234cec4d 100644 --- a/frontend/src/hooks/api/tags/queries.tsx +++ b/frontend/src/hooks/api/tags/queries.tsx @@ -4,23 +4,21 @@ 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<{ tags: UserWsTags }>(`/api/v1/projects/${projectID}/tags`); - return data.workspaceTags; + return data.tags; }; -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 +26,15 @@ 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`, - { - color: tagColor || "", - slug: tagSlug - } - ); - return data.workspaceTag; + mutationFn: async ({ projectId: projectID, tagColor, tagSlug }) => { + const { data } = await apiRequest.post<{ tag: WsTag }>(`/api/v1/projects/${projectID}/tags`, { + color: tagColor || "", + slug: tagSlug + }); + return data.tag; }, onSuccess: (tagData) => { - queryClient.invalidateQueries({ queryKey: workspaceTags.getWsTags(tagData?.projectId) }); + queryClient.invalidateQueries({ queryKey: projectTags.getWsTags(tagData?.projectId) }); } }); }; @@ -49,13 +44,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<{ tag: WsTag }>( + `/api/v1/projects/${projectId}/tags/${tagID}` ); - return data.workspaceTag; + return data.tag; }, 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..09ac22147 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/trustedIps/queries.ts b/frontend/src/hooks/api/trustedIps/queries.ts index 57a5d5844..36815b313 100644 --- a/frontend/src/hooks/api/trustedIps/queries.ts +++ b/frontend/src/hooks/api/trustedIps/queries.ts @@ -5,15 +5,15 @@ import { apiRequest } from "@app/config/request"; import { TrustedIp } from "./types"; const trustedIps = { - getTrustedIps: (workspaceId: string) => [{ workspaceId }, "trusted-ips"] as const + getTrustedIps: (projectId: string) => [{ projectId }, "trusted-ips"] as const }; -export const useGetTrustedIps = (workspaceId: string) => { +export const useGetTrustedIps = (projectId: string) => { return useQuery({ - queryKey: trustedIps.getTrustedIps(workspaceId), + queryKey: trustedIps.getTrustedIps(projectId), queryFn: async () => { const { data } = await apiRequest.get<{ trustedIps: TrustedIp[] }>( - `/api/v1/workspace/${workspaceId}/trusted-ips` + `/api/v1/projects/${projectId}/trusted-ips` ); return data.trustedIps; @@ -25,17 +25,17 @@ export const useAddTrustedIp = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ - workspaceId, + projectId, ipAddress, comment, isActive }: { - workspaceId: string; + projectId: string; ipAddress: string; comment?: string; isActive: boolean; }) => { - const { data } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/trusted-ips`, { + const { data } = await apiRequest.post(`/api/v1/projects/${projectId}/trusted-ips`, { ipAddress, ...(comment ? { comment } : {}), isActive @@ -44,7 +44,7 @@ export const useAddTrustedIp = () => { return data; }, onSuccess(_, dto) { - queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.workspaceId) }); + queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.projectId) }); } }); }; @@ -53,20 +53,20 @@ export const useUpdateTrustedIp = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ - workspaceId, + projectId, trustedIpId, ipAddress, comment, isActive }: { - workspaceId: string; + projectId: string; trustedIpId: string; ipAddress: string; comment?: string; isActive: boolean; }) => { const { data } = await apiRequest.patch( - `/api/v1/workspace/${workspaceId}/trusted-ips/${trustedIpId}`, + `/api/v1/projects/${projectId}/trusted-ips/${trustedIpId}`, { ipAddress, ...(comment ? { comment } : {}), @@ -77,7 +77,7 @@ export const useUpdateTrustedIp = () => { return data; }, onSuccess(_, dto) { - queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.workspaceId) }); + queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.projectId) }); } }); }; @@ -85,21 +85,15 @@ export const useUpdateTrustedIp = () => { export const useDeleteTrustedIp = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ - workspaceId, - trustedIpId - }: { - workspaceId: string; - trustedIpId: string; - }) => { + mutationFn: async ({ projectId, trustedIpId }: { projectId: string; trustedIpId: string }) => { const { data } = await apiRequest.delete( - `/api/v1/workspace/${workspaceId}/trusted-ips/${trustedIpId}` + `/api/v1/projects/${projectId}/trusted-ips/${trustedIpId}` ); return data; }, onSuccess(_, dto) { - queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.workspaceId) }); + queryClient.invalidateQueries({ queryKey: trustedIps.getTrustedIps(dto.projectId) }); } }); }; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index 8c29d4825..4b73eba28 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -7,8 +7,19 @@ export type { GetAuthTokenAPI } from "./auth/types"; export type { IncidentContact } from "./incidentContacts/types"; export type { IntegrationAuth } from "./integrationAuth/types"; export type { TCloudIntegration, TIntegration } from "./integrations/types"; -export type { UserWsKeyPair } from "./keys/types"; export type { Organization } from "./organization/types"; +export type { + CreateEnvironmentDTO, + CreateWorkspaceDTO, + DeleteEnvironmentDTO, + DeleteWorkspaceDTO, + Project, + ProjectEnv, + ProjectTag, + ToggleAutoCapitalizationDTO, + UpdateEnvironmentDTO, + UpdateProjectDTO +} from "./projects/types"; export type { TSecretApprovalPolicy } from "./secretApproval/types"; export type { TGetSecretApprovalRequestDetails, @@ -29,18 +40,6 @@ export type { SubscriptionPlan } from "./subscriptions/types"; export type { WsTag } from "./tags/types"; export type { OrgUser, TWorkspaceUser, User, UserEnc } from "./users/types"; export type { TWebhook } from "./webhooks/types"; -export type { - CreateEnvironmentDTO, - CreateWorkspaceDTO, - DeleteEnvironmentDTO, - DeleteWorkspaceDTO, - ToggleAutoCapitalizationDTO, - UpdateEnvironmentDTO, - UpdateProjectDTO, - Workspace, - WorkspaceEnv, - WorkspaceTag -} from "./workspace/types"; export enum ApiErrorTypes { ValidationError = "ValidationFailure", diff --git a/frontend/src/hooks/api/users/mutation.tsx b/frontend/src/hooks/api/users/mutation.tsx index 1f3fdf3ad..7acfb8fe0 100644 --- a/frontend/src/hooks/api/users/mutation.tsx +++ b/frontend/src/hooks/api/users/mutation.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace"; +import { projectKeys } from "../projects"; import { userKeys } from "./query-keys"; import { AddUserToWsDTONonE2EE } from "./types"; @@ -11,14 +11,14 @@ export const useAddUserToWsNonE2EE = () => { return useMutation({ mutationFn: async ({ projectId, usernames, roleSlugs }) => { - const { data } = await apiRequest.post(`/api/v2/workspace/${projectId}/memberships`, { + const { data } = await apiRequest.post(`/api/v1/projects/${projectId}/memberships`, { usernames, roleSlugs }); return data; }, onSuccess: (_, { orgId, projectId }) => { - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceUsers(projectId) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectUsers(projectId) }); queryClient.invalidateQueries({ queryKey: userKeys.allOrgMembershipProjectMemberships(orgId) }); diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 77289fee6..2c0125361 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -8,9 +8,9 @@ import { queryClient as qc } from "@app/hooks/api/reactQuery"; import { APIKeyDataV2 } from "../apiKeys/types"; import { MfaMethod } from "../auth/types"; import { TGroupWithProjectMemberships } from "../groups/types"; +import { projectKeys } from "../projects"; import { setAuthToken } from "../reactQuery"; import { subscriptionQueryKeys } from "../subscriptions/queries"; -import { workspaceKeys } from "../workspace"; import { userKeys } from "./query-keys"; import { AddUserToOrgDTO, @@ -197,10 +197,10 @@ export const useAddUsersToOrg = () => { projects?.forEach((project) => { if (project.slug) { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceGroupMemberships(project.slug) + queryKey: projectKeys.getProjectGroupMemberships(project.slug) }); } - queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceUsers(project.id) }); + queryClient.invalidateQueries({ queryKey: projectKeys.getProjectUsers(project.id) }); }); } }); diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 5a90d4b64..a59965dfc 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -1,5 +1,5 @@ import { MfaMethod } from "../auth/types"; -import { ProjectType, ProjectUserMembershipTemporaryMode } from "../workspace/types"; +import { ProjectType, ProjectUserMembershipTemporaryMode } from "../projects/types"; export enum AuthMethod { EMAIL = "email", diff --git a/frontend/src/hooks/api/webhooks/mutation.tsx b/frontend/src/hooks/api/webhooks/mutation.tsx index 10d5e472e..66eb8ae17 100644 --- a/frontend/src/hooks/api/webhooks/mutation.tsx +++ b/frontend/src/hooks/api/webhooks/mutation.tsx @@ -13,8 +13,8 @@ export const useCreateWebhook = () => { const { data } = await apiRequest.post("/api/v1/webhooks", dto); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -27,11 +27,11 @@ export const useTestWebhook = () => { const { data } = await apiRequest.post(`/api/v1/webhooks/${webhookId}/test`); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); }, - onError: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onError: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -46,8 +46,8 @@ export const useUpdateWebhook = () => { }); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; @@ -60,8 +60,8 @@ export const useDeleteWebhook = () => { const { data } = await apiRequest.delete(`/api/v1/webhooks/${dto.webhookId}`); return data; }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(workspaceId) }); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.getWebhooks(projectId) }); } }); }; diff --git a/frontend/src/hooks/api/webhooks/query.tsx b/frontend/src/hooks/api/webhooks/query.tsx index fc1840409..3f92a9695 100644 --- a/frontend/src/hooks/api/webhooks/query.tsx +++ b/frontend/src/hooks/api/webhooks/query.tsx @@ -8,19 +8,19 @@ export const queryKeys = { getWebhooks: (workspaceId: string) => ["webhooks", { workspaceId }] }; -const fetchWebhooks = async (workspaceId: string) => { +const fetchWebhooks = async (projectId: string) => { const { data } = await apiRequest.get<{ webhooks: TWebhook[] }>("/api/v1/webhooks", { params: { - workspaceId + projectId } }); return data.webhooks; }; -export const useGetWebhooks = (workspaceId: string) => +export const useGetWebhooks = (projectId: string) => useQuery({ - queryKey: queryKeys.getWebhooks(workspaceId), - queryFn: () => fetchWebhooks(workspaceId), - enabled: Boolean(workspaceId) + queryKey: queryKeys.getWebhooks(projectId), + queryFn: () => fetchWebhooks(projectId), + enabled: Boolean(projectId) }); diff --git a/frontend/src/hooks/api/webhooks/types.ts b/frontend/src/hooks/api/webhooks/types.ts index 86183bf1b..7e2758ff8 100644 --- a/frontend/src/hooks/api/webhooks/types.ts +++ b/frontend/src/hooks/api/webhooks/types.ts @@ -23,7 +23,7 @@ export type TWebhook = { }; export type TCreateWebhookDto = { - workspaceId: string; + projectId: string; environment: string; webhookUrl: string; webhookSecretKey?: string; @@ -33,16 +33,16 @@ export type TCreateWebhookDto = { export type TUpdateWebhookDto = { webhookId: string; - workspaceId: string; + projectId: string; isDisabled?: boolean; }; export type TDeleteWebhookDto = { webhookId: string; - workspaceId: string; + projectId: string; }; export type TTestWebhookDTO = { webhookId: string; - workspaceId: string; + projectId: string; }; diff --git a/frontend/src/hooks/api/workflowIntegrations/mutation.tsx b/frontend/src/hooks/api/workflowIntegrations/mutation.tsx index 89c65f888..d362c7b82 100644 --- a/frontend/src/hooks/api/workflowIntegrations/mutation.tsx +++ b/frontend/src/hooks/api/workflowIntegrations/mutation.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { workspaceKeys } from "../workspace/query-keys"; +import { projectKeys } from "../projects/query-keys"; import { workflowIntegrationKeys } from "./queries"; import { TCheckMicrosoftTeamsIntegrationInstallationStatusDTO, @@ -118,15 +118,15 @@ 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: workspaceKeys.getWorkspaceWorkflowIntegrationConfig(workspaceId, integration) + queryKey: projectKeys.getProjectWorkflowIntegrationConfig(workspaceId, integration) }); } }); @@ -138,14 +138,14 @@ 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; }, onSuccess: (_, { projectId, integration }) => { queryClient.invalidateQueries({ - queryKey: workspaceKeys.getWorkspaceWorkflowIntegrationConfig(projectId, integration) + queryKey: projectKeys.getProjectWorkflowIntegrationConfig(projectId, integration) }); } }); 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/query-keys.tsx b/frontend/src/hooks/api/workspace/query-keys.tsx deleted file mode 100644 index aca6f48b7..000000000 --- a/frontend/src/hooks/api/workspace/query-keys.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { TListProjectIdentitiesDTO, TSearchProjectsDTO } from "@app/hooks/api/workspace/types"; - -import type { CaStatus } from "../ca"; -import { WorkflowIntegrationPlatform } from "../workflowIntegrations/types"; - -export const workspaceKeys = { - getWorkspaceById: (workspaceId: string) => ["workspaces", { workspaceId }] as const, - getWorkspaceSecrets: (workspaceId: string) => [{ workspaceId }, "workspace-secrets"] as const, - getWorkspaceIndexStatus: (workspaceId: string) => - [{ workspaceId }, "workspace-index-status"] as const, - getProjectUpgradeStatus: (workspaceId: string) => [{ workspaceId }, "workspace-upgrade-status"], - getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"], - getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"], - getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"], - getAllUserWorkspace: () => ["workspaces"] as const, - getWorkspaceAuditLogs: (workspaceId: string) => - [{ workspaceId }, "workspace-audit-logs"] as const, - getWorkspaceUsers: ( - workspaceId: string, - includeGroupMembers: boolean = false, - roles: string[] = [] - ) => [{ workspaceId, includeGroupMembers, roles }, "workspace-users"] as const, - getWorkspaceUserDetails: (workspaceId: string, membershipId: string) => - [{ workspaceId, membershipId }, "workspace-user-details"] as const, - getWorkspaceIdentityMemberships: (workspaceId: string) => - [{ workspaceId }, "workspace-identity-memberships"] as const, - getWorkspaceIdentityMembershipDetails: (workspaceId: string, identityId: string) => - [{ workspaceId, identityId }, "workspace-identity-membership-details"] as const, - // allows invalidation using above key without knowing params - getWorkspaceIdentityMembershipsWithParams: ({ - workspaceId, - ...params - }: TListProjectIdentitiesDTO) => - [...workspaceKeys.getWorkspaceIdentityMemberships(workspaceId), params] as const, - searchWorkspace: (dto: TSearchProjectsDTO) => ["search-projects", dto] as const, - getWorkspaceGroupMemberships: (workspaceId: string) => - [{ workspaceId }, "workspace-groups"] as const, - getWorkspaceGroupMembershipDetails: (workspaceId: string, groupId: string) => - [{ workspaceId, groupId }, "workspace-group-membership-details"] as const, - getWorkspaceCas: ({ projectSlug }: { projectSlug: string }) => - [{ projectSlug }, "workspace-cas"] as const, - specificWorkspaceCas: ({ projectSlug, status }: { projectSlug: string; status?: CaStatus }) => - [...workspaceKeys.getWorkspaceCas({ projectSlug }), { status }] as const, - allWorkspaceCertificates: () => ["workspace-certificates"] as const, - forWorkspaceCertificates: (slug: string) => - [...workspaceKeys.allWorkspaceCertificates(), slug] as const, - specificWorkspaceCertificates: ({ - slug, - offset, - limit - }: { - slug: string; - offset: number; - limit: number; - }) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const, - getWorkspacePkiAlerts: (workspaceId: string) => - [{ workspaceId }, "workspace-pki-alerts"] as const, - getWorkspacePkiSubscribers: (projectId: string) => - [{ projectId }, "workspace-pki-subscribers"] as const, - getWorkspacePkiCollections: (workspaceId: string) => - [{ workspaceId }, "workspace-pki-collections"] as const, - getWorkspaceCertificateTemplates: (workspaceId: string) => - [{ workspaceId }, "workspace-certificate-templates"] as const, - getWorkspaceWorkflowIntegrationConfig: ( - workspaceId: string, - integration: WorkflowIntegrationPlatform - ) => [{ workspaceId, integration }, "workspace-workflow-integration-config"] as const, - getWorkspaceSshCas: (projectId: string) => [{ projectId }, "workspace-ssh-cas"] as const, - allWorkspaceSshCertificates: (projectId: string) => - [{ projectId }, "workspace-ssh-certificates"] as const, - getWorkspaceSshHosts: (projectId: string) => [{ projectId }, "workspace-ssh-hosts"] as const, - getWorkspaceSshHostGroups: (projectId: string) => - [{ projectId }, "workspace-ssh-host-groups"] as const, - specificWorkspaceSshCertificates: ({ - offset, - limit, - projectId - }: { - offset: number; - limit: number; - projectId: string; - }) => [...workspaceKeys.allWorkspaceSshCertificates(projectId), { offset, limit }] as const, - getWorkspaceSshCertificateTemplates: (projectId: string) => - [{ projectId }, "workspace-ssh-certificate-templates"] as const, - getProjectSshConfig: (projectId: string) => [{ projectId }, "project-ssh-config"] as const -}; diff --git a/frontend/src/hooks/useGetProjectTypeFromRoute.tsx b/frontend/src/hooks/useGetProjectTypeFromRoute.tsx index 3156216db..972671a96 100644 --- a/frontend/src/hooks/useGetProjectTypeFromRoute.tsx +++ b/frontend/src/hooks/useGetProjectTypeFromRoute.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { useRouterState } from "@tanstack/react-router"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; export const useGetProjectTypeFromRoute = () => { const { location } = useRouterState(); diff --git a/frontend/src/hooks/usePathAccessPolicies.tsx b/frontend/src/hooks/usePathAccessPolicies.tsx index 1fbc5fd52..0ad25b0d2 100644 --- a/frontend/src/hooks/usePathAccessPolicies.tsx +++ b/frontend/src/hooks/usePathAccessPolicies.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { useSubscription, useWorkspace } from "@app/context"; +import { useProject, useSubscription } from "@app/context"; import { useGetAccessApprovalPolicies } from "@app/hooks/api"; const matchesPath = (folderPath: string, pattern: string) => { @@ -37,10 +37,10 @@ type Params = { }; export const usePathAccessPolicies = ({ secretPath, environment }: Params) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { subscription } = useSubscription(); const { data: policies } = useGetAccessApprovalPolicies({ - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, options: { enabled: subscription.secretApproval } diff --git a/frontend/src/layouts/KmsLayout/KmsLayout.tsx b/frontend/src/layouts/KmsLayout/KmsLayout.tsx index c34bc3626..285f3b7f8 100644 --- a/frontend/src/layouts/KmsLayout/KmsLayout.tsx +++ b/frontend/src/layouts/KmsLayout/KmsLayout.tsx @@ -4,12 +4,12 @@ import { Link, Outlet } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; export const KmsLayout = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { assumedPrivilegeDetails } = useProjectPermission(); return ( @@ -34,7 +34,7 @@ export const KmsLayout = () => { {({ isActive }) => ( @@ -51,7 +51,7 @@ export const KmsLayout = () => { {({ isActive }) => ( @@ -70,7 +70,7 @@ export const KmsLayout = () => { {({ isActive }) => ( @@ -87,7 +87,7 @@ export const KmsLayout = () => { {({ isActive }) => ( @@ -104,7 +104,7 @@ export const KmsLayout = () => { {({ isActive }) => ( diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx index e9bb6a902..71be38b11 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx @@ -40,7 +40,7 @@ import { envConfig } from "@app/config/env"; import { useOrganization, useSubscription, useUser } from "@app/context"; import { isInfisicalCloud } from "@app/helpers/platform"; import { useToggle } from "@app/hooks"; -import { useGetOrganizations, useLogoutUser, workspaceKeys } from "@app/hooks/api"; +import { projectKeys, useGetOrganizations, useLogoutUser } from "@app/hooks/api"; import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries"; import { MfaMethod } from "@app/hooks/api/auth/types"; import { getAuthToken } from "@app/hooks/api/reactQuery"; @@ -135,7 +135,7 @@ export const Navbar = () => { const handleOrgChange = async (orgId: string) => { queryClient.removeQueries({ queryKey: authKeys.getAuthToken }); - queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() }); const { token, isMfaEnabled, mfaMethod } = await selectOrganization({ organizationId: orgId diff --git a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx index da9b5acdf..ec4727330 100644 --- a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx +++ b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx @@ -7,6 +7,7 @@ import { faFileLines, faHome, faMobile, + faPlug, faSitemap, faStamp, faUsers @@ -16,12 +17,12 @@ import { Link, Outlet } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; export const PkiManagerLayout = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { assumedPrivilegeDetails } = useProjectPermission(); const { t } = useTranslation(); @@ -48,7 +49,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -65,7 +66,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -82,7 +83,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -99,7 +100,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -116,7 +117,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -130,12 +131,29 @@ export const PkiManagerLayout = () => { )} + + {({ isActive }) => ( + +
+
+ +
+ App Connections +
+
+ )} + {({ isActive }) => ( @@ -152,7 +170,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( @@ -169,7 +187,7 @@ export const PkiManagerLayout = () => { {({ isActive }) => ( diff --git a/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx b/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx index 995083ee7..754d80547 100644 --- a/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx +++ b/frontend/src/layouts/ProjectLayout/components/AssumePrivilegeModeBanner/AssumePrivilegeModeBanner.tsx @@ -2,13 +2,13 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api"; import { ActorType } from "@app/hooks/api/auditLogs/enums"; export const AssumePrivilegeModeBanner = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const exitAssumePrivilegeMode = useRemoveAssumeProjectPrivilege(); const { assumedPrivilegeDetails } = useProjectPermission(); @@ -32,15 +32,12 @@ export const AssumePrivilegeModeBanner = () => { onClick={() => { exitAssumePrivilegeMode.mutate( { - projectId: currentWorkspace.id + projectId: currentProject.id }, { onSuccess: () => { - const url = getProjectHomePage( - currentWorkspace.type, - currentWorkspace.environments - ); - window.location.href = url.replace("$projectId", currentWorkspace.id); + const url = getProjectHomePage(currentProject.type, currentProject.environments); + window.location.href = url.replace("$projectId", currentProject.id); } } ); diff --git a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx index ebc28a227..eb5b22938 100644 --- a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx +++ b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx @@ -28,21 +28,21 @@ import { OrgPermissionActions, OrgPermissionSubjects, useOrganization, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; -import { useGetUserWorkspaces } from "@app/hooks/api"; +import { useGetUserProjects } from "@app/hooks/api"; +import { Project } from "@app/hooks/api/projects/types"; import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation"; import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries"; -import { Workspace } from "@app/hooks/api/workspace/types"; export const ProjectSelect = () => { const [searchProject, setSearchProject] = useState(""); - const { currentWorkspace } = useWorkspace(); + const { currentProject: currentWorkspace } = useProject(); const { currentOrg } = useOrganization(); - const { data: workspaces = [] } = useGetUserWorkspaces(); + const { data: projects = [] } = useGetUserProjects(); const { data: projectFavorites } = useGetUserProjectFavorites(currentOrg.id); const { subscription } = useSubscription(); @@ -86,16 +86,16 @@ export const ProjectSelect = () => { "upgradePlan" ] as const); - const projects = useMemo(() => { - const projectOptions = workspaces - .map((w): Workspace & { isFavorite: boolean } => ({ + const projectsSortedByFav = useMemo(() => { + const projectOptions = projects + .map((w): Project & { isFavorite: boolean } => ({ ...w, isFavorite: Boolean(projectFavorites?.includes(w.id)) })) .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)); return projectOptions; - }, [workspaces, projectFavorites, currentWorkspace]); + }, [projects, projectFavorites, currentWorkspace]); return (
@@ -147,7 +147,7 @@ export const ProjectSelect = () => { />
- {projects + {projectsSortedByFav ?.filter((el) => el.name?.toLowerCase().includes(searchProject.toLowerCase())) ?.map((workspace) => { return ( @@ -200,16 +200,20 @@ export const ProjectSelect = () => {
- {(isAllowed) => ( - } - onClick={() => - handlePopUpOpen(isAddingProjectsAllowed ? "addNewWs" : "upgradePlan") - } - > - New Project - + {(isOldProjectPermissionAllowed) => ( + + {(isAllowed) => ( + } + onClick={() => + handlePopUpOpen(isAddingProjectsAllowed ? "addNewWs" : "upgradePlan") + } + > + New Project + + )} + )} diff --git a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx index 0584e8e9c..0b9b934c3 100644 --- a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx +++ b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx @@ -6,6 +6,7 @@ import { faCog, faHome, faMobile, + faPlug, faPuzzlePiece, faUsers, faVault @@ -15,7 +16,7 @@ import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount, @@ -25,16 +26,15 @@ import { import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; export const SecretManagerLayout = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject, projectId } = useProject(); const { assumedPrivilegeDetails } = useProjectPermission(); const { t } = useTranslation(); - const workspaceId = currentWorkspace?.id || ""; - const projectSlug = currentWorkspace?.slug || ""; + const projectSlug = currentProject?.slug || ""; const location = useLocation(); const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ - workspaceId + projectId }); const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({ projectSlug @@ -42,7 +42,7 @@ export const SecretManagerLayout = () => { // we only show the secret rotations v1 tab if they have existing rotations const { data: secretRotations } = useGetSecretRotations({ - workspaceId, + workspaceId: projectId, options: { refetchOnMount: false } @@ -74,9 +74,9 @@ export const SecretManagerLayout = () => { @@ -85,7 +85,7 @@ export const SecretManagerLayout = () => { isSelected={ isActive || location.pathname.startsWith( - `/projects/secret-management/${currentWorkspace.id}/overview` + `/projects/secret-management/${currentProject.id}/overview` ) } > @@ -101,7 +101,7 @@ export const SecretManagerLayout = () => { {({ isActive }) => ( @@ -119,7 +119,7 @@ export const SecretManagerLayout = () => { {({ isActive }) => ( @@ -137,7 +137,7 @@ export const SecretManagerLayout = () => { {({ isActive }) => ( @@ -159,12 +159,29 @@ export const SecretManagerLayout = () => { )} + + {({ isActive }) => ( + +
+
+ +
+ App Connections +
+
+ )} + {({ isActive }) => ( @@ -181,7 +198,7 @@ export const SecretManagerLayout = () => { {({ isActive }) => ( @@ -198,7 +215,7 @@ export const SecretManagerLayout = () => { {({ isActive }) => ( diff --git a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx index 265fcc003..af7bba5d1 100644 --- a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx +++ b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx @@ -4,6 +4,7 @@ import { faDatabase, faHome, faMagnifyingGlass, + faPlug, faUsers } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -13,9 +14,9 @@ import { motion } from "framer-motion"; import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { ProjectPermissionSecretScanningFindingActions } from "@app/context/ProjectPermissionContext/types"; import { useGetSecretScanningUnresolvedFindingCount } from "@app/hooks/api/secretScanningV2"; @@ -23,14 +24,14 @@ import { useGetSecretScanningUnresolvedFindingCount } from "@app/hooks/api/secre import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; export const SecretScanningLayout = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { assumedPrivilegeDetails } = useProjectPermission(); const { permission } = useProjectPermission(); const { subscription } = useSubscription(); const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount( - currentWorkspace.id, + currentProject.id, { enabled: subscription.secretScanning && @@ -64,7 +65,7 @@ export const SecretScanningLayout = () => { {({ isActive }) => ( @@ -81,7 +82,7 @@ export const SecretScanningLayout = () => { {({ isActive }) => ( @@ -100,12 +101,29 @@ export const SecretScanningLayout = () => { )} + + {({ isActive }) => ( + +
+
+ +
+ App Connections +
+
+ )} +
{({ isActive }) => ( @@ -122,7 +140,7 @@ export const SecretScanningLayout = () => { {({ isActive }) => ( @@ -139,7 +157,7 @@ export const SecretScanningLayout = () => { {({ isActive }) => ( diff --git a/frontend/src/layouts/SshLayout/SshLayout.tsx b/frontend/src/layouts/SshLayout/SshLayout.tsx index 9c807723e..92872ec1f 100644 --- a/frontend/src/layouts/SshLayout/SshLayout.tsx +++ b/frontend/src/layouts/SshLayout/SshLayout.tsx @@ -15,14 +15,14 @@ import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; export const SshLayout = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { assumedPrivilegeDetails } = useProjectPermission(); return ( @@ -47,7 +47,7 @@ export const SshLayout = () => { {({ isActive }) => ( @@ -70,7 +70,7 @@ export const SshLayout = () => { {({ isActive }) => ( @@ -93,7 +93,7 @@ export const SshLayout = () => { {({ isActive }) => ( @@ -110,7 +110,7 @@ export const SshLayout = () => { {({ isActive }) => ( @@ -127,7 +127,7 @@ export const SshLayout = () => { {({ isActive }) => ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx index 04fafa0b2..5601e48db 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx @@ -13,7 +13,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreatePkiAlert, useGetPkiAlertById, @@ -60,15 +60,15 @@ type Props = { }; export const PkiAlertModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: alert } = useGetPkiAlertById( (popUp?.pkiAlert?.data as { alertId: string })?.alertId || "" ); const { data: pkiCollections } = useListWorkspacePkiCollections({ - workspaceId: projectId + projectId }); const { mutateAsync: createPkiAlert } = useCreatePkiAlert(); diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx index 497f10c39..d3aeefab7 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useDeletePkiAlert } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -12,8 +12,8 @@ import { PkiAlertModal } from "./PkiAlertModal"; import { PkiAlertsTable } from "./PkiAlertsTable"; export const PkiAlertsSection = () => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { mutateAsync: deletePkiAlert } = useDeletePkiAlert(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsTable.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsTable.tsx index b67c5a2c6..4ddbd4671 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsTable.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsTable.tsx @@ -10,7 +10,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useListWorkspacePkiAlerts } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -24,11 +24,11 @@ type Props = { }; export const PkiAlertsTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data, isPending } = useListWorkspacePkiAlerts({ - workspaceId: projectId + projectId }); return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx index 366f51c62..5900a1ba9 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreatePkiCollection, useGetPkiCollectionById, @@ -28,8 +28,8 @@ type Props = { export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: pkiCollection } = useGetPkiCollectionById( (popUp?.pkiCollection?.data as { collectionId: string })?.collectionId || "" diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx index 81f5a8609..66732ad88 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useDeletePkiCollection } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -12,8 +12,8 @@ import { PkiCollectionModal } from "./PkiCollectionModal"; import { PkiCollectionTable } from "./PkiCollectionTable"; export const PkiCollectionSection = () => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { mutateAsync: deletePkiCollection } = useDeletePkiCollection(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionTable.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionTable.tsx index ff525b1f4..ebd86973f 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionTable.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionTable.tsx @@ -19,7 +19,7 @@ import { THead, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useListWorkspacePkiCollections } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -32,11 +32,11 @@ type Props = { export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data, isPending } = useListWorkspacePkiCollections({ - workspaceId: projectId + projectId }); return ( diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx index 0bf3c4431..4e5859215 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx @@ -15,7 +15,7 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api"; import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -30,7 +30,7 @@ import { } from "./components"; const Page = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const params = useParams({ from: ROUTE_PATHS.CertManager.CertAuthDetailsByIDPage.id @@ -38,11 +38,11 @@ const Page = () => { const { caName } = params as { caName: string }; const { data } = useGetCa({ caName, - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", type: CaType.INTERNAL }) as { data: TInternalCertificateAuthority }; - const projectId = currentWorkspace?.id || ""; + const projectId = currentProject?.id || ""; const { mutateAsync: deleteCa } = useDeleteCa(); @@ -55,11 +55,11 @@ const Page = () => { const onRemoveCaSubmit = async () => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; await deleteCa({ caName, - projectId: currentWorkspace.id, + projectId: currentProject.id, type: CaType.INTERNAL }); diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaDetailsSection.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaDetailsSection.tsx index f12fcfd28..9ee5c1b8b 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaDetailsSection.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaDetailsSection.tsx @@ -4,7 +4,7 @@ import { format } from "date-fns"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, IconButton, Tooltip } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { CaStatus, CaType, InternalCaType, useGetCa } from "@app/hooks/api"; import { caStatusToNameMap, caTypeToNameMap } from "@app/hooks/api/ca/constants"; @@ -21,7 +21,7 @@ type Props = { }; export const CaDetailsSection = ({ caName, handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ initialState: "Copy ID to clipboard" }); @@ -31,7 +31,7 @@ export const CaDetailsSection = ({ caName, handlePopUpOpen }: Props) => { const { data } = useGetCa({ caName, - projectId: currentWorkspace.id, + projectId: currentProject.id, type: CaType.INTERNAL }); diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx index 6f9cf3a1c..857586a58 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx @@ -13,7 +13,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaRenewalType, useRenewCa @@ -45,8 +45,8 @@ type Props = { }; export const CaRenewalModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectSlug = currentWorkspace?.slug || ""; + const { currentProject } = useProject(); + const projectSlug = currentProject?.slug || ""; const popUpData = popUp?.renewCa?.data as { caId: string; diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx index 6f79cbb24..d737e6c45 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx @@ -8,7 +8,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, IconButton, TextArea, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { useGetCaCsr, useImportCaCertificate } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -26,7 +26,7 @@ type Props = { }; export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [copyTextCaCsr, isCopyingCaCsr, setCopyTextCaCsr] = useTimedReset({ initialState: "Copy to clipboard" }); @@ -41,7 +41,7 @@ export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { }); const { data: csr } = useGetCaCsr(caId); - const { mutateAsync: importCaCertificate } = useImportCaCertificate(currentWorkspace.id); + const { mutateAsync: importCaCertificate } = useImportCaCertificate(currentProject.id); useEffect(() => { reset(); @@ -49,11 +49,11 @@ export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { const onFormSubmit = async ({ certificate, certificateChain }: FormData) => { try { - if (!csr || !caId || !currentWorkspace?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; await importCaCertificate({ caId, - projectSlug: currentWorkspace?.slug, + projectSlug: currentProject?.slug, certificate, certificateChain }); diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx index 677407dc8..5979d9711 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaStatus, useGetCaById, @@ -46,16 +46,16 @@ type Props = { }; export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: cas } = useListWorkspaceCas({ - projectSlug: currentWorkspace?.slug ?? "", + projectId: currentProject.id, status: CaStatus.ACTIVE }); const { data: ca } = useGetCaById(caId); const { data: csr } = useGetCaCsr(caId); const { mutateAsync: signIntermediate } = useSignIntermediate(); - const { mutateAsync: importCaCertificate } = useImportCaCertificate(currentWorkspace.id); + const { mutateAsync: importCaCertificate } = useImportCaCertificate(currentProject.id); const { control, @@ -102,7 +102,7 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { const onFormSubmit = async ({ notAfter, maxPathLength }: FormData) => { try { - if (!csr || !caId || !currentWorkspace?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; const { certificate, certificateChain } = await signIntermediate({ caId: parentCaId, @@ -114,7 +114,7 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { await importCaCertificate({ caId, - projectSlug: currentWorkspace?.slug, + projectSlug: currentProject?.slug, certificate, certificateChain }); diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx index d3040576f..2a976d682 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx @@ -16,7 +16,7 @@ import { Switch // DatePicker } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaStatus, CaType, @@ -84,10 +84,10 @@ const caTypes = [ ]; export const CaModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: ca } = useGetCa({ caName: (popUp?.ca?.data as { name: string })?.name || "", - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", type: CaType.INTERNAL }); @@ -178,13 +178,13 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => { configuration }: FormData) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; if (ca) { // update await updateMutateAsync({ caName: ca.name, - projectId: currentWorkspace.id, + projectId: currentProject.id, name, type: CaType.INTERNAL, status, @@ -193,7 +193,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => { } else { // create await createMutateAsync({ - projectId: currentWorkspace.id, + projectId: currentProject.id, name, type, status, diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx index 5de6027fa..ed4e8f847 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx @@ -5,7 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -15,7 +15,7 @@ import { CaModal } from "./CaModal"; import { CaTable } from "./CaTable"; export const CaSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: deleteCa } = useDeleteCa(); const { mutateAsync: updateCa } = useUpdateCa(); @@ -30,9 +30,9 @@ export const CaSection = () => { const onRemoveCaSubmit = async (caName: string) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; - await deleteCa({ caName, projectId: currentWorkspace.id, type: CaType.INTERNAL }); + await deleteCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL }); createNotification({ text: "Successfully deleted CA", @@ -50,9 +50,9 @@ export const CaSection = () => { const onUpdateCaStatus = async ({ caName, status }: { caName: string; status: CaStatus }) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName, projectId: currentWorkspace.id, type: CaType.INTERNAL, status }); + await updateCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL, status }); createNotification({ text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx index 00ab03625..81fce98c6 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx @@ -22,7 +22,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useListCasByTypeAndProjectId } from "@app/hooks/api"; import { caStatusToNameMap, @@ -49,8 +49,8 @@ type Props = { export const CaTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListCasByTypeAndProjectId(CaType.INTERNAL, currentWorkspace.id); + const { currentProject } = useProject(); + const { data, isPending } = useListCasByTypeAndProjectId(CaType.INTERNAL, currentProject.id); const cas = data as TInternalCertificateAuthority[]; return ( @@ -80,7 +80,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => { navigate({ to: "/projects/cert-management/$projectId/ca/$caName", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, caName: ca.name } }) diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx index 68757f549..121c32e5f 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx @@ -4,6 +4,7 @@ import { SingleValue } from "react-select"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; +import { AppConnectionOption } from "@app/components/app-connections"; import { createNotification } from "@app/components/notifications"; import { Button, @@ -16,7 +17,7 @@ import { SelectItem, Switch } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { TAvailableAppConnection, @@ -127,11 +128,11 @@ const caTypes = [ ]; export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: ca, isLoading: isCaLoading } = useGetCa({ caName: (popUp?.ca?.data as { name: string })?.name || "", - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", type: (popUp?.ca?.data as { type: CaType })?.type || "" }); @@ -201,17 +202,17 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { }, [popUp?.ca?.isOpen, popUp?.ca?.data, reset, ca]); const { data: availableRoute53Connections, isPending: isRoute53Pending } = - useListAvailableAppConnections(AppConnection.AWS, { + useListAvailableAppConnections(AppConnection.AWS, currentProject.id, { enabled: caType === CaType.ACME }); const { data: availableCloudflareConnections, isPending: isCloudflarePending } = - useListAvailableAppConnections(AppConnection.Cloudflare, { + useListAvailableAppConnections(AppConnection.Cloudflare, currentProject.id, { enabled: caType === CaType.ACME }); const { data: availableAzureConnections, isPending: isAzurePending } = - useListAvailableAppConnections(AppConnection.AzureADCS, { + useListAvailableAppConnections(AppConnection.AzureADCS, currentProject.id, { enabled: caType === CaType.AZURE_AD_CS }); @@ -297,7 +298,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { configuration: formConfiguration }: FormData) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; let configPayload: any; @@ -321,7 +322,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { if (ca) { await updateMutateAsync({ caName: ca.name, - projectId: currentWorkspace.id, + projectId: currentProject.id, name, type, status, @@ -330,7 +331,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { }); } else { await createMutateAsync({ - projectId: currentWorkspace.id, + projectId: currentProject.id, name, type, status, @@ -457,6 +458,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} @@ -599,6 +601,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx index c8d117e21..7e60b0373 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx @@ -5,7 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -13,7 +13,7 @@ import { ExternalCaModal } from "./ExternalCaModal"; import { ExternalCaTable } from "./ExternalCaTable"; export const ExternalCaSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: deleteCa } = useDeleteCa(); const { mutateAsync: updateCa } = useUpdateCa(); @@ -26,9 +26,9 @@ export const ExternalCaSection = () => { const onRemoveCaSubmit = async (caName: string, type: CaType) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; - await deleteCa({ caName, type, projectId: currentWorkspace.id }); + await deleteCa({ caName, type, projectId: currentProject.id }); createNotification({ text: "Successfully deleted CA", @@ -54,9 +54,9 @@ export const ExternalCaSection = () => { status: CaStatus; }) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName: name, type, status, projectId: currentWorkspace.id }); + await updateCa({ caName: name, type, status, projectId: currentProject.id }); createNotification({ text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx index e7b18f429..a948473e8 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx @@ -26,7 +26,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useListExternalCasByProjectId } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -44,8 +44,8 @@ type Props = { }; export const ExternalCaTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListExternalCasByProjectId(currentWorkspace.id); + const { currentProject } = useProject(); + const { data, isPending } = useListExternalCasByProjectId(currentProject.id); return (
diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx index c4cffe7b0..70b51cbbe 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx @@ -14,7 +14,7 @@ import { SelectItem, TextArea } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useGetCert, useImportCertificate, useListWorkspacePkiCollections } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -48,13 +48,13 @@ type TCertificateDetails = { export const CertificateImportModal = ({ popUp, handlePopUpToggle }: Props) => { const [certificateDetails, setCertificateDetails] = useState(null); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: cert } = useGetCert( (popUp?.certificateImport?.data as { serialNumber: string })?.serialNumber || "" ); const { data } = useListWorkspacePkiCollections({ - workspaceId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); const { mutateAsync: importCertificate } = useImportCertificate(); @@ -76,10 +76,10 @@ export const CertificateImportModal = ({ popUp, handlePopUpToggle }: Props) => { collectionId }: FormData) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; const { serialNumber, certificate, certificateChain, privateKey } = await importCertificate({ - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, certificatePem, privateKeyPem, diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx index a10743023..a8509fe17 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx @@ -22,7 +22,7 @@ import { SelectItem, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaStatus, useCreateCertificate, @@ -89,22 +89,22 @@ const CERT_TEMPLATE_NONE_VALUE = "none"; export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { const [certificateDetails, setCertificateDetails] = useState(null); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: cert } = useGetCert( (popUp?.certificate?.data as { serialNumber: string })?.serialNumber || "" ); const { data: cas } = useListWorkspaceCas({ - projectSlug: currentWorkspace?.slug ?? "", + projectId: currentProject.id, status: CaStatus.ACTIVE }); const { data } = useListWorkspacePkiCollections({ - workspaceId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); const { data: templatesData } = useListWorkspaceCertificateTemplates({ - workspaceId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); const { mutateAsync: createCertificate } = useCreateCertificate(); @@ -191,12 +191,12 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { extendedKeyUsages }: FormData) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ caId: !selectedCertTemplate ? caId : undefined, certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined, - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, pkiCollectionId: collectionId, friendlyName, commonName, diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx index 2f14fda3f..1d1539296 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx @@ -4,7 +4,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useRevokeCert } from "@app/hooks/api"; import { crlReasons } from "@app/hooks/api/certificates/constants"; import { CrlReason } from "@app/hooks/api/certificates/enums"; @@ -35,7 +35,7 @@ type Props = { }; export const CertificateRevocationModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: revokeCertificate } = useRevokeCert(); const { @@ -49,12 +49,12 @@ export const CertificateRevocationModal = ({ popUp, handlePopUpToggle }: Props) const onFormSubmit = async ({ revocationReason }: FormData) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; const { serialNumber } = popUp.revokeCertificate.data as { serialNumber: string }; await revokeCertificate({ - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, serialNumber, revocationReason }); diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx index d709458f0..8f547e573 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx @@ -22,7 +22,7 @@ import { SelectItem, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaStatus, useCreateCertTemplate, @@ -82,7 +82,7 @@ type Props = { }; export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: ca } = useGetCaById(caId); @@ -91,12 +91,12 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro ); const { data: cas } = useListWorkspaceCas({ - projectSlug: currentWorkspace?.slug ?? "", + projectId: currentProject?.id, status: CaStatus.ACTIVE }); const { data: collectionsData } = useListWorkspacePkiCollections({ - workspaceId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); const { mutateAsync: createCertTemplate } = useCreateCertTemplate(); @@ -155,7 +155,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro keyUsages, extendedKeyUsages }: FormData) => { - if (!currentWorkspace?.id) { + if (!currentProject?.id) { return; } @@ -163,7 +163,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro if (certTemplate) { await updateCertTemplate({ id: certTemplate.id, - projectId: currentWorkspace.id, + projectId: currentProject.id, pkiCollectionId: collectionId, caId, name, @@ -184,7 +184,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro }); } else { await createCertTemplate({ - projectId: currentWorkspace.id, + projectId: currentProject.id, pkiCollectionId: collectionId, caId, name, diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx index 0db7ba923..89f9780e8 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx @@ -12,7 +12,7 @@ import { DeleteActionModal, IconButton } from "@app/components/v2"; import { ProjectPermissionPkiTemplateActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteCertTemplate } from "@app/hooks/api"; @@ -33,18 +33,18 @@ export const CertificateTemplatesSection = ({ caId }: Props) => { "upgradePlan" ] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: deleteCertTemplate } = useDeleteCertTemplate(); const onRemoveCertificateTemplateSubmit = async (id: string) => { - if (!currentWorkspace?.id) { + if (!currentProject?.id) { return; } try { await deleteCertTemplate({ id, - projectId: currentWorkspace.id + projectId: currentProject.id }); createNotification({ diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx index 44ddbc8ba..696d07762 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx @@ -7,7 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionCertificateActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { useDeleteCert } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -19,7 +19,7 @@ import { CertificateRevocationModal } from "./CertificateRevocationModal"; import { CertificatesTable } from "./CertificatesTable"; export const CertificatesSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: deleteCert } = useDeleteCert(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -32,9 +32,9 @@ export const CertificatesSection = () => { const onRemoveCertificateSubmit = async (serialNumber: string) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; - await deleteCert({ serialNumber, projectSlug: currentWorkspace.slug }); + await deleteCert({ serialNumber, projectSlug: currentProject.slug }); createNotification({ text: "Successfully deleted certificate", diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx index 98f83bc02..3cf35ebd6 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx @@ -33,7 +33,7 @@ import { import { ProjectPermissionCertificateActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { useListWorkspaceCertificates } from "@app/hooks/api"; import { caSupportsCapability } from "@app/hooks/api/ca/constants"; @@ -62,15 +62,15 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data, isPending } = useListWorkspaceCertificates({ - projectSlug: currentWorkspace?.slug ?? "", + projectId: currentProject?.slug ?? "", offset: (page - 1) * perPage, limit: perPage }); // Fetch CA data to determine capabilities - const { data: caData } = useListCasByProjectId(currentWorkspace?.id ?? ""); + const { data: caData } = useListCasByProjectId(currentProject?.id ?? ""); // Create mapping from caId to CA type for capability checking const caCapabilityMap = useMemo(() => { diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx index 0261d55fd..c53df83be 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx @@ -16,7 +16,7 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api"; import { PkiItemType } from "@app/hooks/api/pkiCollections/constants"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -30,8 +30,8 @@ export const PkiCollectionPage = () => { from: ROUTE_PATHS.CertManager.PkiCollectionDetailsByIDPage.id }); const collectionId = params.collectionId as string; - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data } = useGetPkiCollectionById(collectionId); const { mutateAsync: deletePkiCollection } = useDeletePkiCollection(); diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx index f356f2190..3bcc3dc31 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx @@ -4,7 +4,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaStatus, useAddItemToPkiCollection, @@ -41,15 +41,15 @@ export const AddPkiCollectionItemModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: cas } = useListWorkspaceCas({ - projectSlug: currentWorkspace?.slug || "", + projectId: currentProject?.id || "", status: CaStatus.ACTIVE }); const { data } = useListWorkspaceCertificates({ - projectSlug: currentWorkspace?.slug || "", + projectId: currentProject?.slug || "", offset: 0, limit: 25 }); diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx index 04db9861b..d7d44d4b5 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx @@ -19,7 +19,7 @@ import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -29,8 +29,8 @@ import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from ". const Page = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const subscriberName = useParams({ from: ROUTE_PATHS.CertManager.PkiSubscriberDetailsByIDPage.id, select: (el) => el.subscriberName diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx index 62e7337bd..d3533d71d 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx @@ -27,8 +27,8 @@ import { import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useGetPkiSubscriberCertificates } from "@app/hooks/api"; import { caSupportsCapability } from "@app/hooks/api/ca/constants"; @@ -45,8 +45,8 @@ type Props = { const PER_PAGE_INIT = 25; export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { permission } = useProjectPermission(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); @@ -64,7 +64,7 @@ export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen ); // Fetch CA data to determine capabilities - const { data: caData } = useListCasByProjectId(currentWorkspace.id); + const { data: caData } = useListCasByProjectId(currentProject.id); // Create mapping from caId to CA type for capability checking const caCapabilityMap = useMemo(() => { diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx index 1f15982a4..bb928dcc2 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx @@ -16,8 +16,8 @@ import { import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { @@ -44,8 +44,8 @@ type TCertificateDetails = { }; export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { permission } = useProjectPermission(); const [certificateDetails, setCertificateDetails] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx index f6deb97cb..932f3201b 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx @@ -22,7 +22,7 @@ import { TabPanel, Tabs } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { CaType, useCreatePkiSubscriber, @@ -158,8 +158,8 @@ const schema = z export type FormData = z.infer; export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { data: subscribers } = useListWorkspacePkiSubscribers(projectId); const { data: cas } = useListCasByProjectId(projectId); const [tabValue, setTabValue] = useState(FormTab.Configuration); diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx index f81636e49..f9680af9b 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx @@ -7,7 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { useDeletePkiSubscriber, useUpdatePkiSubscriber } from "@app/hooks/api"; import { PkiSubscriberStatus } from "@app/hooks/api/pkiSubscriber/types"; @@ -17,8 +17,8 @@ import { PkiSubscriberModal } from "./PkiSubscriberModal"; import { PkiSubscribersTable } from "./PkiSubscribersTable"; export const PkiSubscriberSection = () => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber(); const { mutateAsync: updatePkiSubscriber } = useUpdatePkiSubscriber(); @@ -55,7 +55,7 @@ export const PkiSubscriberSection = () => { status: PkiSubscriberStatus; }) => { try { - if (!currentWorkspace?.slug) return; + if (!currentProject?.slug) return; await updatePkiSubscriber({ subscriberName, projectId, status }); diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx index d649c43a7..d28c2d3a2 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx @@ -30,7 +30,7 @@ import { import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { useListWorkspacePkiSubscribers } from "@app/hooks/api"; import { @@ -49,8 +49,8 @@ type Props = { export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListWorkspacePkiSubscribers(currentWorkspace?.id || ""); + const { currentProject } = useProject(); + const { data, isPending } = useListWorkspacePkiSubscribers(currentProject?.id || ""); return (
@@ -77,7 +77,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => { navigate({ to: "/projects/cert-management/$projectId/subscribers/$subscriberName", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, subscriberName: subscriber.name } }) diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx index 4449e9a97..2ffe68813 100644 --- a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx @@ -42,8 +42,8 @@ import { import { ProjectPermissionPkiTemplateActions, ProjectPermissionSub, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteCertTemplateV2 } from "@app/hooks/api"; @@ -55,7 +55,7 @@ import { PkiTemplateForm } from "./components/PkiTemplateForm"; const PER_PAGE_INIT = 25; export const PkiTemplateListPage = () => { const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ @@ -68,7 +68,7 @@ export const PkiTemplateListPage = () => { const { subscription } = useSubscription(); const { data, isPending } = useListCertificateTemplates({ - projectId: currentWorkspace.id, + projectId: currentProject.id, offset: (page - 1) * perPage, limit: perPage }); @@ -78,7 +78,7 @@ export const PkiTemplateListPage = () => { const onRemovePkiSubscriberSubmit = async () => { try { const pkiTemplate = await deleteCertTemplate.mutateAsync({ - projectId: currentWorkspace.id, + projectId: currentProject.id, templateName: popUp?.deleteTemplate?.data?.name }); diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx index f52c83725..b8cdbfe1c 100644 --- a/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx @@ -18,7 +18,7 @@ import { Input, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateCertTemplateV2, useListCasByProjectId, @@ -72,9 +72,9 @@ type Props = { }; export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: cas, isPending: isCaLoading } = useListCasByProjectId(currentWorkspace.id); + const { data: cas, isPending: isCaLoading } = useListCasByProjectId(currentProject.id); const { mutateAsync: createCertTemplate } = useCreateCertTemplateV2(); const { mutateAsync: updateCertTemplate } = useUpdateCertTemplateV2(); @@ -124,7 +124,7 @@ export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { extendedKeyUsages, ca }: FormData) => { - if (!currentWorkspace?.id) { + if (!currentProject?.id) { return; } @@ -132,7 +132,7 @@ export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { if (certTemplate) { await updateCertTemplate({ templateName: certTemplate.name, - projectId: currentWorkspace.id, + projectId: currentProject.id, caName: ca.name, name, commonName, @@ -152,7 +152,7 @@ export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { }); } else { await createCertTemplate({ - projectId: currentWorkspace.id, + projectId: currentProject.id, caName: ca.name, name, commonName, diff --git a/frontend/src/pages/cert-manager/layout.tsx b/frontend/src/pages/cert-manager/layout.tsx index 6b846909e..c8ec6a23b 100644 --- a/frontend/src/pages/cert-manager/layout.tsx +++ b/frontend/src/pages/cert-manager/layout.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; import { BreadcrumbTypes } from "@app/components/v2"; -import { workspaceKeys } from "@app/hooks/api"; +import { projectKeys } from "@app/hooks/api"; +import { fetchProjectById } from "@app/hooks/api/projects/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; -import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { PkiManagerLayout } from "@app/layouts/PkiManagerLayout"; import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect"; @@ -13,15 +13,15 @@ export const Route = createFileRoute( component: PkiManagerLayout, beforeLoad: async ({ params, context }) => { const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId) + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId) }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId + projectId: params.projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + queryFn: () => fetchUserProjectPermissions({ projectId: params.projectId }) }); return { diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx index 8c959c7b0..7abbee35f 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx @@ -13,7 +13,7 @@ import { ModalContent, TextArea } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateKmipClient, useUpdateKmipClient } from "@app/hooks/api/kmip"; import { KmipPermission, TKmipClient } from "@app/hooks/api/kmip/types"; @@ -60,8 +60,8 @@ type FormProps = Pick & { const KmipClientForm = ({ onComplete, kmipClient }: FormProps) => { const createKmipClient = useCreateKmipClient(); const updateKmipClient = useUpdateKmipClient(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const isUpdate = !!kmipClient; const { diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx index bcc49b11a..5abad9f18 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx @@ -39,9 +39,9 @@ import { import { ProjectPermissionKmipActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { getUserTablePreference, @@ -59,9 +59,9 @@ import { KmipClientCertificateModal } from "./KmipClientCertificateModal"; import { KmipClientModal } from "./KmipClientModal"; export const KmipClientTable = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const projectId = currentWorkspace?.id ?? ""; + const projectId = currentProject?.id ?? ""; const { offset, diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx index c43b6c8d0..93c0102b1 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx @@ -14,7 +14,7 @@ import { SelectItem, TextArea } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { keyUsageDefaultOption, kmsKeyUsageOptions } from "@app/helpers/kms"; import { AllowedEncryptionKeyAlgorithms, @@ -49,8 +49,8 @@ type FormProps = Pick & { const CmekForm = ({ onComplete, cmek }: FormProps) => { const createCmek = useCreateCmek(); const updateCmek = useUpdateCmek(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const isUpdate = !!cmek; const { diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx index e1a2fe789..b1a7c409c 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx @@ -49,8 +49,8 @@ import { ProjectPermissionActions, ProjectPermissionCmekActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { kmsKeyUsageOptions } from "@app/helpers/kms"; import { @@ -87,10 +87,10 @@ const getStatusBadgeProps = ( }; export const CmekTable = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { permission } = useProjectPermission(); - const projectId = currentWorkspace?.id ?? ""; + const projectId = currentProject?.id ?? ""; const { offset, diff --git a/frontend/src/pages/kms/layout.tsx b/frontend/src/pages/kms/layout.tsx index 60bc35ab7..f29a7627a 100644 --- a/frontend/src/pages/kms/layout.tsx +++ b/frontend/src/pages/kms/layout.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; import { BreadcrumbTypes } from "@app/components/v2"; -import { workspaceKeys } from "@app/hooks/api"; +import { projectKeys } from "@app/hooks/api"; +import { fetchProjectById } from "@app/hooks/api/projects/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; -import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { KmsLayout } from "@app/layouts/KmsLayout"; import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect"; @@ -13,15 +13,15 @@ export const Route = createFileRoute( component: KmsLayout, beforeLoad: async ({ params, context }) => { const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId) + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId) }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId + projectId: params.projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + queryFn: () => fetchUserProjectPermissions({ projectId: params.projectId }) }); return { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx index 7f751fc7d..c1c9f0b0a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx @@ -21,10 +21,10 @@ import { useAddUsersToOrg, useFetchServerStatus, useGetOrgRoles, - useGetUserWorkspaces + useGetUserProjects } from "@app/hooks/api"; +import { ProjectType, ProjectVersion } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { OrgInviteLink } from "./OrgInviteLink"; @@ -76,7 +76,7 @@ export const AddOrgMemberModal = ({ const { data: organizationRoles } = useGetOrgRoles(currentOrg?.id ?? ""); const { data: serverDetails } = useFetchServerStatus(); const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg(); - const { data: projects, isPending: isProjectsLoading } = useGetUserWorkspaces({ + const { data: projects, isPending: isProjectsLoading } = useGetUserProjects({ includeRoles: true }); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx index ff3f31c9e..80aefe3f2 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx @@ -1,24 +1,17 @@ import { Helmet } from "react-helmet"; -import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { OrgPermissionCan } from "@app/components/permissions"; -import { Button, PageHeader } from "@app/components/v2"; +import { PageHeader } from "@app/components/v2"; import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext/types"; import { withPermission } from "@app/hoc"; -import { usePopUp } from "@app/hooks"; -import { - AddAppConnectionModal, - AppConnectionsTable -} from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; +import { AppConnectionsTable } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; export const AppConnectionsPage = withPermission( () => { - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const); - return (
@@ -30,54 +23,18 @@ export const AppConnectionsPage = withPermission(
- App Connections - -
- - Docs - -
-
- - {(isAllowed) => ( - - )} - -
- } - description="Create and configure connections with third-party apps for re-use across Infisical projects" + title="App Connections" + description="Manage organization App Connections" /> -
- - handlePopUpToggle("addConnection", isOpen)} - /> +
+
+ + + App connections can also be created and managed independently in projects now. + +
+
diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx index c74985a3f..c52f89dc9 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { Modal, ModalContent } from "@app/components/v2"; import { TAppConnection } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { AppConnectionForm } from "./AppConnectionForm"; import { AppConnectionsSelect } from "./AppConnectionList"; @@ -10,29 +11,44 @@ import { AppConnectionsSelect } from "./AppConnectionList"; type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; + projectId?: string; + projectType?: ProjectType; + app?: AppConnection; + onComplete?: (appConnection: TAppConnection) => void; }; type ContentProps = { onComplete: (appConnection: TAppConnection) => void; + projectId?: string; + projectType?: ProjectType; + app?: AppConnection; }; -const Content = ({ onComplete }: ContentProps) => { +const Content = ({ onComplete, projectId, projectType, app }: ContentProps) => { const [selectedApp, setSelectedApp] = useState(null); - if (selectedApp) { + if (app ?? selectedApp) { return ( setSelectedApp(null)} - app={selectedApp} + onBack={app ? undefined : () => setSelectedApp(null)} + app={(app ?? selectedApp)!} + projectId={projectId} /> ); } - return ; + return ; }; -export const AddAppConnectionModal = ({ isOpen, onOpenChange }: Props) => { +export const AddAppConnectionModal = ({ + isOpen, + onOpenChange, + projectId, + projectType, + app, + onComplete +}: Props) => { return ( { title="Add Connection" subTitle="Select a third-party app to connect to." > - onOpenChange(false)} /> + { + if (onComplete) onComplete(appConnection); + onOpenChange(false); + }} + /> ); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index f1826e5ae..f82e4107d 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -52,12 +52,15 @@ type FormProps = { onComplete: (appConnection: TAppConnection) => void; } & ({ appConnection: TAppConnection } | { app: AppConnection }); -type CreateFormProps = FormProps & { app: AppConnection }; +type CreateFormProps = FormProps & { + app: AppConnection; + projectId?: string; +}; type UpdateFormProps = FormProps & { appConnection: TAppConnection; }; -const CreateForm = ({ app, onComplete }: CreateFormProps) => { +const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { const createAppConnection = useCreateAppConnection(); const { name: appName } = APP_CONNECTION_MAP[app]; @@ -68,7 +71,10 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { > ) => { try { - const connection = await createAppConnection.mutateAsync(formData); + const connection = await createAppConnection.mutateAsync({ + ...formData, + projectId + }); createNotification({ text: `Successfully added ${appName} Connection`, type: "success" @@ -88,15 +94,15 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { case AppConnection.AWS: return ; case AppConnection.GitHub: - return ; + return ; case AppConnection.GitHubRadar: - return ; + return ; case AppConnection.GCP: return ; case AppConnection.AzureKeyVault: - return ; + return ; case AppConnection.AzureAppConfiguration: - return ; + return ; case AppConnection.AzureADCS: return ; case AppConnection.Databricks: @@ -118,9 +124,9 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { case AppConnection.Camunda: return ; case AppConnection.AzureClientSecrets: - return ; + return ; case AppConnection.AzureDevOps: - return ; + return ; case AppConnection.Windmill: return ; case AppConnection.Auth0: @@ -142,7 +148,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { case AppConnection.Flyio: return ; case AppConnection.GitLab: - return ; + return ; case AppConnection.Cloudflare: return ; case AppConnection.Bitbucket: @@ -200,16 +206,33 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { case AppConnection.AWS: return ; case AppConnection.GitHub: - return ; + return ( + + ); case AppConnection.GitHubRadar: - return ; + return ( + + ); case AppConnection.GCP: return ; case AppConnection.AzureKeyVault: - return ; + return ( + + ); case AppConnection.AzureAppConfiguration: return ( - + ); case AppConnection.AzureADCS: return ; @@ -232,9 +255,21 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { case AppConnection.Camunda: return ; case AppConnection.AzureClientSecrets: - return ; + return ( + + ); case AppConnection.AzureDevOps: - return ; + return ( + + ); case AppConnection.Windmill: return ; case AppConnection.Auth0: @@ -256,7 +291,13 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { case AppConnection.Flyio: return ; case AppConnection.GitLab: - return ; + return ( + + ); case AppConnection.Cloudflare: return ; case AppConnection.Bitbucket: @@ -278,12 +319,12 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { } }; -type Props = { onBack?: () => void } & Pick & +type Props = { onBack?: () => void; projectId?: string } & Pick & ( | { app: AppConnection; appConnection?: undefined } | { app?: undefined; appConnection: TAppConnection } ); -export const AppConnectionForm = ({ onBack, ...props }: Props) => { +export const AppConnectionForm = ({ onBack, projectId, ...props }: Props) => { const { app, appConnection } = props; return ( @@ -296,7 +337,7 @@ export const AppConnectionForm = ({ onBack, ...props }: Props) => { {appConnection ? ( ) : ( - + )}
); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx index 433abf076..3772478f0 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx @@ -6,7 +6,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { AzureAppConfigurationConnectionMethod, @@ -15,6 +19,7 @@ import { } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { AzureAppConfigurationFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -25,6 +30,7 @@ type ClientSecretForm = z.infer; type Props = { appConnection?: TAzureAppConfigurationConnection; onSubmit: (formData: ClientSecretForm) => Promise; + projectId: string | undefined | null; }; const baseSchema = genericAppConnectionFieldsSchema.extend({ @@ -96,7 +102,11 @@ const getDefaultValues = (appConnection?: TAzureAppConfigurationConnection): Par return base; }; -export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: Props) => { +export const AzureAppConfigurationConnectionForm = ({ + appConnection, + onSubmit, + projectId +}: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -110,6 +120,8 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: defaultValues: getDefaultValues(appConnection) }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -128,7 +140,12 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "azureAppConfigurationConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + connectionId: appConnection?.id, + projectId, + returnUrl + } as AzureAppConfigurationFormData) ); window.location.assign( `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-app-configuration` diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx index 6f70f789e..dcfdbaa08 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx @@ -7,7 +7,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { AzureClientSecretsConnectionMethod, @@ -16,6 +20,7 @@ import { } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { AzureClientSecretsFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -26,6 +31,7 @@ type ClientSecretForm = z.infer; type Props = { appConnection?: TAzureClientSecretsConnection; onSubmit: (formData: ClientSecretForm) => Promise; + projectId: string | undefined | null; }; const baseSchema = genericAppConnectionFieldsSchema.extend({ @@ -97,7 +103,7 @@ const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partia return base; }; -export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Props) => { +export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -111,6 +117,8 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr defaultValues: getDefaultValues(appConnection) }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -129,7 +137,12 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "azureClientSecretsConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + connectionId: appConnection?.id, + projectId, + returnUrl + } as AzureClientSecretsFormData) ); window.location.assign( `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://graph.microsoft.com/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets` diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureDevOpsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureDevOpsConnectionForm.tsx index c4511a0da..7d2a4ce7d 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureDevOpsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureDevOpsConnectionForm.tsx @@ -7,7 +7,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { AzureDevOpsConnectionMethod, @@ -16,6 +20,7 @@ import { } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { AzureDevOpsFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -65,6 +70,7 @@ type OnSubmitForm = z.infer | z.infer Promise; + projectId: string | undefined | null; }; const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial => { @@ -132,7 +138,7 @@ const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial { +export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -146,6 +152,8 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) => defaultValues: getDefaultValues(appConnection) }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -164,7 +172,12 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) => localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "azureDevOpsConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + connectionId: appConnection?.id, + projectId, + returnUrl + } as AzureDevOpsFormData) ); window.location.assign( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx index ed99fda01..8c0f00acf 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx @@ -6,7 +6,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { useGetAppConnectionOption } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; @@ -15,6 +19,7 @@ import { TAzureKeyVaultConnection } from "@app/hooks/api/appConnections/types/azure-key-vault-connection"; +import { AzureKeyVaultFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -25,6 +30,7 @@ type ClientSecretForm = z.infer; type Props = { appConnection?: TAzureKeyVaultConnection; onSubmit: (formData: ClientSecretForm) => Promise; + projectId: string | undefined | null; }; const baseSchema = genericAppConnectionFieldsSchema.extend({ @@ -96,7 +102,7 @@ const getDefaultValues = (appConnection?: TAzureKeyVaultConnection): Partial { +export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -110,6 +116,8 @@ export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props) defaultValues: getDefaultValues(appConnection) }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -129,7 +137,12 @@ export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props) localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "azureKeyVaultConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + connectionId: appConnection?.id, + projectId, + returnUrl + } as AzureKeyVaultFormData) ); window.location.assign( `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://vault.azure.net/.default%20openid%20offline_access&state=${state}<:>azure-key-vault` diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx index 5c9a5bcd3..c69f662c6 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx @@ -25,7 +25,11 @@ import { OrgGatewayPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext/types"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { gatewaysQueryKeys } from "@app/hooks/api"; import { @@ -35,6 +39,7 @@ import { } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { GithubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -42,6 +47,7 @@ import { type Props = { appConnection?: TGitHubConnection; + projectId: string | undefined | null; }; const formSchema = genericAppConnectionFieldsSchema.extend({ @@ -63,7 +69,7 @@ const formSchema = genericAppConnectionFieldsSchema.extend({ type FormData = z.infer; -export const GitHubConnectionForm = ({ appConnection }: Props) => { +export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -98,13 +104,21 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => { const selectedMethod = watch("method"); const instanceType = watch("credentials.instanceType"); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const onSubmit = (formData: FormData) => { setIsRedirecting(true); const state = crypto.randomBytes(16).toString("hex"); localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "githubConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + credentials: formData.credentials as TGitHubConnection["credentials"], + connectionId: appConnection?.id, + projectId, + returnUrl + } as GithubFormData) ); const githubHost = diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubRadarConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubRadarConnectionForm.tsx index f9c39a502..446eeb4cc 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubRadarConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubRadarConnectionForm.tsx @@ -6,7 +6,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { GitHubRadarConnectionMethod, @@ -15,6 +19,7 @@ import { } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { GithubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -22,6 +27,7 @@ import { type Props = { appConnection?: TGitHubRadarConnection; + projectId: string | undefined | null; }; const formSchema = genericAppConnectionFieldsSchema.extend({ @@ -31,7 +37,7 @@ const formSchema = genericAppConnectionFieldsSchema.extend({ type FormData = z.infer; -export const GitHubRadarConnectionForm = ({ appConnection }: Props) => { +export const GitHubRadarConnectionForm = ({ appConnection, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -48,6 +54,8 @@ export const GitHubRadarConnectionForm = ({ appConnection }: Props) => { } }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -63,7 +71,12 @@ export const GitHubRadarConnectionForm = ({ appConnection }: Props) => { localStorage.setItem("latestCSRFToken", state); localStorage.setItem( "githubRadarConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) + JSON.stringify({ + ...formData, + connectionId: appConnection?.id, + projectId, + returnUrl + } as GithubRadarFormData) ); switch (formData.method) { diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitLabConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitLabConnectionForm.tsx index 8cbc2c54c..ed4e022c4 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitLabConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitLabConnectionForm.tsx @@ -16,7 +16,11 @@ import { Select, SelectItem } from "@app/components/v2"; -import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + APP_CONNECTION_MAP, + getAppConnectionMethodDetails, + useGetAppConnectionOauthReturnUrl +} from "@app/helpers/appConnections"; import { isInfisicalCloud } from "@app/helpers/platform"; import { useGetAppConnectionOption } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; @@ -26,6 +30,7 @@ import { TGitLabConnection } from "@app/hooks/api/appConnections/types/gitlab-connection"; +import { GitLabFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types"; import { genericAppConnectionFieldsSchema, GenericAppConnectionsFields @@ -34,6 +39,7 @@ import { type Props = { appConnection?: TGitLabConnection; onSubmit: (formData: FormData) => Promise; + projectId: string | undefined | null; }; const formSchema = z.discriminatedUnion("method", [ @@ -72,7 +78,7 @@ const formSchema = z.discriminatedUnion("method", [ type FormData = z.infer; -export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Props) => { +export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit, projectId }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -98,6 +104,8 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr } as FormData)) }); + const returnUrl = useGetAppConnectionOauthReturnUrl(); + const { handleSubmit, control, @@ -132,8 +140,10 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr JSON.stringify({ ...formData, connectionId: appConnection?.id, - isUpdate - }) + isUpdate, + projectId, + returnUrl + } as GitLabFormData) ); // Redirect to Gitlab OAuth diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx index 2087ebf63..0137d5a9e 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx @@ -9,14 +9,16 @@ import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useAppConnectionOptions } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { ProjectType } from "@app/hooks/api/projects/types"; type Props = { onSelect: (app: AppConnection) => void; + projectType?: ProjectType; }; -export const AppConnectionsSelect = ({ onSelect }: Props) => { +export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => { const { subscription } = useSubscription(); - const { isPending, data: appConnectionOptions } = useAppConnectionOptions(); + const { isPending, data: appConnectionOptions } = useAppConnectionOptions(projectType); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx index fb7f880c4..941a90db4 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx @@ -1,19 +1,23 @@ import { useCallback } from "react"; +import { subject } from "@casl/ability"; import { faAsterisk, + faBuilding, faCheck, faCopy, faEdit, faEllipsisV, faInfoCircle, faServer, + faTable, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { VariablePermissionCan } from "@app/components/permissions"; import { Badge, DropdownMenu, @@ -25,9 +29,11 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { OrgPermissionSubjects } from "@app/context"; +import { OrgPermissionSubjects, ProjectPermissionSub } from "@app/context"; import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; +import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { getProjectBaseURL } from "@app/helpers/project"; import { useToggle } from "@app/hooks"; import { TAppConnection } from "@app/hooks/api/appConnections"; @@ -36,15 +42,18 @@ type Props = { onDelete: (appConnection: TAppConnection) => void; onEditCredentials: (appConnection: TAppConnection) => void; onEditDetails: (appConnection: TAppConnection) => void; + isProjectView: boolean; }; export const AppConnectionRow = ({ appConnection, onDelete, onEditCredentials, - onEditDetails + onEditDetails, + isProjectView }: Props) => { - const { id, name, method, app, description, isPlatformManagedCredentials } = appConnection; + const { id, name, method, app, description, isPlatformManagedCredentials, project } = + appConnection; const [isIdCopied, setIsIdCopied] = useToggle(false); @@ -111,7 +120,38 @@ export const AppConnectionRow = ({ {methodDetails.name}

- + {!isProjectView && ( + + {project ? ( + +

+ + {project.name} +

+ + ) : ( +

+ + Organization +

+ )} + + )}
{isPlatformManagedCredentials && ( @@ -143,48 +183,91 @@ export const AppConnectionRow = ({ > Copy Connection ID - - {(isAllowed: boolean) => ( - } - onClick={() => onEditDetails(appConnection)} + {(isProjectView || !project) && ( + <> + - Edit Details - - )} - - - {(isAllowed: boolean) => ( - } - onClick={() => onEditCredentials(appConnection)} + {(isAllowed: boolean) => ( + } + onClick={() => onEditDetails(appConnection)} + > + Edit Details + + )} + + - {isPlatformManagedCredentials ? "View" : "Edit"} Credentials - - )} - - - {(isAllowed: boolean) => ( - } - onClick={() => onDelete(appConnection)} + {(isAllowed: boolean) => ( + } + onClick={() => onEditCredentials(appConnection)} + > + {isPlatformManagedCredentials ? "View" : "Edit"} Credentials + + )} + + - Delete Connection - - )} - + {(isAllowed: boolean) => ( + } + onClick={() => onDelete(appConnection)} + > + Delete Connection + + )} + + + )} diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx index bc448663a..91ad35d4a 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx @@ -2,16 +2,21 @@ import { useMemo, useState } from "react"; import { faArrowDown, faArrowUp, + faArrowUpRightFromSquare, + faBookOpen, faCheckCircle, faFilter, faMagnifyingGlass, faPlug, + faPlus, faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; +import { VariablePermissionCan } from "@app/components/permissions"; import { + Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -29,6 +34,9 @@ import { THead, Tr } from "@app/components/v2"; +import { OrgPermissionSubjects, ProjectPermissionSub } from "@app/context"; +import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; +import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; import { getUserTablePreference, @@ -39,7 +47,9 @@ import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { TAppConnection, useListAppConnections } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; +import { AddAppConnectionModal } from "./AddAppConnectionModal"; import { AppConnectionRow } from "./AppConnectionRow"; import { DeleteAppConnectionModal } from "./DeleteAppConnectionModal"; import { EditAppConnectionCredentialsModal } from "./EditAppConnectionCredentialsModal"; @@ -48,22 +58,45 @@ import { EditAppConnectionDetailsModal } from "./EditAppConnectionDetailsModal"; enum AppConnectionsOrderBy { App = "app", Name = "name", - Method = "method" + Method = "method", + ManagedBy = "managed-by" } type AppConnectionFilters = { apps: AppConnection[]; }; -export const AppConnectionsTable = () => { - const { isPending, data: appConnections = [] } = useListAppConnections(); +enum View { + All = "all", + Scope = "scope" +} + +const APP_CONNECTION_VIEW_STORAGE_KEY = "app-connection-view"; + +type Props = { + projectId?: string; + projectType?: ProjectType; +}; + +export const AppConnectionsTable = ({ projectId, projectType }: Props) => { + const isProjectView = Boolean(projectId); + const { isPending, data: appConnections = [] } = useListAppConnections(projectId); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "addConnection", "deleteConnection", "editCredentials", "editDetails" ] as const); + const [view, setView] = useState(() => { + const storedView = localStorage.getItem(APP_CONNECTION_VIEW_STORAGE_KEY) as View | null; + + if (storedView && Object.values(View).includes(storedView)) return storedView; + + return View.Scope; + }); + const [filters, setFilters] = useState({ apps: [] }); @@ -96,6 +129,10 @@ export const AppConnectionsTable = () => { .filter((appConnection) => { const { app, method, name } = appConnection; + if (view === View.Scope && !isProjectView && appConnection.projectId) { + return false; + } + if (filters.apps.length && !filters.apps.includes(app)) return false; const searchValue = search.trim().toLowerCase(); @@ -121,6 +158,13 @@ export const AppConnectionsTable = () => { .localeCompare( getAppConnectionMethodDetails(connectionTwo.method).name.toLowerCase() ); + case AppConnectionsOrderBy.ManagedBy: + if (!connectionOne.project) return 1; + if (!connectionTwo.project) return -1; + + return connectionOne.project.name + .toLowerCase() + .localeCompare(connectionTwo.project.name.toLowerCase()); case AppConnectionsOrderBy.App: default: return APP_CONNECTION_MAP[connectionOne.app].name @@ -128,7 +172,7 @@ export const AppConnectionsTable = () => { .localeCompare(APP_CONNECTION_MAP[connectionTwo.app].name.toLowerCase()); } }), - [appConnections, orderDirection, search, orderBy, filters] + [appConnections, orderDirection, search, orderBy, filters, view] ); useResetPageHelper({ @@ -165,8 +209,87 @@ export const AppConnectionsTable = () => { handlePopUpOpen("editDetails", appConnection); return ( -
+
+
+
+
+

App Connections

+ +
+ + Docs + +
+
+
+

+ Create and configure connections with third-party apps for re-use across your project + {isProjectView ? "" : "s"}. +

+
+ + {(isAllowed) => ( + + )} + +
+ {!isProjectView && ( +
+ + +
+ )} setSearch(e.target.value)} @@ -188,38 +311,46 @@ export const AppConnectionsTable = () => { - + Filter by Apps {appConnections.length ? ( - [...new Set(appConnections.map(({ app }) => app))].map((app) => ( - { - e.preventDefault(); - setFilters((prev) => ({ - ...prev, - apps: prev.apps.includes(app) - ? prev.apps.filter((a) => a !== app) - : [...prev.apps, app] - })); - }} - key={app} - icon={ - filters.apps.includes(app) && ( - - ) - } - iconPos="right" - > -
- {`${APP_CONNECTION_MAP[app].name} - {APP_CONNECTION_MAP[app].name} -
-
- )) + [...new Set(appConnections.map(({ app }) => app))] + .sort((a, b) => { + return a.toLowerCase().localeCompare(b.toLowerCase()); + }) + .map((app) => ( + { + e.preventDefault(); + setFilters((prev) => ({ + ...prev, + apps: prev.apps.includes(app) + ? prev.apps.filter((a) => a !== app) + : [...prev.apps, app] + })); + }} + key={app} + icon={ + filters.apps.includes(app) && ( + + ) + } + iconPos="right" + > +
+ {`${APP_CONNECTION_MAP[app].name} + {APP_CONNECTION_MAP[app].name} +
+
+ )) ) : ( No Connections Configured )} @@ -269,7 +400,21 @@ export const AppConnectionsTable = () => {
- + {!isProjectView && ( + +
+ Managed By + handleSort(AppConnectionsOrderBy.ManagedBy)} + > + + +
+ + )} @@ -284,6 +429,7 @@ export const AppConnectionsTable = () => { onDelete={handleDelete} onEditCredentials={handleEditCredentials} onEditDetails={handleEditDetails} + isProjectView={isProjectView} /> ))} @@ -323,6 +469,12 @@ export const AppConnectionsTable = () => { onOpenChange={(isOpen) => handlePopUpToggle("editDetails", isOpen)} appConnection={popUp.editDetails.data} /> + handlePopUpToggle("addConnection", isOpen)} + projectId={projectId} + projectType={projectType} + />
); }; diff --git a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx index 2798a7121..f5f6d2c88 100644 --- a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx @@ -12,73 +12,14 @@ import { AzureKeyVaultConnectionMethod, GitHubConnectionMethod, GitLabConnectionMethod, - TAzureAppConfigurationConnection, - TAzureClientSecretsConnection, - TAzureDevOpsConnection, - TAzureKeyVaultConnection, - TGitHubConnection, - TGitHubRadarConnection, - TGitLabConnection, + TAppConnection, useCreateAppConnection, useUpdateAppConnection } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; -type BaseFormData = { - returnUrl?: string; - connectionId?: string; - isUpdate?: boolean; -}; - -type GithubFormData = BaseFormData & - Pick; - -type GithubRadarFormData = BaseFormData & - Pick; - -type GitLabFormData = BaseFormData & - Pick; - -type AzureKeyVaultFormData = BaseFormData & - Pick & - Pick; - -type AzureAppConfigurationFormData = BaseFormData & - Pick & - Pick; - -type AzureClientSecretsFormData = BaseFormData & - Pick & - Pick; - -type OAuthCredentials = Extract< - TAzureDevOpsConnection, - { method: AzureDevOpsConnectionMethod.OAuth } ->["credentials"]; -type AccessTokenCredentials = Extract< - TAzureDevOpsConnection, - { method: AzureDevOpsConnectionMethod.AccessToken } ->["credentials"]; - -type AzureDevOpsFormData = BaseFormData & - Pick & - (Pick | Pick); - -type FormDataMap = { - [AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub }; - [AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar }; - [AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab }; - [AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault }; - [AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & { - app: AppConnection.AzureAppConfiguration; - }; - [AppConnection.AzureClientSecrets]: AzureClientSecretsFormData & { - app: AppConnection.AzureClientSecrets; - }; - [AppConnection.AzureDevOps]: AzureDevOpsFormData & { - app: AppConnection.AzureDevOps; - }; -}; +import { FormDataMap } from "./OauthCallbackPage.types"; const formDataStorageFieldMap: Partial> = { [AppConnection.GitHub]: "githubConnectionFormData", @@ -148,11 +89,14 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.GitLab); - const { connectionId, name, description, returnUrl, isUpdate, credentials } = formData; + const { connectionId, name, description, returnUrl, isUpdate, projectId, credentials } = + formData; + + let connection: TAppConnection; try { if (isUpdate && connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.GitLab, connectionId, credentials: { @@ -161,10 +105,11 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.GitLab, name, description, + projectId, method: GitLabConnectionMethod.OAuth, credentials: { code: code as string, @@ -173,14 +118,12 @@ export const OAuthCallbackPage = () => { }); } - navigate({ - to: returnUrl ?? "/organization/app-connections" - }); - return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; } catch (err: any) { createNotification({ @@ -189,7 +132,10 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); return null; } @@ -201,11 +147,13 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.AzureKeyVault); - const { connectionId, name, description, returnUrl } = formData; + const { connectionId, name, description, returnUrl, projectId } = formData; + + let connection: TAppConnection; try { if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.AzureKeyVault, connectionId, credentials: { @@ -214,10 +162,11 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.AzureKeyVault, name, description, + projectId, method: AzureKeyVaultConnectionMethod.OAuth, credentials: { tenantId: formData.tenantId, @@ -232,14 +181,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -249,11 +204,13 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.AzureAppConfiguration); - const { connectionId, name, description, returnUrl } = formData; + const { connectionId, name, description, returnUrl, projectId } = formData; + + let connection: TAppConnection; try { if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.AzureAppConfiguration, connectionId, credentials: { @@ -262,10 +219,11 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.AzureAppConfiguration, name, description, + projectId, method: AzureAppConfigurationConnectionMethod.OAuth, credentials: { code: code as string, @@ -280,14 +238,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -297,11 +261,13 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.AzureClientSecrets); - const { connectionId, name, description, returnUrl } = formData; + const { connectionId, name, description, returnUrl, projectId } = formData; + + let connection: TAppConnection; try { if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.AzureClientSecrets, connectionId, credentials: { @@ -310,11 +276,12 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.AzureClientSecrets, name, description, method: AzureClientSecretsConnectionMethod.OAuth, + projectId, credentials: { code: code as string, tenantId: formData.tenantId @@ -328,14 +295,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -345,7 +318,9 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.AzureDevOps); - const { connectionId, name, description, returnUrl } = formData; + const { connectionId, name, description, returnUrl, projectId } = formData; + + let connection: TAppConnection; try { if (!("tenantId" in formData)) { @@ -353,7 +328,7 @@ export const OAuthCallbackPage = () => { } if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.AzureDevOps, connectionId, credentials: { @@ -363,11 +338,12 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.AzureDevOps, name, description, method: AzureDevOpsConnectionMethod.OAuth, + projectId, credentials: { code: code as string, tenantId: formData.tenantId as string, @@ -382,14 +358,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -399,11 +381,14 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.GitHub); - const { connectionId, name, description, returnUrl, gatewayId, credentials } = formData; + const { connectionId, name, description, returnUrl, gatewayId, credentials, projectId } = + formData; + + let connection: TAppConnection; try { if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.GitHub, ...(installationId ? { @@ -427,10 +412,11 @@ export const OAuthCallbackPage = () => { }) }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.GitHub, name, description, + projectId, ...(installationId ? { method: GitHubConnectionMethod.App, @@ -460,14 +446,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -477,11 +469,13 @@ export const OAuthCallbackPage = () => { clearState(AppConnection.GitHubRadar); - const { connectionId, name, description, returnUrl } = formData; + const { connectionId, name, description, returnUrl, projectId } = formData; + + let connection: TAppConnection; try { if (connectionId) { - await updateAppConnection.mutateAsync({ + connection = await updateAppConnection.mutateAsync({ app: AppConnection.GitHubRadar, connectionId, credentials: { @@ -490,11 +484,12 @@ export const OAuthCallbackPage = () => { } }); } else { - await createAppConnection.mutateAsync({ + connection = await createAppConnection.mutateAsync({ app: AppConnection.GitHubRadar, name, description, method: GitHubConnectionMethod.App, + projectId, credentials: { code: code as string, installationId: installationId as string @@ -508,14 +503,20 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/app-connections" + to: returnUrl, + params: { + projectId + } }); + return null; } return { connectionId, returnUrl, - appConnectionName: formData.app + appConnectionName: formData.app, + projectId, + connection }; }, []); @@ -530,8 +531,13 @@ export const OAuthCallbackPage = () => { if (!isReady) return; (async () => { - let data: { connectionId?: string; returnUrl?: string; appConnectionName?: string } | null = - null; + let data: { + returnUrl: string; + appConnectionName: string; + connectionId?: string; + projectId?: string; + connection: TAppConnection; + } | null = null; if (appConnection === AppConnection.GitHub) { data = await handleGithub(); @@ -554,16 +560,26 @@ export const OAuthCallbackPage = () => { text: `Successfully ${data.connectionId ? "updated" : "added"} ${data.appConnectionName ? APP_CONNECTION_MAP[data.appConnectionName as AppConnection].name : ""} Connection`, type: "success" }); - } else { - createNotification({ - text: "Failed to add connection", - type: "error" + + await navigate({ + to: data.returnUrl, + params: { + projectId: data.projectId ?? undefined + }, + // scott: if it's not an app connection page we need to pass connection details as it's an inline creation + search: data.returnUrl.includes("app-connections") + ? undefined + : { + connectionId: data.connection.id, + connectionName: data.connection.name, + ...(data.returnUrl.includes("integrations") + ? { + selectedTab: IntegrationsListPageTabs.SecretSyncs + } + : {}) + } }); } - - await navigate({ - to: data?.returnUrl ?? "/organization/app-connections" - }); })(); }, [isReady]); diff --git a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.types.ts b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.types.ts new file mode 100644 index 000000000..6c76a60b3 --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.types.ts @@ -0,0 +1,68 @@ +import { + AzureDevOpsConnectionMethod, + TAzureAppConfigurationConnection, + TAzureClientSecretsConnection, + TAzureDevOpsConnection, + TAzureKeyVaultConnection, + TGitHubConnection, + TGitHubRadarConnection, + TGitLabConnection +} from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +type BaseFormData = { + returnUrl: string; + connectionId?: string; + isUpdate?: boolean; + projectId: string; +}; + +export type GithubFormData = BaseFormData & + Pick; + +export type GithubRadarFormData = BaseFormData & + Pick; + +export type GitLabFormData = BaseFormData & + Pick; + +export type AzureKeyVaultFormData = BaseFormData & + Pick & + Pick; + +export type AzureAppConfigurationFormData = BaseFormData & + Pick & + Pick; + +export type AzureClientSecretsFormData = BaseFormData & + Pick & + Pick; + +type OAuthCredentials = Extract< + TAzureDevOpsConnection, + { method: AzureDevOpsConnectionMethod.OAuth } +>["credentials"]; +type AccessTokenCredentials = Extract< + TAzureDevOpsConnection, + { method: AzureDevOpsConnectionMethod.AccessToken } +>["credentials"]; + +export type AzureDevOpsFormData = BaseFormData & + Pick & + (Pick | Pick); + +export type FormDataMap = { + [AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub }; + [AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar }; + [AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab }; + [AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault }; + [AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & { + app: AppConnection.AzureAppConfiguration; + }; + [AppConnection.AzureClientSecrets]: AzureClientSecretsFormData & { + app: AppConnection.AzureClientSecrets; + }; + [AppConnection.AzureDevOps]: AzureDevOpsFormData & { + app: AppConnection.AzureDevOps; + }; +}; diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index 715f9c0b2..40b2f973f 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -21,7 +21,7 @@ import { SelectItem } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { useGetUserWorkspaces } from "@app/hooks/api"; +import { useGetUserProjects } from "@app/hooks/api"; import { eventToNameMap, secretEvents, @@ -29,7 +29,7 @@ import { } from "@app/hooks/api/auditLogs/constants"; import { EventType } from "@app/hooks/api/auditLogs/enums"; import { UserAgentType } from "@app/hooks/api/auth/types"; -import { Workspace } from "@app/hooks/api/workspace/types"; +import { Project } from "@app/hooks/api/projects/types"; import { LogFilterItem } from "./LogFilterItem"; import { auditLogFilterFormSchema, Presets, TAuditLogFilterFormData } from "./types"; @@ -44,7 +44,7 @@ type Props = { presets?: Presets; setFilter: (data: TAuditLogFilterFormData) => void; filter: TAuditLogFilterFormData; - project?: Workspace; + project?: Project; }; const getActiveFilterCount = (filter: TAuditLogFilterFormData) => { @@ -72,7 +72,7 @@ const getActiveFilterCount = (filter: TAuditLogFilterFormData) => { }; export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { - const { data: workspaces = [] } = useGetUserWorkspaces(); + const { data: workspaces = [] } = useGetUserProjects(); const { currentOrg } = useOrganization(); const workspacesInOrg = workspaces.filter((ws) => ws.orgId === currentOrg?.id); diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx index 5bbb42d32..f1173447c 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsSection.tsx @@ -13,7 +13,7 @@ import { } from "@app/context"; import { Timezone } from "@app/helpers/datetime"; import { withPermission, withProjectPermission } from "@app/hoc"; -import { Workspace } from "@app/hooks/api/workspace/types"; +import { Project } from "@app/hooks/api/projects/types"; import { usePopUp } from "@app/hooks/usePopUp"; import { LogsDateFilter } from "./LogsDateFilter"; @@ -31,7 +31,7 @@ type Props = { refetchInterval?: number; showFilters?: boolean; pageView?: boolean; - project?: Workspace; + project?: Project; }; const LogsSectionComponent = ({ diff --git a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx index 8e0ec9ba9..b1338be24 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/types.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/types.tsx @@ -1,7 +1,7 @@ import { z } from "zod"; import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; export enum AuditLogDateFilterType { Relative = "relative", diff --git a/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx b/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx index 729654d76..a0d440872 100644 --- a/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx +++ b/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx @@ -4,11 +4,11 @@ import { faArrowUpRightFromSquare, faBookOpen, faCopy, + faDoorClosed, faEdit, faEllipsisV, faInfoCircle, faMagnifyingGlass, - faPlug, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; @@ -243,7 +243,7 @@ export const GatewayListPage = withPermission( ? "No Gateways match search..." : "No Gateways have been configured" } - icon={gateways?.length ? faSearch : faPlug} + icon={gateways?.length ? faSearch : faDoorClosed} /> )} ) => { const { currentOrg } = useOrganization(); - const { data: workspaces = [] } = useGetUserWorkspaces(); + const { data: workspaces = [] } = useGetUserProjects(); const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace(); const { @@ -77,7 +77,7 @@ const Content = ({ identityId, handlePopUpToggle }: Omit) => { const onFormSubmit = async ({ project: selectedProject, role }: FormData) => { try { await addIdentityToWorkspace({ - workspaceId: selectedProject.id, + projectId: selectedProject.id, identityId, role: role.slug || undefined }); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectRow.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectRow.tsx index 5b0aac0b7..1622962ea 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectRow.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectRow.tsx @@ -8,7 +8,7 @@ import { createNotification } from "@app/components/notifications"; import { IconButton, Td, Tooltip, Tr } from "@app/components/v2"; import { getProjectBaseURL } from "@app/helpers/project"; import { formatProjectRoleName } from "@app/helpers/roles"; -import { useGetUserWorkspaces } from "@app/hooks/api"; +import { useGetUserProjects } from "@app/hooks/api"; import { IdentityMembership } from "@app/hooks/api/identities/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -31,7 +31,7 @@ export const IdentityProjectRow = ({ membership: { id, createdAt, identity, project, roles }, handlePopUpOpen }: Props) => { - const { data: workspaces } = useGetUserWorkspaces(); + const { data: workspaces } = useGetUserProjects(); const navigate = useNavigate(); const isAccessible = useMemo(() => { diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx index 0ea97c4d3..321a84ccb 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx @@ -25,7 +25,7 @@ export const IdentityProjectsSection = ({ identityId }: Props) => { try { await deleteMutateAsync({ identityId: id, - workspaceId: projectId + projectId }); createNotification({ diff --git a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx index 7b066a099..f7d20e2b7 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import { useForm } from "react-hook-form"; import { faArrowDownAZ, faBorderAll, @@ -16,6 +15,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; +import { RequestProjectAccessModal } from "@app/components/projects/RequestProjectAccessModal"; import { Badge, Button, @@ -24,12 +24,9 @@ import { DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, - FormControl, IconButton, Input, Lottie, - Modal, - ModalContent, Pagination, Skeleton, Tooltip @@ -43,12 +40,8 @@ import { setUserTablePreference } from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; -import { - useOrgAdminAccessProject, - useRequestProjectAccess, - useSearchProjects -} from "@app/hooks/api"; -import { ProjectType, Workspace, WorkspaceEnv } from "@app/hooks/api/workspace/types"; +import { useOrgAdminAccessProject, useSearchProjects } from "@app/hooks/api"; +import { Project, ProjectEnv, ProjectType } from "@app/hooks/api/projects/types"; import { ProjectListToggle, ProjectListView @@ -62,53 +55,6 @@ type Props = { onProjectListViewChange: (value: ProjectListView) => void; }; -type RequestAccessModalProps = { - projectId: string; - onPopUpToggle: () => void; -}; - -const RequestAccessModal = ({ projectId, onPopUpToggle }: RequestAccessModalProps) => { - const form = useForm<{ note: string }>(); - - const requestProjectAccess = useRequestProjectAccess(); - - const onFormSubmit = ({ note }: { note: string }) => { - if (requestProjectAccess.isPending) return; - requestProjectAccess.mutate( - { - comment: note, - projectId - }, - { - onSuccess: () => { - createNotification({ - type: "success", - title: "Project Access Request Sent", - text: "Project admins will receive an email of your request" - }); - onPopUpToggle(); - } - } - ); - }; - - return ( -
- - - -
- - -
-
- ); -}; - export const AllProjectView = ({ onAddNewProject, onUpgradePlan, @@ -155,7 +101,7 @@ export const AllProjectView = ({ const handleAccessProject = async ( type: ProjectType, projectId: string, - environments: WorkspaceEnv[] + environments: ProjectEnv[] ) => { try { await orgAdminAccessProject.mutateAsync({ @@ -180,7 +126,7 @@ export const AllProjectView = ({ offset, totalCount: searchedProjects?.totalCount || 0 }); - const requestedWorkspaceDetails = (popUp.requestAccessConfirmation.data || {}) as Workspace; + const requestedWorkspaceDetails = (popUp.requestAccessConfirmation.data || {}) as Project; const handleToggleFilterByProjectType = (el: ProjectType) => setProjectTypeFilter((state) => (state === el ? undefined : el)); @@ -278,22 +224,26 @@ export const AllProjectView = ({
- {(isAllowed) => ( - + {(isOldProjectPermissionAllowed) => ( + + {(isAllowed) => ( + + )} + )}
@@ -419,20 +369,11 @@ export const AllProjectView = ({
No Projects Found
)} - handlePopUpToggle("requestAccessConfirmation", isOpen)} - > - - handlePopUpToggle("requestAccessConfirmation")} - projectId={requestedWorkspaceDetails?.id} - /> - - + project={requestedWorkspaceDetails} + /> ); }; diff --git a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx index d63099273..46f520d41 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx @@ -39,11 +39,11 @@ import { setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; -import { useGetUserWorkspaces } from "@app/hooks/api"; +import { useGetUserProjects } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { Project, ProjectType } from "@app/hooks/api/projects/types"; import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation"; import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries"; -import { ProjectType, Workspace } from "@app/hooks/api/workspace/types"; import { ProjectListToggle, ProjectListView @@ -79,7 +79,7 @@ export const MyProjectView = ({ {} ); - const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces(); + const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserProjects(); const { setPage, perPage, @@ -136,7 +136,7 @@ export const MyProjectView = ({ const { workspacesWithFaveProp } = useMemo(() => { const workspacesWithFav = filteredWorkspaces - .map((w): Workspace & { isFavorite: boolean } => ({ + .map((w): Project & { isFavorite: boolean } => ({ ...w, isFavorite: Boolean(projectFavorites?.includes(w.id)) })) @@ -188,7 +188,7 @@ export const MyProjectView = ({ } }; - const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => ( + const renderProjectGridItem = (workspace: Project, isFavorite: boolean) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
{ @@ -242,7 +242,7 @@ export const MyProjectView = ({

); - const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => ( + const renderProjectListItem = (workspace: Project, isFavorite: boolean, index: number) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
{ @@ -480,22 +480,26 @@ export const MyProjectView = ({
- {(isAllowed) => ( - + {(isOldProjectV1Allowed) => ( + + {(isAllowed) => ( + + )} + )} diff --git a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts index 03b86f2ab..9c374d398 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts @@ -122,12 +122,11 @@ export const formSchema = z.object({ .refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }), permissions: z .object({ - workspace: z + project: z .object({ create: z.boolean().optional() }) .optional(), - "audit-logs": auditLogsPermissionSchema, member: generalPermissionSchema, groups: groupPermissionSchema, @@ -162,7 +161,11 @@ export const rolePermission2Form = (permissions: TPermission[] = []) => { // i would have to write a if loop with both conditions same const formVal: Record = {}; permissions.forEach((permission) => { - const { subject, action } = permission; + const { action } = permission; + let { subject } = permission; + if (subject === OrgPermissionSubjects.Workspace) { + subject = OrgPermissionSubjects.Project; + } if (!formVal?.[subject]) formVal[subject] = {}; formVal[subject][action] = true; }); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgRoleWorkspaceRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgRoleWorkspaceRow.tsx index 2746e10bc..5f9041a29 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgRoleWorkspaceRow.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgRoleWorkspaceRow.tsx @@ -28,7 +28,7 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) => const rule = useWatch({ control, - name: "permissions.workspace" + name: "permissions.project" }); const selectedPermissionCategory = useMemo(() => { @@ -60,7 +60,7 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) => setIsCustom.off(); if (val === Permission.NoAccess) { - setValue("permissions.workspace", { create: false }, { shouldDirty: true }); + setValue("permissions.project", { create: false }, { shouldDirty: true }); } }; @@ -95,8 +95,8 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) => {PERMISSION_ACTIONS.map(({ action, label }) => { return ( ( , - | "workspace" + | "project" | "organization-admin-console" | "kmip" | "gateway" diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx index 6ca1a5c28..8c6d511b1 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx @@ -1,9 +1,8 @@ import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context"; +import { useOrgPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; @@ -15,52 +14,45 @@ export const ExternalMigrationsTab = () => { const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["selectImportPlatform"] as const); return ( - -
-
-
-

Import from external source

+
+
+
+

Import from external source

- + - -
-

Import data from another platform to Infisical.

- handlePopUpToggle("selectImportPlatform", state)} - /> +
- +

Import data from another platform to Infisical.

+ + handlePopUpToggle("selectImportPlatform", state)} + /> +
); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx index c3ecdfa79..08d350c9c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx @@ -7,8 +7,8 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { getProjectTitle } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates"; -import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal"; import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm"; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx index 3c5e138e3..ad4f2776a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx @@ -15,12 +15,12 @@ import { TextArea } from "@app/components/v2"; import { getProjectLottieIcon } from "@app/helpers/project"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { TProjectTemplate, useCreateProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates"; -import { ProjectType } from "@app/hooks/api/workspace/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx index bd19db761..572be1d81 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx @@ -9,9 +9,9 @@ import { useOrganization } from "@app/context"; import { useAddUserToWsNonE2EE, useGetOrgMembershipProjectMemberships, - useGetUserWorkspaces + useGetUserProjects } from "@app/hooks/api"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; +import { ProjectVersion } from "@app/hooks/api/projects/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z @@ -34,7 +34,7 @@ type Props = { const UserAddToProjectModalChild = ({ membershipId, popUp, handlePopUpToggle }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; - const { data: workspaces = [] } = useGetUserWorkspaces(); + const { data: workspaces = [] } = useGetUserProjects(); const { mutateAsync: addUserToWorkspaceNonE2EE } = useAddUserToWsNonE2EE(); diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectRow.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectRow.tsx index aac501fce..a7a88a72c 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectRow.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectRow.tsx @@ -7,7 +7,7 @@ import { createNotification } from "@app/components/notifications"; import { IconButton, Td, Tooltip, Tr } from "@app/components/v2"; import { getProjectBaseURL } from "@app/helpers/project"; import { formatProjectRoleName } from "@app/helpers/roles"; -import { useGetUserWorkspaces } from "@app/hooks/api"; +import { useGetUserProjects } from "@app/hooks/api"; import { TWorkspaceUser } from "@app/hooks/api/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { OrgAccessControlTabSections } from "@app/types/org"; @@ -24,7 +24,7 @@ export const UserProjectRow = ({ membership: { id, project, user, roles }, handlePopUpOpen }: Props) => { - const { data: workspaces = [] } = useGetUserWorkspaces(); + const { data: workspaces = [] } = useGetUserProjects(); const navigate = useNavigate(); const isAccessible = useMemo(() => { diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx index c93e1fa8b..e06ed390d 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx @@ -32,7 +32,7 @@ export const UserProjectsSection = ({ membershipId }: Props) => { const handleRemoveUser = async (projectId: string, username: string) => { try { - await removeUserFromWorkspace({ workspaceId: projectId, usernames: [username], orgId }); + await removeUserFromWorkspace({ projectId, usernames: [username], orgId }); createNotification({ text: "Successfully removed user from project", type: "success" diff --git a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx index 8bb007e88..9da8770d2 100644 --- a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx +++ b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx @@ -3,9 +3,9 @@ import { useTranslation } from "react-i18next"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectAccessControlTabs } from "@app/types/project"; import { @@ -18,7 +18,7 @@ import { const Page = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const selectedTab = useSearch({ strict: false, select: (el) => el.selectedTab @@ -26,15 +26,15 @@ const Page = () => { const updateSelectedTab = (tab: string) => { navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const, + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, search: (prev) => ({ ...prev, selectedTab: tab }), params: { - projectId: currentWorkspace.id + projectId: currentProject.id } }); }; - const isSecretManager = currentWorkspace.type === ProjectType.SecretManager; + const isSecretManager = currentProject.type === ProjectType.SecretManager; return (
diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx index f909f92b4..a279bd1b0 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Modal, ModalContent } from "@app/components/v2"; -import { useOrganization, useWorkspace } from "@app/context"; +import { useOrganization, useProject } from "@app/context"; import { useAddGroupToWorkspace, useGetOrganizationGroups, @@ -31,14 +31,14 @@ type Props = { const Content = ({ popUp, handlePopUpToggle }: Props) => { const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const orgId = currentOrg?.id || ""; const { data: groups } = useGetOrganizationGroups(orgId); - const { data: groupMemberships } = useListWorkspaceGroups(currentWorkspace?.id || ""); + const { data: groupMemberships } = useListWorkspaceGroups(currentProject?.id || ""); - const { data: roles } = useGetProjectRoles(currentWorkspace?.id || ""); + const { data: roles } = useGetProjectRoles(currentProject?.id || ""); const { mutateAsync: addGroupToWorkspaceMutateAsync } = useAddGroupToWorkspace(); @@ -64,7 +64,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { const onFormSubmit = async ({ group, role }: FormData) => { try { await addGroupToWorkspaceMutateAsync({ - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", groupId: group.id, role: role.slug || undefined }); diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx index 91b6d3186..49ff350fd 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx @@ -24,13 +24,13 @@ import { Tag, Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; import { usePopUp } from "@app/hooks"; import { useGetProjectRoles, useUpdateGroupWorkspaceRole } from "@app/hooks/api"; import { TGroupMembership } from "@app/hooks/api/groups/types"; +import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/projects/types"; import { TProjectRole } from "@app/hooks/api/roles/types"; -import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types"; import { groupBy } from "@app/lib/fn/array"; const temporaryRoleFormSchema = z.object({ @@ -213,7 +213,7 @@ type FormProps = { }; const GroupRolesForm = ({ projectRoles, roles, groupId, onClose }: FormProps) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [searchRoles, setSearchRoles] = useState(""); @@ -255,7 +255,7 @@ const GroupRolesForm = ({ projectRoles, roles, groupId, onClose }: FormProps) => try { await updateGroupWorkspaceRole.mutateAsync({ - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", groupId, roles: selectedRoles }); @@ -373,11 +373,11 @@ export const GroupRoles = ({ className, popperContentProps }: TMemberRolesProp) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { popUp, handlePopUpToggle } = usePopUp(["editRole"] as const); const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles( - currentWorkspace?.id ?? "" + currentProject?.id ?? "" ); return ( diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx index 5ba6082e1..ee23a5778 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx @@ -8,8 +8,8 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteGroupFromWorkspace } from "@app/hooks/api"; @@ -19,7 +19,7 @@ import { GroupTable } from "./GroupsTable"; export const GroupsSection = () => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace(); @@ -44,7 +44,7 @@ export const GroupsSection = () => { try { await deleteMutateAsync({ groupId, - projectId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); createNotification({ diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx index 9bd98084e..3ce5b4062 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx @@ -32,7 +32,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { getUserTablePreference, @@ -61,7 +61,7 @@ enum GroupsOrderBy { } export const GroupTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const { @@ -85,7 +85,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { }; const { data: groupMemberships = [], isPending } = useListWorkspaceGroups( - currentWorkspace?.id || "" + currentProject?.id || "" ); const filteredGroupMemberships = useMemo(() => { @@ -159,9 +159,9 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { onKeyDown={(evt) => { if (evt.key === "Enter") { navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/groups/$groupId` as const, + to: `${getProjectBaseURL(currentProject.type)}/groups/$groupId` as const, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, groupId: id } }); @@ -169,9 +169,9 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { }} onClick={() => navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/groups/$groupId` as const, + to: `${getProjectBaseURL(currentProject.type)}/groups/$groupId` as const, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, groupId: id } }) diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index 5b6e773db..7fbe99934 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -45,7 +45,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { formatProjectRoleName } from "@app/helpers/roles"; import { @@ -57,7 +57,7 @@ import { withProjectPermission } from "@app/hoc"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; -import { ProjectIdentityOrderBy } from "@app/hooks/api/workspace/types"; +import { ProjectIdentityOrderBy } from "@app/hooks/api/projects/types"; import { usePopUp } from "@app/hooks/usePopUp"; import { IdentityModal } from "./components/IdentityModal"; @@ -66,7 +66,7 @@ const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2; export const IdentityTab = withProjectPermission( () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject, projectId } = useProject(); const navigate = useNavigate(); const { @@ -92,11 +92,9 @@ export const IdentityTab = withProjectPermission( setUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, newPerPage); }; - const workspaceId = currentWorkspace?.id ?? ""; - const { data, isPending, isFetching } = useGetWorkspaceIdentityMemberships( { - workspaceId: currentWorkspace?.id || "", + projectId, offset, limit, orderDirection, @@ -126,7 +124,7 @@ export const IdentityTab = withProjectPermission( try { await deleteMutateAsync({ identityId, - workspaceId + projectId }); createNotification({ @@ -261,9 +259,9 @@ export const IdentityTab = withProjectPermission( onKeyDown={(evt) => { if (evt.key === "Enter") { navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/identities/$identityId` as const, + to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, identityId: id } }); @@ -271,9 +269,9 @@ export const IdentityTab = withProjectPermission( }} onClick={() => navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/identities/$identityId` as const, + to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, identityId: id } }) diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx index 1baf80b9b..7261ba839 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx @@ -14,7 +14,7 @@ import { ModalContent, Spinner } from "@app/components/v2"; -import { useOrganization, useWorkspace } from "@app/context"; +import { useOrganization, useProject } from "@app/context"; import { useAddIdentityToWorkspace, useGetIdentityMembershipOrgs, @@ -37,10 +37,9 @@ type Props = { const Content = ({ popUp, handlePopUpToggle }: Props) => { const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); + const { projectId } = useProject(); const organizationId = currentOrg?.id || ""; - const workspaceId = currentWorkspace?.id || ""; const { data: identityMembershipOrgsData, isPending: isMembershipsLoading } = useGetIdentityMembershipOrgs({ @@ -49,12 +48,12 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { }); const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships; const { data: identityMembershipsData } = useGetWorkspaceIdentityMemberships({ - workspaceId, + projectId, limit: 20000 // TODO: this is temp to preserve functionality for larger projects, will optimize in PR referenced above }); const identityMemberships = identityMembershipsData?.identityMemberships; - const { data: roles, isPending: isRolesLoading } = useGetProjectRoles(workspaceId); + const { data: roles, isPending: isRolesLoading } = useGetProjectRoles(projectId); const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace(); @@ -80,7 +79,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { const onFormSubmit = async ({ identity, role }: FormData) => { try { await addIdentityToWorkspaceMutateAsync({ - workspaceId, + projectId, identityId: identity.id, role: role.slug || undefined }); diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index d9c9a8718..cdf49cf12 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -22,7 +22,7 @@ import { OrgPermissionSubjects, useOrganization, useOrgPermission, - useWorkspace + useProject } from "@app/context"; import { useAddUsersToOrg, @@ -30,8 +30,8 @@ import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; +import { ProjectVersion } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; const addMemberFormSchema = z.object({ @@ -57,7 +57,7 @@ type Props = { export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { const { t } = useTranslation(); const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate({ from: "" }); const { permission } = useOrgPermission(); const requesterEmail = useSearch({ @@ -66,12 +66,12 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { }); const orgId = currentOrg?.id || ""; - const workspaceId = currentWorkspace?.id || ""; + const projectId = currentProject?.id || ""; - const { data: members } = useGetWorkspaceUsers(workspaceId); + const { data: members } = useGetWorkspaceUsers(projectId); const { data: orgUsers } = useGetOrgUsers(orgId); - const { data: roles } = useGetProjectRoles(currentWorkspace?.id || ""); + const { data: roles } = useGetProjectRoles(currentProject?.id || ""); const { control, @@ -94,7 +94,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { }, [requesterEmail]); const onAddMembers = async ({ orgMemberships, projectRoleSlugs }: TAddMemberForm) => { - if (!currentWorkspace) return; + if (!currentProject) return; if (!currentOrg?.id) return; const existingMembers = orgMemberships.filter((membership) => !membership.isNewInvitee); @@ -109,7 +109,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { if (!selectedMembers) return; try { - if (currentWorkspace.version === ProjectVersion.V1) { + if (currentProject.version === ProjectVersion.V1) { createNotification({ type: "error", text: "Please upgrade your project to invite new members to the project." @@ -146,8 +146,8 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { organizationRoleSlug: ProjectMembershipRole.Member, // only applies to new invites projects: [ { - slug: currentWorkspace.slug, - id: currentWorkspace.id, + slug: currentProject.slug, + id: currentProject.id, projectRoleSlug: projectRoleSlugs.map((role) => role.slug) } ] diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx index d18f8715a..291a5b58b 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx @@ -29,14 +29,14 @@ import { ProjectPermissionActions, ProjectPermissionMemberActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { useGetProjectRoles, useUpdateUserWorkspaceRole } from "@app/hooks/api"; +import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { TWorkspaceUser } from "@app/hooks/api/types"; -import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types"; const roleFormSchema = z.object({ roles: z @@ -64,9 +64,8 @@ type Props = { }; export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props) => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; - const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(workspaceId); + const { projectId } = useProject(); + const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(projectId); const { permission } = useProjectPermission(); const isMemberEditDisabled = permission.cannot( ProjectPermissionMemberActions.Edit, @@ -130,7 +129,7 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props) try { await updateMembershipRole.mutateAsync({ - workspaceId, + projectId, membershipId: projectMember.id, roles: sanitizedRoles }); diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRoleForm.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRoleForm.tsx index 735257293..187e1813a 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRoleForm.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRoleForm.tsx @@ -1,7 +1,7 @@ import { Link } from "@tanstack/react-router"; import { Alert, AlertDescription } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { TWorkspaceUser } from "@app/hooks/api/types"; @@ -12,7 +12,7 @@ type Props = { onOpenUpgradeModal: (title: string) => void; }; export const MemberRoleForm = ({ projectMember, onOpenUpgradeModal }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); return (
@@ -22,9 +22,9 @@ export const MemberRoleForm = ({ projectMember, onOpenUpgradeModal }: Props) => > diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx index 5dd439744..3e93ec392 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx @@ -43,8 +43,8 @@ import { ProjectPermissionActions, ProjectPermissionMemberActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { removeTrailingSlash } from "@app/helpers/string"; import { usePopUp } from "@app/hooks"; @@ -89,7 +89,7 @@ export const SpecificPrivilegeSecretForm = ({ secretPath?: string; onClose?: () => void; }) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ "deletePrivilege", @@ -129,7 +129,7 @@ export const SpecificPrivilegeSecretForm = ({ temporaryAccess: privilege } : { - environmentSlug: currentWorkspace.environments?.[0]?.slug, + environmentSlug: currentProject.environments?.[0]?.slug, secretPath: initialSecretPath, read: selectedActions.includes(ProjectPermissionActions.Read), edit: selectedActions.includes(ProjectPermissionActions.Edit), @@ -202,7 +202,7 @@ export const SpecificPrivilegeSecretForm = ({ // This is used for requesting access additional privileges, not directly creating a privilege! const handleRequestAccess = async (data: TSecretPermissionForm) => { if (!policies) return; - if (!currentWorkspace) { + if (!currentProject) { createNotification({ type: "error", text: "No workspace found.", @@ -253,7 +253,7 @@ export const SpecificPrivilegeSecretForm = ({ ...(data.temporaryAccess.isTemporary && { temporaryRange: data.temporaryAccess.temporaryRange }), - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, isTemporary: data.temporaryAccess.isTemporary, permissions: actions .filter(({ allowed }) => allowed) @@ -307,7 +307,7 @@ export const SpecificPrivilegeSecretForm = ({ position="popper" dropdownContainerClassName="max-w-none" > - {currentWorkspace?.environments?.map(({ slug, id, name }) => ( + {currentProject?.environments?.map(({ slug, id, name }) => ( {name} diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx index a8ecde73a..da5616453 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx @@ -9,7 +9,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useOrganization, - useWorkspace + useProject } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteUserFromWorkspace } from "@app/hooks/api"; @@ -19,7 +19,7 @@ import { MembersTable } from "./MembersTable"; export const MembersSection = () => { const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: removeUserFromWorkspace } = useDeleteUserFromWorkspace(); @@ -32,11 +32,11 @@ export const MembersSection = () => { const handleRemoveUser = async () => { const username = (popUp?.removeMember?.data as { username: string })?.username; if (!currentOrg?.id) return; - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; try { await removeUserFromWorkspace({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, usernames: [username], orgId: currentOrg.id }); diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx index 2cbe144ee..22eb50460 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx @@ -44,12 +44,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { - ProjectPermissionActions, - ProjectPermissionSub, - useUser, - useWorkspace -} from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject, useUser } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { formatProjectRoleName } from "@app/helpers/roles"; import { @@ -81,7 +76,7 @@ type Filter = { }; export const MembersTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { user } = useUser(); const navigate = useNavigate(); const [filter, setFilter] = useState({ @@ -90,8 +85,8 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { const filterRoles = useMemo(() => filter.roles, [filter.roles]); const userId = user?.id || ""; - const workspaceId = currentWorkspace?.id || ""; - const { data: projectRoles } = useGetProjectRoles(workspaceId); + const projectId = currentProject?.id || ""; + const { data: projectRoles } = useGetProjectRoles(projectId); const { search, @@ -116,7 +111,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { }; const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers( - workspaceId, + projectId, undefined, filterRoles ); @@ -312,9 +307,9 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { onKeyDown={(evt) => { if (evt.key === "Enter") { navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/members/$membershipId`, + to: `${getProjectBaseURL(currentProject.type)}/members/$membershipId`, params: { - projectId: workspaceId, + projectId, membershipId } }); @@ -322,9 +317,9 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { }} onClick={() => navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/members/$membershipId`, + to: `${getProjectBaseURL(currentProject.type)}/members/$membershipId`, params: { - projectId: workspaceId, + projectId, membershipId } }) diff --git a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx index 211f1d0e8..e7b3b10b8 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx @@ -39,7 +39,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { isCustomProjectRole } from "@app/helpers/roles"; import { @@ -67,8 +67,8 @@ export const ProjectRoleList = () => { "deleteRole", "duplicateRole" ] as const); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: roles, isPending: isRolesLoading } = useGetProjectRoles(projectId); @@ -250,9 +250,9 @@ export const ProjectRoleList = () => { className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700" onClick={() => navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug`, + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug`, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, roleSlug: slug } }) @@ -292,9 +292,9 @@ export const ProjectRoleList = () => { onClick={(e) => { e.stopPropagation(); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug`, + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug`, params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, roleSlug: slug } }); diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx index 8264d47bf..d63b6bb9c 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx @@ -22,7 +22,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useToggle } from "@app/hooks"; import { useCreateServiceToken } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -70,7 +70,7 @@ type Props = { const ServiceTokenForm = () => { const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, @@ -81,7 +81,7 @@ const ServiceTokenForm = () => { scopes: [ { secretPath: "/", - environment: currentWorkspace?.environments?.[0]?.slug + environment: currentProject?.environments?.[0]?.slug } ] } @@ -111,7 +111,7 @@ const ServiceTokenForm = () => { const onFormSubmit = async ({ name, scopes, expiresIn, permissions }: FormData) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; const randomBytes = crypto.randomBytes(16).toString("hex"); @@ -122,7 +122,7 @@ const ServiceTokenForm = () => { scopes, expiresIn: Number(expiresIn), name, - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, randomBytes, permissions: Object.entries(permissions) .filter(([, permissionsValue]) => permissionsValue) @@ -172,7 +172,7 @@ const ServiceTokenForm = () => { ( { onValueChange={(e) => onChange(e)} className="w-full" > - {currentWorkspace?.environments.map(({ name, slug }) => ( + {currentProject?.environments.map(({ name, slug }) => ( {name} @@ -225,7 +225,7 @@ const ServiceTokenForm = () => { variant="outline_bg" onClick={() => append({ - environment: currentWorkspace?.environments?.[0]?.slug || "", + environment: currentProject?.environments?.[0]?.slug || "", secretPath: "" }) } @@ -334,7 +334,7 @@ const ServiceTokenForm = () => { export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => { const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); return ( { { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data, isPending } = useGetUserWsServiceTokens({ - workspaceID: currentWorkspace?.id || "" + workspaceID: currentProject?.id || "" }); const { diff --git a/frontend/src/pages/project/AppConnectionsPage/AppConnectionsPage.tsx b/frontend/src/pages/project/AppConnectionsPage/AppConnectionsPage.tsx new file mode 100644 index 000000000..84fb69b83 --- /dev/null +++ b/frontend/src/pages/project/AppConnectionsPage/AppConnectionsPage.tsx @@ -0,0 +1,41 @@ +import { Helmet } from "react-helmet"; + +import { PageHeader } from "@app/components/v2"; +import { useProject } from "@app/context"; +import { + ProjectPermissionAppConnectionActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; +import { withProjectPermission } from "@app/hoc"; +import { AppConnectionsTable } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; + +export const AppConnectionsPage = withProjectPermission( + () => { + const { currentProject } = useProject(); + + return ( +
+ + Infisical | App Connections + + + +
+
+ + + +
+
+
+ ); + }, + { + action: ProjectPermissionAppConnectionActions.Read, + subject: ProjectPermissionSub.AppConnections + } +); diff --git a/frontend/src/pages/project/AppConnectionsPage/route-cert-manager.tsx b/frontend/src/pages/project/AppConnectionsPage/route-cert-manager.tsx new file mode 100644 index 000000000..8be255e73 --- /dev/null +++ b/frontend/src/pages/project/AppConnectionsPage/route-cert-manager.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AppConnectionsPage } from "./AppConnectionsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections" +)({ + component: AppConnectionsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "App Connections" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AppConnectionsPage/route-secret-manager.tsx b/frontend/src/pages/project/AppConnectionsPage/route-secret-manager.tsx new file mode 100644 index 000000000..2767c8689 --- /dev/null +++ b/frontend/src/pages/project/AppConnectionsPage/route-secret-manager.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AppConnectionsPage } from "./AppConnectionsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections" +)({ + component: AppConnectionsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "App Connections" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AppConnectionsPage/route-secret-scanning.tsx b/frontend/src/pages/project/AppConnectionsPage/route-secret-scanning.tsx new file mode 100644 index 000000000..724926495 --- /dev/null +++ b/frontend/src/pages/project/AppConnectionsPage/route-secret-scanning.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AppConnectionsPage } from "./AppConnectionsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections" +)({ + component: AppConnectionsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "App Connections" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx index 0c83b0520..1e9b1ad54 100644 --- a/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx +++ b/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx @@ -1,11 +1,11 @@ import { Helmet } from "react-helmet"; import { PageHeader } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { LogsSection } from "@app/pages/organization/AuditLogsPage/components"; export const AuditLogsPage = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); return (
@@ -19,7 +19,7 @@ export const AuditLogsPage = () => { title="Audit logs" description="Audit logs for security and compliance teams to monitor information access." /> - +
diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx index a3dfabcd0..d6bda7ea1 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -4,8 +4,8 @@ import { useParams } from "@tanstack/react-router"; import { ProjectPermissionCan } from "@app/components/permissions"; import { EmptyState, PageHeader, Spinner } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/workspace/queries"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; +import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/projects/queries"; import { GroupDetailsSection } from "./components/GroupDetailsSection"; import { GroupMembersSection } from "./components/GroupMembersSection"; @@ -16,10 +16,10 @@ const Page = () => { select: (el) => el.groupId as string }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: groupMembership, isPending } = useGetWorkspaceGroupMembershipDetails( - currentWorkspace.id, + currentProject.id, groupId ); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx index 90c327067..0689d674a 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx @@ -14,7 +14,7 @@ import { IconButton } from "@app/components/v2"; import { CopyButton } from "@app/components/v2/CopyButton"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; import { useDeleteGroupFromWorkspace } from "@app/hooks/api"; @@ -31,14 +31,14 @@ export const GroupDetailsSection = ({ groupMembership }: Props) => { ] as const); const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const onRemoveGroupSubmit = async () => { try { await deleteMutateAsync({ groupId: groupMembership.group.id, - projectId: currentWorkspace.id + projectId: currentProject.id }); createNotification({ @@ -47,9 +47,9 @@ export const GroupDetailsSection = ({ groupMembership }: Props) => { }); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/access-management`, + to: `${getProjectBaseURL(currentProject.type)}/access-management`, params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: "groups" diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 7d155ba43..1df989602 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -23,7 +23,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; import { getUserTablePreference, @@ -69,12 +69,12 @@ export const GroupMembersTable = ({ groupMembership }: Props) => { setUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, newPerPage); }; - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: groupMemberships, isPending } = useListProjectGroupUsers({ id: groupMembership.group.id, groupSlug: groupMembership.group.slug, - projectId: currentWorkspace.id, + projectId: currentProject.id, offset, limit: perPage, search, @@ -127,7 +127,7 @@ export const GroupMembersTable = ({ groupMembership }: Props) => { { actorId: userId, actorType: ActorType.USER, - projectId: currentWorkspace.id + projectId: currentProject.id }, { onSuccess: () => { @@ -136,8 +136,8 @@ export const GroupMembersTable = ({ groupMembership }: Props) => { text: "User privilege assumption has started" }); - const url = getProjectHomePage(currentWorkspace.type, currentWorkspace.environments); - window.location.href = url.replace("$projectId", currentWorkspace.id); + const url = getProjectHomePage(currentProject.type, currentProject.environments); + window.location.href = url.replace("$projectId", currentProject.id); } } ); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index e1da4edab..993320e56 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -18,7 +18,7 @@ import { ProjectPermissionActions, ProjectPermissionIdentityActions, ProjectPermissionSub, - useWorkspace + useProject } from "@app/context"; import { getProjectBaseURL, getProjectHomePage } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; @@ -38,12 +38,10 @@ const Page = () => { strict: false, select: (el) => el.identityId as string }); - const { currentWorkspace } = useWorkspace(); - - const workspaceId = currentWorkspace?.id || ""; + const { currentProject, projectId } = useProject(); const { data: identityMembershipDetails, isPending: isMembershipDetailsLoading } = - useGetWorkspaceIdentityMembershipDetails(workspaceId, identityId); + useGetWorkspaceIdentityMembershipDetails(projectId, identityId); const { mutateAsync: deleteMutateAsync, isPending: isDeletingIdentity } = useDeleteIdentityFromWorkspace(); @@ -59,7 +57,7 @@ const Page = () => { { actorId: identityId, actorType: ActorType.IDENTITY, - projectId: workspaceId + projectId }, { onSuccess: () => { @@ -67,8 +65,8 @@ const Page = () => { type: "success", text: "Identity privilege assumption has started" }); - const url = getProjectHomePage(currentWorkspace.type, currentWorkspace.environments); - window.location.href = url.replace("$projectId", currentWorkspace.id); + const url = getProjectHomePage(currentProject.type, currentProject.environments); + window.location.href = url.replace("$projectId", currentProject.id); } } ); @@ -78,7 +76,7 @@ const Page = () => { try { await deleteMutateAsync({ identityId, - workspaceId + projectId }); createNotification({ text: "Successfully removed identity from project", @@ -86,9 +84,9 @@ const Page = () => { }); handlePopUpClose("deleteIdentity"); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const, + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, params: { - projectId: workspaceId + projectId }, search: { selectedTab: "identities" diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx index fe574a32d..9b4be2cb8 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx @@ -24,8 +24,8 @@ import { import { ProjectPermissionIdentityActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useCreateIdentityProjectAdditionalPrivilege, @@ -78,8 +78,8 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ isDisabled }: Props) => { const isCreate = !privilegeId; - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: privilegeDetails, isPending } = useGetIdentityProjectPrivilegeDetails({ identityId, projectId, @@ -225,7 +225,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ > Save - +
diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx index 1fa98628e..eb8653acb 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx @@ -23,7 +23,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; import { usePopUp } from "@app/hooks"; import { useUpdateIdentityWorkspaceRole } from "@app/hooks/api"; @@ -41,7 +41,7 @@ export const IdentityRoleDetailsSection = ({ identityMembershipDetails, isMembershipDetailsLoading }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ "deleteRole", "modifyRole" @@ -53,7 +53,7 @@ export const IdentityRoleDetailsSection = ({ try { const updatedRoles = identityMembershipDetails?.roles?.filter((el) => el.id !== id); await updateIdentityWorkspaceRole({ - workspaceId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", identityId: identityMembershipDetails.identity.id, roles: updatedRoles.map( ({ diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx index c5860ec11..d47f4a74e 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx @@ -29,13 +29,13 @@ import { ProjectPermissionActions, ProjectPermissionIdentityActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useGetProjectRoles, useUpdateIdentityWorkspaceRole } from "@app/hooks/api"; import { IdentityMembership } from "@app/hooks/api/identities/types"; +import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types"; const roleFormSchema = z.object({ roles: z @@ -62,9 +62,8 @@ type Props = { }; export const IdentityRoleModify = ({ identityProjectMembership }: Props) => { - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; - const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(workspaceId); + const { projectId } = useProject(); + const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(projectId); const { permission } = useProjectPermission(); const isIdentityEditDisabled = permission.cannot( ProjectPermissionIdentityActions.Edit, @@ -117,7 +116,7 @@ export const IdentityRoleModify = ({ identityProjectMembership }: Props) => { try { await updateIdentityWorkspaceRole.mutateAsync({ - workspaceId, + projectId, identityId: identityProjectMembership.identity.id, roles: sanitizedRoles }); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index 7fadf2cb9..23b8299da 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -19,7 +19,7 @@ import { ProjectPermissionMemberActions, ProjectPermissionSub, useOrganization, - useWorkspace + useProject } from "@app/context"; import { getProjectBaseURL, getProjectHomePage } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; @@ -40,12 +40,10 @@ export const Page = () => { select: (el) => el.membershipId as string }); const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); - - const workspaceId = currentWorkspace?.id || ""; + const { currentProject, projectId } = useProject(); const { data: membershipDetails, isPending: isMembershipDetailsLoading } = - useGetWorkspaceUserDetails(workspaceId, membershipId); + useGetWorkspaceUserDetails(projectId, membershipId); const { mutateAsync: removeUserFromWorkspace, isPending: isRemovingUserFromWorkspace } = useDeleteUserFromWorkspace(); @@ -63,7 +61,7 @@ export const Page = () => { { actorId: userId, actorType: ActorType.USER, - projectId: workspaceId + projectId }, { onSuccess: () => { @@ -72,19 +70,19 @@ export const Page = () => { text: "User privilege assumption has started" }); - const url = getProjectHomePage(currentWorkspace.type, currentWorkspace.environments); - window.location.href = url.replace("$projectId", currentWorkspace.id); + const url = getProjectHomePage(currentProject.type, currentProject.environments); + window.location.href = url.replace("$projectId", currentProject.id); } } ); }; const handleRemoveUser = async () => { - if (!currentOrg?.id || !currentWorkspace?.id || !membershipDetails?.user?.username) return; + if (!currentOrg?.id || !currentProject?.id || !membershipDetails?.user?.username) return; try { await removeUserFromWorkspace({ - workspaceId: currentWorkspace.id, + projectId, usernames: [membershipDetails?.user?.username], orgId: currentOrg.id }); @@ -93,9 +91,9 @@ export const Page = () => { type: "success" }); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const, + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, params: { - projectId: currentWorkspace.id + projectId: currentProject.id } }); } catch (error) { diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx index 9817aefdb..6fa54247f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx @@ -23,8 +23,8 @@ import { import { ProjectPermissionMemberActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useCreateProjectUserAdditionalPrivilege, @@ -77,8 +77,8 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ isDisabled }: Props) => { const isCreate = !privilegeId; - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: privilegeDetails, isPending } = useGetProjectUserPrivilegeDetails( privilegeId || "" ); @@ -221,7 +221,7 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ > Save - + diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx index 5ff78360d..012141c03 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx @@ -22,12 +22,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { - ProjectPermissionActions, - ProjectPermissionSub, - useUser, - useWorkspace -} from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject, useUser } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; import { usePopUp } from "@app/hooks"; import { useUpdateUserWorkspaceRole } from "@app/hooks/api"; @@ -49,7 +44,7 @@ export const MemberRoleDetailsSection = ({ }: Props) => { const { user } = useUser(); const userId = user?.id; - const { currentWorkspace } = useWorkspace(); + const { projectId } = useProject(); const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ "deleteRole", "modifyRole" @@ -63,7 +58,7 @@ export const MemberRoleDetailsSection = ({ try { const updatedRoles = membershipDetails?.roles?.filter((el) => el.id !== id); await updateUserWorkspaceRole({ - workspaceId: currentWorkspace?.id || "", + projectId, roles: updatedRoles.map( ({ role, diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx index 739b22b7d..e7a05f70f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx @@ -29,14 +29,14 @@ import { ProjectPermissionActions, ProjectPermissionMemberActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { useGetProjectRoles, useUpdateUserWorkspaceRole } from "@app/hooks/api"; +import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { TWorkspaceUser } from "@app/hooks/api/types"; -import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types"; const roleFormSchema = z.object({ roles: z @@ -65,9 +65,8 @@ type Props = { export const MemberRoleModify = ({ projectMember, onOpenUpgradeModal }: Props) => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; - const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(workspaceId); + const { projectId } = useProject(); + const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles(projectId); const { permission } = useProjectPermission(); const isMemberEditDisabled = permission.cannot( ProjectPermissionMemberActions.Edit, @@ -131,7 +130,7 @@ export const MemberRoleModify = ({ projectMember, onOpenUpgradeModal }: Props) = try { await updateMembershipRole.mutateAsync({ - workspaceId, + projectId, membershipId: projectMember.id, roles: sanitizedRoles }); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx index e400767a1..775244b4d 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx @@ -16,7 +16,7 @@ import { DropdownMenuTrigger, PageHeader } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; @@ -33,8 +33,8 @@ const Page = () => { strict: false, select: (el) => el.roleSlug as string }); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data } = useGetProjectRoleBySlug(projectId, roleSlug as string); @@ -48,7 +48,7 @@ const Page = () => { const onDeleteRoleSubmit = async () => { try { - if (!currentWorkspace?.slug || !data?.id) return; + if (!currentProject?.slug || !data?.id) return; await deleteProjectRole({ projectId, @@ -61,7 +61,7 @@ const Page = () => { }); handlePopUpClose("deleteRole"); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const, + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, params: { projectId }, diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx index abdf5c5d5..37f6b3868 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx @@ -9,7 +9,7 @@ import { IconButton } from "@app/components/v2"; import { usePopUp } from "@app/hooks"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal"; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AppConnectionPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AppConnectionPermissionConditions.tsx new file mode 100644 index 000000000..2991d895c --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AppConnectionPermissionConditions.tsx @@ -0,0 +1,19 @@ +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; + +import { ConditionsFields } from "./ConditionsFields"; + +type Props = { + position?: number; + isDisabled?: boolean; +}; + +export const AppConnectionPermissionConditions = ({ position = 0, isDisabled }: Props) => { + return ( + + ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/DuplicateProjectRoleModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/DuplicateProjectRoleModal.tsx index 24974225e..d36562089 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/DuplicateProjectRoleModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/DuplicateProjectRoleModal.tsx @@ -5,7 +5,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent, Spinner } from "@app/components/v2"; -import { ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { getProjectBaseURL } from "@app/helpers/project"; import { useCreateProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api"; @@ -45,7 +45,7 @@ const Content = ({ role, onClose }: ContentProps) => { resolver: zodResolver(schema) }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const createRole = useCreateProjectRole(); const navigate = useNavigate(); @@ -70,7 +70,7 @@ const Content = ({ role, onClose }: ContentProps) => { }); const newRole = await createRole.mutateAsync({ - projectId: currentWorkspace.id, + projectId: currentProject.id, permissions: sanitizedPermission, ...form }); @@ -81,10 +81,10 @@ const Content = ({ role, onClose }: ContentProps) => { }); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const, + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, params: { roleSlug: newRole.slug, - projectId: currentWorkspace.id + projectId: currentProject.id } }); @@ -142,9 +142,9 @@ const Content = ({ role, onClose }: ContentProps) => { }; export const DuplicateProjectRoleModal = ({ isOpen, onOpenChange, roleSlug }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: role, isPending } = useGetProjectRoleBySlug(currentWorkspace.id, roleSlug ?? ""); + const { data: role, isPending } = useGetProjectRoleBySlug(currentProject.id, roleSlug ?? ""); if (!roleSlug) return null; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx index a39ae8d64..f6c44b386 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx @@ -23,6 +23,7 @@ export const renderOperatorSelectItems = (type: string) => { case "secretTags": return Contains; case "identityId": + case "connectionId": return ( <> Equal diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx index 845c5f54e..22e64d48e 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx @@ -19,7 +19,7 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { EXCLUDED_PERMISSION_SUBS, diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx index d9cae6244..3064311bc 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx @@ -13,7 +13,7 @@ import { ModalContent } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { PROJECT_PERMISSION_OBJECT, diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 856275374..89c1e5d86 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -12,6 +12,7 @@ import { } from "@app/context"; import { PermissionConditionOperators, + ProjectPermissionAppConnectionActions, ProjectPermissionAuditLogsActions, ProjectPermissionCommitsActions, ProjectPermissionDynamicSecretActions, @@ -32,8 +33,8 @@ import { TPermissionCondition, TPermissionConditionOperators } from "@app/context/ProjectPermissionContext/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { TProjectPermission } from "@app/hooks/api/roles/types"; -import { ProjectType } from "@app/hooks/api/workspace/types"; const GeneralPolicyActionSchema = z.object({ read: z.boolean().optional(), @@ -134,6 +135,14 @@ const SecretScanningConfigPolicyActionSchema = z.object({ [ProjectPermissionSecretScanningConfigActions.Update]: z.boolean().optional() }); +const AppConnectionPolicyActionSchema = z.object({ + [ProjectPermissionAppConnectionActions.Create]: z.boolean().optional(), + [ProjectPermissionAppConnectionActions.Read]: z.boolean().optional(), + [ProjectPermissionAppConnectionActions.Edit]: z.boolean().optional(), + [ProjectPermissionAppConnectionActions.Delete]: z.boolean().optional(), + [ProjectPermissionAppConnectionActions.Connect]: z.boolean().optional() +}); + const KmipPolicyActionSchema = z.object({ [ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(), [ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(), @@ -311,6 +320,12 @@ export const projectRoleFormSchema = z.object({ }) .array() .default([]), + [ProjectPermissionSub.AppConnections]: AppConnectionPolicyActionSchema.extend({ + inverted: z.boolean().optional(), + conditions: ConditionSchema + }) + .array() + .default([]), [ProjectPermissionSub.Commits]: CommitPolicyActionSchema.array().default([]), [ProjectPermissionSub.Member]: MemberPolicyActionSchema.array().default([]), @@ -393,7 +408,8 @@ type TConditionalFields = | ProjectPermissionSub.SecretRotation | ProjectPermissionSub.Identity | ProjectPermissionSub.SecretSyncs - | ProjectPermissionSub.SecretEvents; + | ProjectPermissionSub.SecretEvents + | ProjectPermissionSub.AppConnections; export const isConditionalSubjects = ( subject: ProjectPermissionSub @@ -408,7 +424,8 @@ export const isConditionalSubjects = ( subject === ProjectPermissionSub.PkiSubscribers || subject === ProjectPermissionSub.CertificateTemplates || subject === ProjectPermissionSub.SecretSyncs || - subject === ProjectPermissionSub.SecretEvents; + subject === ProjectPermissionSub.SecretEvents || + subject === ProjectPermissionSub.AppConnections; const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => { const formConditions: z.infer = []; @@ -515,7 +532,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { ProjectPermissionSub.SshCertificates, ProjectPermissionSub.SshHostGroups, ProjectPermissionSub.SecretSyncs, - ProjectPermissionSub.SecretEvents + ProjectPermissionSub.SecretEvents, + ProjectPermissionSub.AppConnections ].includes(subject) ) { // from above statement we are sure it won't be undefined @@ -654,6 +672,27 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { return; } + if (subject === ProjectPermissionSub.AppConnections) { + const canCreate = action.includes(ProjectPermissionAppConnectionActions.Create); + const canRead = action.includes(ProjectPermissionAppConnectionActions.Read); + const canEdit = action.includes(ProjectPermissionAppConnectionActions.Edit); + const canDelete = action.includes(ProjectPermissionAppConnectionActions.Delete); + const canConnect = action.includes(ProjectPermissionAppConnectionActions.Connect); + + // from above statement we are sure it won't be undefined + formVal[subject]!.push({ + [ProjectPermissionAppConnectionActions.Read]: canRead, + [ProjectPermissionAppConnectionActions.Create]: canCreate, + [ProjectPermissionAppConnectionActions.Edit]: canEdit, + [ProjectPermissionAppConnectionActions.Delete]: canDelete, + [ProjectPermissionAppConnectionActions.Connect]: canConnect, + conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], + inverted + }); + + return; + } + // for other subjects const canRead = action.includes(ProjectPermissionActions.Read); const canEdit = action.includes(ProjectPermissionActions.Edit); @@ -1597,6 +1636,31 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { value: ProjectPermissionSecretEventActions.SubscribeImportMutations } ] + }, + [ProjectPermissionSub.AppConnections]: { + title: "App Connections", + actions: [ + { + label: "Read", + value: ProjectPermissionAppConnectionActions.Read + }, + { + label: "Create", + value: ProjectPermissionAppConnectionActions.Create + }, + { + label: "Update", + value: ProjectPermissionAppConnectionActions.Edit + }, + { + label: "Delete", + value: ProjectPermissionAppConnectionActions.Delete + }, + { + label: "Connect", + value: ProjectPermissionAppConnectionActions.Connect + } + ] } }; @@ -1669,7 +1733,8 @@ export const ProjectTypePermissionSubjects: Record< ...KmsPermissionSubjects(), ...CertificateManagerPermissionSubjects(), ...SshPermissionSubjects(), - ...SecretScanningSubject() + ...SecretScanningSubject(), + [ProjectPermissionSub.AppConnections]: true }, [ProjectType.KMS]: { ...SharedPermissionSubjects, @@ -1677,7 +1742,8 @@ export const ProjectTypePermissionSubjects: Record< ...SecretsManagerPermissionSubjects(), ...CertificateManagerPermissionSubjects(), ...SshPermissionSubjects(), - ...SecretScanningSubject() + ...SecretScanningSubject(), + [ProjectPermissionSub.AppConnections]: false }, [ProjectType.CertificateManager]: { ...SharedPermissionSubjects, @@ -1685,7 +1751,8 @@ export const ProjectTypePermissionSubjects: Record< ...KmsPermissionSubjects(), ...SecretsManagerPermissionSubjects(), ...SshPermissionSubjects(), - ...SecretScanningSubject() + ...SecretScanningSubject(), + [ProjectPermissionSub.AppConnections]: true }, [ProjectType.SSH]: { ...SharedPermissionSubjects, @@ -1693,7 +1760,8 @@ export const ProjectTypePermissionSubjects: Record< ...CertificateManagerPermissionSubjects(), ...KmsPermissionSubjects(), ...SecretsManagerPermissionSubjects(), - ...SecretScanningSubject() + ...SecretScanningSubject(), + [ProjectPermissionSub.AppConnections]: false }, [ProjectType.SecretScanning]: { ...SharedPermissionSubjects, @@ -1701,7 +1769,8 @@ export const ProjectTypePermissionSubjects: Record< ...SshPermissionSubjects(), ...CertificateManagerPermissionSubjects(), ...KmsPermissionSubjects(), - ...SecretsManagerPermissionSubjects() + ...SecretsManagerPermissionSubjects(), + [ProjectPermissionSub.AppConnections]: true } }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx index eb7a2ba83..e34f86892 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { useCreateProjectRole, @@ -38,8 +38,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { roleSlug: string; }; - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: role } = useGetProjectRoleBySlug(projectId, popupData?.roleSlug ?? ""); @@ -101,7 +101,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { }); navigate({ - to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const, + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, params: { roleSlug: newRole.slug, projectId diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index f50446a8e..347ebaf99 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -8,14 +8,15 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { createNotification } from "@app/components/notifications"; import { AccessTree } from "@app/components/permissions"; import { Button } from "@app/components/v2"; -import { ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { evaluatePermissionsAbility } from "@app/helpers/permissions"; import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectType } from "@app/hooks/api/workspace/types"; import { AddPoliciesButton } from "./AddPoliciesButton"; +import { AppConnectionPermissionConditions } from "./AppConnectionPermissionConditions"; import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions"; import { GeneralPermissionConditions } from "./GeneralPermissionConditions"; import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies"; @@ -77,6 +78,10 @@ export const renderConditionalComponents = ( return ; } + if (subject === ProjectPermissionSub.AppConnections) { + return ; + } + return ; } @@ -84,10 +89,10 @@ export const renderConditionalComponents = ( }; export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: role, isPending } = useGetProjectRoleBySlug( - currentWorkspace?.id ?? "", + currentProject?.id ?? "", roleSlug as string ); @@ -126,7 +131,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { (role?.slug ?? "") as ProjectMembershipRole ); - const isSecretManagerProject = currentWorkspace.type === ProjectType.SecretManager; + const isSecretManagerProject = currentProject.type === ProjectType.SecretManager; const permissions = form.watch("permissions"); @@ -178,7 +183,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { Save
- +
)} diff --git a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx index 7c65b1857..11f4f0270 100644 --- a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx @@ -5,10 +5,10 @@ import { z } from "zod"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input } from "@app/components/v2"; -import { useProjectPermission, useSubscription, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission, useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; +import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/projects/queries"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/workspace/queries"; const formSchema = z.object({ auditLogsRetentionDays: z.coerce.number().min(0) @@ -19,7 +19,7 @@ type TForm = z.infer; export const AuditLogsRetentionSection = () => { const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { membership } = useProjectPermission(); const { subscription } = useSubscription(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); @@ -32,11 +32,11 @@ export const AuditLogsRetentionSection = () => { resolver: zodResolver(formSchema), values: { auditLogsRetentionDays: - currentWorkspace?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0 + currentProject?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0 } }); - if (!currentWorkspace) return null; + if (!currentProject) return null; const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => { try { @@ -59,7 +59,7 @@ export const AuditLogsRetentionSection = () => { await updateAuditLogsRetention({ auditLogsRetentionDays, - projectSlug: currentWorkspace.slug + projectSlug: currentProject.slug }); createNotification({ diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx index 2ac123607..efc69831d 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx @@ -1,20 +1,19 @@ import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Checkbox } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useToggleDeleteProjectProtection } from "@app/hooks/api/workspace/queries"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; +import { useUpdateProject } from "@app/hooks/api"; export const DeleteProjectProtection = () => { - const { currentWorkspace } = useWorkspace(); - const { mutateAsync } = useToggleDeleteProjectProtection(); + const { projectId, currentProject } = useProject(); + + const { mutateAsync } = useUpdateProject(); const handleToggleDeleteProjectProtection = async (state: boolean) => { try { - if (!currentWorkspace?.id) return; - await mutateAsync({ - workspaceID: currentWorkspace.id, - state + projectId, + hasDeleteProtection: state }); const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`; @@ -40,7 +39,7 @@ export const DeleteProjectProtection = () => { { handleToggleDeleteProjectProtection(state as boolean); }} diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx index ac589ba59..4771e0c88 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx @@ -9,8 +9,8 @@ import { ProjectPermissionActions, ProjectPermissionSub, useOrganization, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { useToggle } from "@app/hooks"; import { useDeleteWorkspace, useGetWorkspaceUsers, useLeaveProject } from "@app/hooks/api"; @@ -26,13 +26,13 @@ export const DeleteProjectSection = () => { const { currentOrg } = useOrganization(); const { hasProjectRole, membership } = useProjectPermission(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [isDeleting, setIsDeleting] = useToggle(); const [isLeaving, setIsLeaving] = useToggle(); const deleteWorkspace = useDeleteWorkspace(); const leaveProject = useLeaveProject(); const { data: members, isPending: isMembersLoading } = useGetWorkspaceUsers( - currentWorkspace?.id || "" + currentProject?.id || "" ); // If isNoAccessMember is true, then the user can't read the workspace members. So we need to handle this case separately. @@ -51,10 +51,10 @@ export const DeleteProjectSection = () => { const handleDeleteWorkspaceSubmit = async () => { setIsDeleting.on(); try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await deleteWorkspace.mutateAsync({ - workspaceID: currentWorkspace?.id + projectID: currentProject?.id }); createNotification({ @@ -81,7 +81,7 @@ export const DeleteProjectSection = () => { try { setIsLeaving.on(); - if (!currentWorkspace?.id || !currentOrg?.id) return; + if (!currentProject?.id || !currentOrg?.id) return; // If there's no members, and the user has access to read members, something went wrong. if (!members && !isNoAccessMember) return; @@ -110,7 +110,7 @@ export const DeleteProjectSection = () => { // If it's actually a no-access member, then we don't really care about the members. await leaveProject.mutateAsync({ - workspaceId: currentWorkspace.id + projectId: currentProject.id }); navigate({ @@ -141,7 +141,7 @@ export const DeleteProjectSection = () => { type="submit" onClick={() => handlePopUpOpen("deleteWorkspace")} > - {`Delete ${currentWorkspace?.name}`} + {`Delete ${currentProject?.name}`} )} @@ -154,7 +154,7 @@ export const DeleteProjectSection = () => { type="submit" onClick={() => handlePopUpOpen("leaveWorkspace")} > - {`Leave ${currentWorkspace?.name}`} + {`Leave ${currentProject?.name}`} )} @@ -162,7 +162,7 @@ export const DeleteProjectSection = () => { handlePopUpToggle("deleteWorkspace", isOpen)} deleteKey="confirm" buttonText="Delete Project" @@ -172,7 +172,7 @@ export const DeleteProjectSection = () => { handlePopUpToggle("leaveWorkspace", isOpen)} deleteKey="confirm" buttonText="Leave Project" diff --git a/frontend/src/pages/public/ErrorPage/ErrorPage.tsx b/frontend/src/pages/public/ErrorPage/ErrorPage.tsx index 1910eb891..ef6a837ae 100644 --- a/frontend/src/pages/public/ErrorPage/ErrorPage.tsx +++ b/frontend/src/pages/public/ErrorPage/ErrorPage.tsx @@ -5,7 +5,17 @@ import { AxiosError } from "axios"; import { Button } from "@app/components/v2"; +import { ProjectAccessError } from "./components"; + export const ErrorPage = ({ error }: ErrorComponentProps) => { + if ( + error instanceof AxiosError && + error.status === 403 && + error.response?.data?.error === "User not a part of the specified project" + ) { + return ; + } + return (
diff --git a/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx new file mode 100644 index 000000000..d6fd57d56 --- /dev/null +++ b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx @@ -0,0 +1,109 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; + +import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { RequestProjectAccessModal } from "@app/components/projects"; +import { AccessRestrictedBanner, Button } from "@app/components/v2"; +import { OrgPermissionSubjects } from "@app/context"; +import { OrgPermissionAdminConsoleAction } from "@app/context/OrgPermissionContext/types"; +import { usePopUp } from "@app/hooks"; +import { useOrgAdminAccessProject, useSearchProjects } from "@app/hooks/api"; + +export const ProjectAccessError = () => { + const orgAdminAccessProject = useOrgAdminAccessProject(); + + const navigate = useNavigate(); + + const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([ + "requestAccessConfirmation" + ] as const); + + const { projectId } = useParams({ + strict: false + }); + + const { data, isPending: isProjectLoading } = useSearchProjects({ + projectIds: projectId ? [projectId] : [], + options: { + enabled: Boolean(projectId) + } + }); + + const [project] = data?.projects ?? []; + + const handleAccessProject = async () => { + if (!project) return; + try { + await orgAdminAccessProject.mutateAsync({ + projectId: project.id + }); + await navigate({ + to: "." + }); + } catch { + createNotification({ + text: "Failed to access project", + type: "error" + }); + } + }; + + return ( +
+ + You are not currently a member of this project. Request access to join project. +
+ + + + + {(isAllowed) => + isAllowed ? ( + + ) : ( + + ) + } + +
+ handlePopUpToggle("requestAccessConfirmation", isOpen)} + project={project} + onComplete={() => { + navigate({ + to: "/organization/projects" + }); + }} + /> + + } + /> +
+ ); +}; diff --git a/frontend/src/pages/public/ErrorPage/components/index.ts b/frontend/src/pages/public/ErrorPage/components/index.ts new file mode 100644 index 000000000..1940497e9 --- /dev/null +++ b/frontend/src/pages/public/ErrorPage/components/index.ts @@ -0,0 +1 @@ +export * from "./ProjectAccessError"; diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx index 06f79e9f0..facbd782b 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx @@ -2,7 +2,7 @@ import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { ProjectPermissionCan } from "@app/components/permissions"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectPermissionCommitsActions, ProjectPermissionSub @@ -23,7 +23,7 @@ export const CommitDetailsPage = () => { from: ROUTE_PATHS.SecretManager.CommitDetailsPage.id, select: (el) => el.folderId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const routerQueryParams: { secretPath?: string } = useSearch({ @@ -36,7 +36,7 @@ export const CommitDetailsPage = () => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, folderId, environment: envSlug }, @@ -51,7 +51,7 @@ export const CommitDetailsPage = () => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId/restore", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, folderId, environment: envSlug, commitId: selectedCommitId @@ -73,7 +73,7 @@ export const CommitDetailsPage = () => { > { export const CommitDetailsTab = ({ selectedCommitId, - workspaceId, + projectId, envSlug, goBackToHistory, goToRollbackPreview }: { selectedCommitId: string; - workspaceId: string; + projectId: string; envSlug: string; goBackToHistory: () => void; goToRollbackPreview: () => void; @@ -71,7 +71,7 @@ export const CommitDetailsTab = ({ "revertChanges" ] as const); - const { data: commitDetails, isLoading } = useGetCommitDetails(workspaceId, selectedCommitId); + const { data: commitDetails, isLoading } = useGetCommitDetails(projectId, selectedCommitId); const routerQueryParams: { secretPath?: string } = useSearch({ from: ROUTE_PATHS.SecretManager.CommitDetailsPage.id @@ -80,7 +80,7 @@ export const CommitDetailsTab = ({ const { mutateAsync: revert } = useCommitRevert({ commitId: selectedCommitId, - projectId: workspaceId, + projectId, environment: envSlug, directory: secretPath }); diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx index e06daea38..ae082d8ea 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx @@ -17,7 +17,7 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectPermissionCommitsActions, ProjectPermissionSub @@ -79,7 +79,7 @@ export const RollbackPreviewTab = (): JSX.Element => { const [deepRollback, setDeepRollback] = useState(false); const [message, setMessage] = useState(""); const [selectedFolderId, setSelectedFolderId] = useState(null); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const envSlug = useParams({ from: ROUTE_PATHS.SecretManager.RollbackPreviewPage.id, select: (el) => el.environment @@ -104,7 +104,7 @@ export const RollbackPreviewTab = (): JSX.Element => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, folderId, environment: envSlug }, @@ -120,7 +120,7 @@ export const RollbackPreviewTab = (): JSX.Element => { ] as const); const { mutateAsync: rollback } = useCommitRollback({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, commitId: selectedCommitId, folderId, deepRollback, @@ -133,7 +133,7 @@ export const RollbackPreviewTab = (): JSX.Element => { folderId, selectedCommitId, envSlug, - currentWorkspace.id, + currentProject.id, deepRollback, secretPath ); diff --git a/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx b/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx index 1acb76446..2dba8ad55 100644 --- a/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx +++ b/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx @@ -4,7 +4,7 @@ import { ProjectPermissionCan } from "@app/components/permissions"; import { PageHeader } from "@app/components/v2"; import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectPermissionCommitsActions, ProjectPermissionSub @@ -17,7 +17,7 @@ export const CommitsPage = () => { from: ROUTE_PATHS.SecretManager.CommitsPage.id, select: (el) => el.environment }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const folderId = useParams({ from: ROUTE_PATHS.SecretManager.CommitsPage.id, @@ -33,7 +33,7 @@ export const CommitsPage = () => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, folderId, environment: envSlug, commitId @@ -67,7 +67,7 @@ export const CommitsPage = () => { > diff --git a/frontend/src/pages/secret-manager/CommitsPage/components/CommitHistoryTab/CommitHistoryTab.tsx b/frontend/src/pages/secret-manager/CommitsPage/components/CommitHistoryTab/CommitHistoryTab.tsx index 58191f389..d5f982529 100644 --- a/frontend/src/pages/secret-manager/CommitsPage/components/CommitHistoryTab/CommitHistoryTab.tsx +++ b/frontend/src/pages/secret-manager/CommitsPage/components/CommitHistoryTab/CommitHistoryTab.tsx @@ -136,7 +136,7 @@ export const CommitHistoryTab = ({ isFetchingNextPage, hasNextPage } = useGetFolderCommitHistory({ - workspaceId: projectId, + projectId, environment, directory: secretPath, limit, diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx index 4d95b6388..36f585850 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx @@ -5,7 +5,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useAddTrustedIp, useGetMyIp, useUpdateTrustedIp } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -27,7 +27,7 @@ type Props = { export const IPAllowlistModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { const { data, isPending } = useGetMyIp(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const addTrustedIp = useAddTrustedIp(); const updateTrustedIp = useUpdateTrustedIp(); @@ -65,11 +65,11 @@ export const IPAllowlistModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: const onIPAllowlistModalSubmit = async ({ ipAddress, comment }: FormData) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; if (popUp?.trustedIp?.data) { await updateTrustedIp.mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId, ipAddress, comment, @@ -77,7 +77,7 @@ export const IPAllowlistModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: }); } else { await addTrustedIp.mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, ipAddress, comment, isActive: true diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx index 63b410ee2..fc2890b7f 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx @@ -8,8 +8,8 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { useDeleteTrustedIp } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -20,7 +20,7 @@ import { IPAllowlistTable } from "./IPAllowlistTable"; export const IPAllowlistSection = () => { const { mutateAsync } = useDeleteTrustedIp(); const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "trustedIp", @@ -30,10 +30,10 @@ export const IPAllowlistSection = () => { const onDeleteTrustedIpSubmit = async (trustedIpId: string) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, trustedIpId }); diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx index 42ecc8d8a..8f3336be8 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx @@ -18,8 +18,8 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { useGetTrustedIps } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -41,8 +41,8 @@ type Props = { export const IPAllowlistTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useGetTrustedIps(currentWorkspace?.id ?? ""); + const { currentProject } = useProject(); + const { data, isPending } = useGetTrustedIps(currentProject?.id ?? ""); const formatType = (type: string, prefix?: number) => { return `${type.slice(0, 2).toUpperCase() + type.slice(2)} ${ @@ -77,10 +77,10 @@ export const IPAllowlistTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: {comment} {/*
- + />

Active

*/} diff --git a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx index c1ba83ae6..3788d955e 100644 --- a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx @@ -20,11 +20,11 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { OrgPermissionActions, OrgPermissionSubjects, useWorkspace } from "@app/context"; +import { OrgPermissionActions, OrgPermissionSubjects, useProject } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { useGetIntegration } from "@app/hooks/api"; import { useDeleteIntegration, useSyncIntegration } from "@app/hooks/api/integrations/queries"; -import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ProjectType } from "@app/hooks/api/projects/types"; import { IntegrationAuditLogsSection } from "./components/IntegrationAuditLogsSection"; import { IntegrationConnectionSection } from "./components/IntegrationConnectionSection"; @@ -45,8 +45,8 @@ export const IntegrationDetailsByIDPage = () => { const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { mutateAsync: syncIntegration } = useSyncIntegration(); const { mutateAsync: deleteIntegration } = useDeleteIntegration(); @@ -56,7 +56,7 @@ export const IntegrationDetailsByIDPage = () => { try { await deleteIntegration({ id: integrationId, - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, shouldDeleteIntegrationSecrets }); diff --git a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/components/IntegrationAuditLogsSection.tsx b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/components/IntegrationAuditLogsSection.tsx index c27b1bb08..6a808814e 100644 --- a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/components/IntegrationAuditLogsSection.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/components/IntegrationAuditLogsSection.tsx @@ -1,7 +1,7 @@ import { Link } from "@tanstack/react-router"; import { EmptyState } from "@app/components/v2"; -import { useSubscription, useWorkspace } from "@app/context"; +import { useProject, useSubscription } from "@app/context"; import { EventType } from "@app/hooks/api/auditLogs/enums"; import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types"; import { LogsSection } from "@app/pages/organization/AuditLogsPage/components/LogsSection"; @@ -15,7 +15,7 @@ type Props = { export const IntegrationAuditLogsSection = ({ integration }: Props) => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const auditLogsRetentionDays = subscription?.auditLogsRetentionDays ?? 30; @@ -31,7 +31,7 @@ export const IntegrationAuditLogsSection = ({ integration }: Props) => { { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { t } = useTranslation(); const { selectedTab } = useSearch({ @@ -30,7 +30,7 @@ export const IntegrationsListPage = () => { to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, search: (prev) => ({ ...prev, selectedTab: tab as IntegrationsListPageTabs }), params: { - projectId: currentWorkspace.id + projectId: currentProject.id } }); }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index 935ad1a8c..42f661606 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -24,8 +24,8 @@ import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; import { SecretSync } from "@app/hooks/api/secretSyncs"; @@ -60,7 +60,7 @@ export const CloudIntegrationSection = ({ "deleteConfirmation" ] as const); const { permission } = useProjectPermission(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const isEmpty = !isLoading && !cloudIntegrations?.length; @@ -68,12 +68,12 @@ export const CloudIntegrationSection = ({ const sortedCloudIntegrations = useMemo(() => { const sortedIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name)); - if (currentWorkspace?.environments.length === 0) { + if (currentProject?.environments.length === 0) { return sortedIntegrations.map((integration) => ({ ...integration, isAvailable: false })); } return sortedIntegrations; - }, [cloudIntegrations, currentWorkspace?.environments]); + }, [cloudIntegrations, currentProject?.environments]); const [search, setSearch] = useState(""); @@ -83,9 +83,9 @@ export const CloudIntegrationSection = ({ return (
- {currentWorkspace?.environments.length === 0 && ( + {currentProject?.environments.length === 0 && (
- +
)}
@@ -138,7 +138,7 @@ export const CloudIntegrationSection = ({ navigate({ to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.SecretSyncs, diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationRow.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationRow.tsx index df862b8ba..c83e24254 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationRow.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationRow.tsx @@ -15,7 +15,7 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Badge, IconButton, Td, Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { TCloudIntegration } from "@app/hooks/api/integrations/types"; import { TIntegration } from "@app/hooks/api/types"; @@ -37,7 +37,7 @@ export const IntegrationRow = ({ cloudIntegration }: IProps) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { id, secretPath, syncMessage, isSynced } = integration; @@ -62,7 +62,7 @@ export const IntegrationRow = ({ to: "/projects/secret-management/$projectId/integrations/$integrationId", params: { integrationId: integration.id, - projectId: currentWorkspace.id + projectId: currentProject.id } }) } diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx index f3706940c..b4f790075 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx @@ -5,7 +5,7 @@ import { useNavigate } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, DeleteActionModal, Spinner } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { useDeleteIntegration, @@ -27,8 +27,8 @@ enum IntegrationView { } export const NativeIntegrationsTab = () => { - const { currentWorkspace } = useWorkspace(); - const { environments, id: workspaceId } = currentWorkspace; + const { currentProject } = useProject(); + const { environments, id: workspaceId } = currentProject; const navigate = useNavigate(); const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } = @@ -91,7 +91,7 @@ export const NativeIntegrationsTab = () => { if (!selectedCloudIntegration) return; try { - redirectForProviderAuth(currentWorkspace.id, navigate, selectedCloudIntegration); + redirectForProviderAuth(currentProject.id, navigate, selectedCloudIntegration); } catch (error) { console.error(error); } diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index fcd42a10f..ed96dcd45 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -36,7 +36,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { getUserTablePreference, @@ -109,7 +109,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { environmentIds: [] }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { search, @@ -372,7 +372,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { No Secret Syncs Configured )} Environment - {currentWorkspace.environments.map((env) => ( + {currentProject.environments.map((env) => ( { e.preventDefault(); diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncsTab.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncsTab.tsx index 20a3c74df..f1866cda0 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncsTab.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncsTab.tsx @@ -1,13 +1,14 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreateSecretSyncModal } from "@app/components/secret-syncs"; +import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; import { Button, Spinner } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { useListSecretSyncs } from "@app/hooks/api/secretSyncs"; @@ -16,14 +17,15 @@ import { SecretSyncsTable } from "./SecretSyncTable"; export const SecretSyncsTab = () => { const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addSync"] as const); + const [initialSyncFormData, setInitialSyncFormData] = useState>(); - const { addSync, ...search } = useSearch({ + const { addSync, connectionId, connectionName, ...search } = useSearch({ from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id }); const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { if (!addSync) return; @@ -32,14 +34,46 @@ export const SecretSyncsTab = () => { navigate({ to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search }); }, [addSync]); + useEffect(() => { + if (connectionId && connectionName) { + const storedFormData = localStorage.getItem("secretSyncFormData"); + + if (!storedFormData) return; + + let form: Partial = {}; + try { + form = JSON.parse(storedFormData) as TSecretSyncForm; + } catch { + return; + } finally { + localStorage.removeItem("secretSyncFormData"); + } + + handlePopUpOpen("addSync", form.destination); + + setInitialSyncFormData({ + ...form, + connection: { id: connectionId, name: connectionName } + }); + + navigate({ + to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, + params: { + projectId: currentProject.id + }, + search + }); + } + }, [connectionId, connectionName]); + const { data: secretSyncs = [], isPending: isSecretSyncsPending } = useListSecretSyncs( - currentWorkspace.id, + currentProject.id, { refetchInterval: 30000 } @@ -100,7 +134,11 @@ export const SecretSyncsTab = () => { handlePopUpToggle("addSync", isOpen)} + initialFormData={initialSyncFormData} + onOpenChange={(isOpen) => { + if (!isOpen) setInitialSyncFormData(undefined); + handlePopUpToggle("addSync", isOpen); + }} /> ); diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx index b7f8236da..405aec3e9 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx @@ -2,22 +2,24 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; -import { workspaceKeys } from "@app/hooks/api"; +import { projectKeys } from "@app/hooks/api"; import { TIntegration } from "@app/hooks/api/integrations/types"; +import { fetchWorkspaceIntegrations } from "@app/hooks/api/projects/queries"; import { fetchSecretSyncsByProjectId, SecretSync, secretSyncKeys, TSecretSync } from "@app/hooks/api/secretSyncs"; -import { fetchWorkspaceIntegrations } from "@app/hooks/api/workspace/queries"; import { IntegrationsListPageTabs } from "@app/types/integrations"; import { IntegrationsListPage } from "./IntegrationsListPage"; const IntegrationsListPageQuerySchema = z.object({ selectedTab: z.nativeEnum(IntegrationsListPageTabs).optional(), - addSync: z.nativeEnum(SecretSync).optional() + addSync: z.nativeEnum(SecretSync).optional(), + connectionId: z.string().optional(), + connectionName: z.string().optional() }); export const Route = createFileRoute( @@ -57,7 +59,7 @@ export const Route = createFileRoute( let integrations: TIntegration[]; try { integrations = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceIntegrations(projectId), + queryKey: projectKeys.getProjectIntegrations(projectId), queryFn: () => fetchWorkspaceIntegrations(projectId) }); } catch { diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 910e07eac..ab9b332b0 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -63,9 +63,9 @@ import { ProjectPermissionActions, ProjectPermissionDynamicSecretActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; import { @@ -92,16 +92,11 @@ import { import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries"; import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/types"; import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { ProjectVersion } from "@app/hooks/api/projects/types"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; -import { - SecretType, - SecretV3RawSanitized, - TSecretFolder, - WorkspaceEnv -} from "@app/hooks/api/types"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; +import { ProjectEnv, SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { useDynamicSecretOverview, useFolderOverview, @@ -173,10 +168,9 @@ export const OverviewPage = () => { const [debouncedScrollOffset] = useDebounce(scrollOffset); const { permission } = useProjectPermission(); const tableRef = useRef(null); - const { currentWorkspace } = useWorkspace(); - const isProjectV3 = currentWorkspace?.version === ProjectVersion.V3; - const workspaceId = currentWorkspace?.id as string; - const projectSlug = currentWorkspace?.slug as string; + const { currentProject, projectId } = useProject(); + const isProjectV3 = currentProject?.version === ProjectVersion.V3; + const projectSlug = currentProject?.slug as string; const [searchFilter, setSearchFilter] = useState(""); const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter); const secretPath = (routerSearch?.secretPath as string) || "/"; @@ -246,7 +240,7 @@ export const OverviewPage = () => { }; }, []); - const userAvailableEnvs = currentWorkspace?.environments || []; + const userAvailableEnvs = currentProject?.environments || []; const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) => permission.can( ProjectPermissionDynamicSecretActions.CreateRootCredential, @@ -267,7 +261,7 @@ export const OverviewPage = () => { ) ); - const [filteredEnvs, setFilteredEnvs] = useState([]); + const [filteredEnvs, setFilteredEnvs] = useState([]); const visibleEnvs = filteredEnvs.length ? filteredEnvs : userAvailableEnvs; const { @@ -276,7 +270,7 @@ export const OverviewPage = () => { getImportedSecretByKey, getEnvImportedSecretKeyCount } = useGetImportedSecretsAllEnvs({ - projectId: workspaceId, + projectId, path: secretPath, environments: (userAvailableEnvs || []).map(({ slug }) => slug) }); @@ -284,7 +278,7 @@ export const OverviewPage = () => { const isFilteredByResources = Object.values(filter).some(Boolean); const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( { - projectId: workspaceId, + projectId, environments: visibleEnvs.map((env) => env.slug), secretPath, orderDirection, @@ -368,7 +362,7 @@ export const OverviewPage = () => { ); const { data: tags } = useGetWsTags( - permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : "" + permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? projectId : "" ); const { mutateAsync: createSecretV3 } = useCreateSecretV3(); @@ -398,7 +392,7 @@ export const OverviewPage = () => { name: folderName, path: secretPath, environment, - projectId: workspaceId, + projectId, description }); }); @@ -456,9 +450,8 @@ export const OverviewPage = () => { try { await updateFolderBatch({ - projectSlug, folders: updatedFolders, - projectId: workspaceId + projectId }); createNotification({ type: "success", @@ -491,7 +484,7 @@ export const OverviewPage = () => { ); if (folderName && parentPath && canCreateFolder) { await createFolder({ - projectId: workspaceId, + projectId, path: parentPath, environment: env, name: folderName @@ -500,7 +493,7 @@ export const OverviewPage = () => { } const result = await createSecretV3({ environment: env, - workspaceId, + projectId, secretPath, secretKey: key, secretValue: value, @@ -555,7 +548,7 @@ export const OverviewPage = () => { try { const result = await updateSecretV3({ environment: env, - workspaceId, + projectId, secretPath, secretKey: key, secretValue, @@ -586,7 +579,7 @@ export const OverviewPage = () => { try { const result = await deleteSecretV3({ environment: env, - workspaceId, + projectId, secretPath, secretKey: key, secretId, @@ -655,7 +648,7 @@ export const OverviewPage = () => { ); if (folderName && parentPath && canCreateFolder) { await createFolder({ - projectId: workspaceId, + projectId, environment: slug, path: parentPath, name: folderName @@ -669,7 +662,7 @@ export const OverviewPage = () => { navigate({ to: "/projects/secret-management/$projectId/secrets/$envSlug", params: { - projectId: workspaceId, + projectId, envSlug: slug }, search: query @@ -1099,7 +1092,7 @@ export const OverviewPage = () => { tags={tags} onChange={setSearchFilter} environments={userAvailableEnvs} - projectId={currentWorkspace?.id} + projectId={currentProject?.id} /> {userAvailableEnvs.length > 0 && (
@@ -1419,7 +1412,7 @@ export const OverviewPage = () => { diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx index 7087faf72..d4f8bbafe 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -19,8 +19,8 @@ import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { ProjectPermissionActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { getKeyValue } from "@app/helpers/parseEnvVar"; @@ -57,16 +57,15 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { formState: { isSubmitting, errors } } = useForm({ resolver: zodResolver(typeSchema) }); - const { currentWorkspace } = useWorkspace(); + const { currentProject, projectId } = useProject(); const { permission } = useProjectPermission(); const canReadTags = permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); - const workspaceId = currentWorkspace?.id || ""; - const environments = currentWorkspace?.environments || []; + const environments = currentProject?.environments || []; const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); const { data: projectTags, isPending: isTagsLoading } = useGetWsTags( - canReadTags ? workspaceId : "" + canReadTags ? projectId : "" ); const secretKeyInputRef = useRef(null); @@ -93,7 +92,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { if (folderName && parentPath && canCreateFolder) { await createFolder({ - projectId: workspaceId, + projectId, path: parentPath, environment, name: folderName @@ -106,7 +105,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { return { ...(await createSecretV3({ environment, - workspaceId, + projectId, secretPath, secretKey: key, secretValue: value || "", @@ -176,7 +175,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { if (!secretKey || isWholeKeyHighlighted) { e.preventDefault(); - const keyStr = currentWorkspace.autoCapitalization ? key.toUpperCase() : key; + const keyStr = currentProject.autoCapitalization ? key.toUpperCase() : key; setValue("key", keyStr); if (value) { setValue("value", value); @@ -191,7 +190,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { try { const parsedSlug = slugSchema.parse(slug); await createWsTag.mutateAsync({ - workspaceID: workspaceId, + projectId, tagSlug: parsedSlug, tagColor: "" }); @@ -220,7 +219,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { }} placeholder="Type your secret name" onPaste={handlePaste} - autoCapitalization={currentWorkspace?.autoCapitalization} + autoCapitalization={currentProject?.autoCapitalization} /> { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined; + ) => { secret?: SecretV3RawSanitized; environmentInfo?: ProjectEnv } | undefined; scrollOffset: number; importedBy?: { environment: { name: string; slug: string }; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx index d15e96a91..5e1f27f57 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx @@ -10,7 +10,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2"; -import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { useToggle } from "@app/hooks"; import { useUpdateSecretV3 } from "@app/hooks/api"; @@ -37,7 +37,7 @@ export const formSchema = z.object({ type TFormSchema = z.infer; function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }: Props) { - const { currentWorkspace } = useWorkspace(); + const { currentProject, projectId } = useProject(); const { permission } = useProjectPermission(); const secrets = environments.map((env) => getSecretByKey(env.slug, secretKey)); @@ -68,7 +68,6 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } secret?.overrideAction === SecretActionType.Created || secret?.overrideAction === SecretActionType.Modified ); - const workspaceId = currentWorkspace?.id || ""; const [isSecNameCopied, setIsSecNameCopied] = useToggle(false); @@ -111,7 +110,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } return updateSecretV3({ environment: secret?.env, - workspaceId, + projectId, secretPath, secretKey: secret.key, secretValue: secret.value || "", @@ -163,7 +162,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } void; isSingleEnv?: boolean; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx index 14faa7e9e..048589827 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx @@ -8,11 +8,11 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, Modal, ModalContent, Spinner } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; -import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api"; +import { projectKeys, useGetWorkspaceById, useMigrateProjectToV3 } from "@app/hooks/api"; +import { ProjectVersion } from "@app/hooks/api/projects/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; enum ProjectUpgradeStatus { InProgress = "IN_PROGRESS", @@ -28,14 +28,14 @@ const formSchema = z.object({ export const SecretV2MigrationSection = () => { const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["migrationInfo"] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const queryClient = useQueryClient(); const { data: workspaceDetails, refetch } = useGetWorkspaceById( // if v3 no need to fetch - currentWorkspace?.version === ProjectVersion.V3 ? "" : currentWorkspace?.id || "", + currentProject?.version === ProjectVersion.V3 ? "" : currentProject?.id || "", { refetchInterval: - currentWorkspace?.upgradeStatus === ProjectUpgradeStatus.InProgress ? 2000 : false + currentProject?.upgradeStatus === ProjectUpgradeStatus.InProgress ? 2000 : false } ); const { membership } = useProjectPermission(); @@ -54,12 +54,12 @@ export const SecretV2MigrationSection = () => { createNotification({ type: "success", text: "Project upgrade completed successfully" }); migrateProjectToV3.reset(); queryClient.invalidateQueries({ - queryKey: workspaceKeys.getAllUserWorkspace() + queryKey: projectKeys.getAllUserProjects() }); } }, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]); - if (isProjectUpgraded || currentWorkspace?.version === ProjectVersion.V3) return null; + if (isProjectUpgraded || currentProject?.version === ProjectVersion.V3) return null; const isUpgrading = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.InProgress; const didProjectUpgradeFailed = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.Failed; @@ -67,7 +67,7 @@ export const SecretV2MigrationSection = () => { const handleMigrationSecretV2 = async () => { try { handlePopUpToggle("migrationInfo"); - await migrateProjectToV3.mutateAsync({ workspaceId: currentWorkspace?.id || "" }); + await migrateProjectToV3.mutateAsync({ projectId: currentProject?.id || "" }); refetch(); createNotification({ text: "Project upgrade started", diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx index 11ee3def8..8f84cd612 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/SelectionPanel.tsx @@ -9,9 +9,9 @@ import { Button, DeleteActionModal, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; @@ -66,9 +66,8 @@ export const SelectionPanel = ({ ); const selectedCount = selectedFolderCount + selectedKeysCount; - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; - const userAvailableEnvs = currentWorkspace?.environments || []; + const { currentProject, projectId } = useProject(); + const userAvailableEnvs = currentProject?.environments || []; const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch(); const { mutateAsync: deleteFolder } = useDeleteFolder(); @@ -134,7 +133,7 @@ export const SelectionPanel = ({ folderId: folder?.id, path: secretPath, environment: env.slug, - projectId: workspaceId + projectId }); } }) @@ -173,7 +172,7 @@ export const SelectionPanel = ({ processedEntries += secretsToDelete.length; await deleteBatchSecretV3({ secretPath, - workspaceId, + projectId, environment: env.slug, secrets: secretsToDelete }); @@ -281,8 +280,8 @@ export const SelectionPanel = ({ isOpen={popUp.bulkMoveSecrets.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("bulkMoveSecrets", isOpen)} environments={userAvailableEnvs} - projectId={workspaceId} - projectSlug={currentWorkspace.slug} + projectId={projectId} + projectSlug={currentProject.slug} sourceSecretPath={secretPath} secrets={selectedEntries[EntryType.SECRET]} onComplete={resetSelectedEntries} diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/components/MoveSecretsDialog/MoveSecretsDialog.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/components/MoveSecretsDialog/MoveSecretsDialog.tsx index bb7862f09..30ea57957 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/components/MoveSecretsDialog/MoveSecretsDialog.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SelectionPanel/components/MoveSecretsDialog/MoveSecretsDialog.tsx @@ -30,13 +30,13 @@ import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionCo import { useDebounce } from "@app/hooks"; import { useMoveSecrets } from "@app/hooks/api"; import { useGetProjectSecretsQuickSearch } from "@app/hooks/api/dashboard"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; projectId: string; projectSlug: string; sourceSecretPath: string; @@ -64,7 +64,6 @@ type MoveResults = { const Content = ({ onComplete, secrets, - projectSlug, environments, projectId, sourceSecretPath @@ -189,7 +188,6 @@ const Content = ({ try { const { isDestinationUpdated, isSourceUpdated } = await moveSecrets.mutateAsync({ - projectSlug, shouldOverwrite, sourceEnvironment: environment.slug, sourceSecretPath, diff --git a/frontend/src/pages/secret-manager/OverviewPage/route.tsx b/frontend/src/pages/secret-manager/OverviewPage/route.tsx index 968286e13..2be6c6f25 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/route.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/route.tsx @@ -6,7 +6,9 @@ import { OverviewPage } from "./OverviewPage"; const SecretOverviewPageQuerySchema = z.object({ search: z.string().catch(""), - secretPath: z.string().catch("/") + secretPath: z.string().catch("/"), + connectionId: z.string().optional(), + connectionName: z.string().optional() }); export const Route = createFileRoute( diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/SecretApprovalsPage.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/SecretApprovalsPage.tsx index bc82e9b90..77106a57c 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/SecretApprovalsPage.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/SecretApprovalsPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api"; import { AccessApprovalRequest } from "./components/AccessApprovalRequest"; @@ -20,11 +20,10 @@ enum TabSection { export const SecretApprovalsPage = () => { const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; - const projectSlug = currentWorkspace?.slug || ""; + const { currentProject, projectId } = useProject(); + const projectSlug = currentProject?.slug || ""; const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ - workspaceId: projectId + projectId }); const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({ projectSlug }); const defaultTab = @@ -67,7 +66,7 @@ export const SecretApprovalsPage = () => { - +
diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx index ad905c1f6..ca04665a5 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx @@ -39,10 +39,10 @@ import { Badge } from "@app/components/v2/Badge"; import { ProjectPermissionMemberActions, ProjectPermissionSub, + useProject, useProjectPermission, useSubscription, - useUser, - useWorkspace + useUser } from "@app/context"; import { getUserTablePreference, @@ -110,7 +110,7 @@ export const AccessApprovalRequest = ({ const { permission } = useProjectPermission(); const { user } = useUser(); const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: members } = useGetWorkspaceUsers(projectId, true); const membersGroupById = members?.reduce>( @@ -410,7 +410,7 @@ export const AccessApprovalRequest = ({ Select an Environment - {currentWorkspace?.environments.map(({ slug, name }) => ( + {currentProject?.environments.map(({ slug, name }) => ( setEnvFilter((state) => (state === slug ? undefined : slug))} key={`request-filter-${slug}`} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index e7269298a..a9691724d 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -25,7 +25,7 @@ import { Tooltip } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; -import { ProjectPermissionActions, useUser, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, useProject, useUser } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useListWorkspaceGroups, useReviewAccessRequest } from "@app/hooks/api"; import { @@ -102,8 +102,8 @@ export const ReviewAccessRequestModal = ({ const [bypassApproval, setBypassApproval] = useState(false); const [bypassReason, setBypassReason] = useState(""); - const { currentWorkspace } = useWorkspace(); - const { data: groupMemberships = [] } = useListWorkspaceGroups(currentWorkspace?.id || ""); + const { currentProject } = useProject(); + const { data: groupMemberships = [] } = useListWorkspaceGroups(currentProject?.id || ""); const { user } = useUser(); const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["editRequest"] as const); diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx index 3d292f9a2..bb20643e8 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx @@ -40,9 +40,9 @@ import { import { ProjectPermissionSub, TProjectPermission, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { ProjectPermissionActions } from "@app/context/ProjectPermissionContext/types"; import { @@ -59,14 +59,14 @@ import { import { useGetAccessApprovalPolicies } from "@app/hooks/api/accessApproval/queries"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { PolicyType } from "@app/hooks/api/policies/enums"; -import { TAccessApprovalPolicy, Workspace } from "@app/hooks/api/types"; +import { Project, TAccessApprovalPolicy } from "@app/hooks/api/types"; import { AccessPolicyForm } from "./components/AccessPolicyModal"; import { ApprovalPolicyRow } from "./components/ApprovalPolicyRow"; import { RemoveApprovalPolicyModal } from "./components/RemoveApprovalPolicyModal"; interface IProps { - workspaceId: string; + projectId: string; } enum PolicyOrderBy { @@ -81,24 +81,24 @@ type PolicyFilters = { environmentIds: string[]; }; -const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?: Workspace) => { +const useApprovalPolicies = (permission: TProjectPermission, currentProject?: Project) => { const { data: accessPolicies, isPending: isAccessPoliciesLoading } = useGetAccessApprovalPolicies( { - projectSlug: currentWorkspace?.slug as string, + projectSlug: currentProject?.slug as string, options: { enabled: permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && - !!currentWorkspace?.slug + !!currentProject?.slug } } ); const { data: secretPolicies, isPending: isSecretPoliciesLoading } = useGetSecretApprovalPolicies( { - workspaceId: currentWorkspace?.id as string, + projectId: currentProject?.id as string, options: { enabled: permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && - !!currentWorkspace?.id + !!currentProject?.id } } ); @@ -118,7 +118,7 @@ const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?: }; }; -export const ApprovalPolicyList = ({ workspaceId }: IProps) => { +export const ApprovalPolicyList = ({ projectId }: IProps) => { const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp([ "policyForm", "deletePolicy", @@ -126,14 +126,14 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { ] as const); const { permission } = useProjectPermission(); const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: members } = useGetWorkspaceUsers(workspaceId, true); - const { data: groups } = useListWorkspaceGroups(currentWorkspace?.id || ""); + const { data: members } = useGetWorkspaceUsers(projectId, true); + const { data: groups } = useListWorkspaceGroups(currentProject?.id || ""); const { policies, isLoading: isPoliciesLoading } = useApprovalPolicies( permission, - currentWorkspace + currentProject ); const [filters, setFilters] = useState({ @@ -367,7 +367,7 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { Change Policy Environment - {currentWorkspace.environments.map((env) => ( + {currentProject.environments.map((env) => ( { e.preventDefault(); @@ -466,7 +466,7 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { )} - {!!currentWorkspace && + {!!currentProject && filteredPolicies ?.slice(offset, perPage * page) .map((policy) => ( @@ -497,8 +497,8 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
handlePopUpToggle("policyForm", isOpen)} members={members} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 856c16ee1..b78b8aa0c 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -22,7 +22,7 @@ import { Tooltip } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { getMemberLabel } from "@app/helpers/members"; import { policyDetails } from "@app/helpers/policies"; import { @@ -207,10 +207,10 @@ const Form = ({ name: "sequenceApprovers" }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: groups } = useListWorkspaceGroups(projectId); - const availableEnvironments = currentWorkspace?.environments || []; + const availableEnvironments = currentProject?.environments || []; const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy; const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy(); @@ -246,7 +246,7 @@ const Form = ({ approvers: [...userApprovers, ...groupApprovers], bypassers: bypassers.length > 0 ? bypassers : undefined, environments: environments.map((env) => env.slug), - workspaceId: currentWorkspace?.id || "" + projectId: currentProject?.id || "" }); } else { await createAccessApprovalPolicy({ @@ -302,7 +302,7 @@ const Form = ({ ...data, approvers: [...userApprovers, ...groupApprovers], bypassers: bypassers.length > 0 ? bypassers : undefined, - workspaceId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", environments: environments.map((env) => env.slug) }); } else { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index 89a97003e..13147b37b 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -33,13 +33,13 @@ import { Approver } from "@app/hooks/api/accessApproval/types"; import { TGroupMembership } from "@app/hooks/api/groups/types"; import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums"; import { ApproverType } from "@app/hooks/api/secretApproval/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { TWorkspaceUser } from "@app/hooks/api/users/types"; interface IPolicy { id: string; name: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; projectId?: string; secretPath?: string; approvals: number; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx index d267ffc78..c9f92f6ed 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx @@ -4,7 +4,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { DeleteActionModal, Spinner } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useDeleteAccessApprovalPolicy, useDeleteSecretApprovalPolicy, @@ -29,18 +29,18 @@ export const RemoveApprovalPolicyModal = ({ const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy(); const { mutateAsync: deleteAccessApprovalPolicy } = useDeleteAccessApprovalPolicy(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleDeletePolicy = async () => { try { if (policyType === PolicyType.ChangePolicy) { await deleteSecretApprovalPolicy({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, id: policyId }); } else { await deleteAccessApprovalPolicy({ - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, id: policyId }); } @@ -59,14 +59,14 @@ export const RemoveApprovalPolicyModal = ({ const deleteSecretApprovalData = useGetSecretApprovalRequestCount({ policyId, - workspaceId: currentWorkspace.id, + projectId: currentProject.id, options: { enabled: Boolean(policyId) && policyType === PolicyType.ChangePolicy } }); const deleteAccessApprovalData = useGetAccessRequestsCount({ - projectSlug: currentWorkspace.slug, + projectSlug: currentProject.slug, policyId, options: { enabled: Boolean(policyId) && policyType === PolicyType.AccessPolicy diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx index 46b611328..827d14f1e 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx @@ -35,9 +35,9 @@ import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionMemberActions, ProjectPermissionSub, + useProject, useProjectPermission, - useUser, - useWorkspace + useUser } from "@app/context"; import { getUserTablePreference, @@ -58,8 +58,7 @@ import { } from "./components/SecretApprovalRequestChanges"; export const SecretApprovalRequest = () => { - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; + const { currentProject, projectId } = useProject(); const [selectedApprovalId, setSelectedApprovalId] = useState(null); // filters @@ -92,7 +91,7 @@ export const SecretApprovalRequest = () => { isPending: isApprovalRequestLoading, refetch } = useGetSecretApprovalRequests({ - workspaceId, + projectId, status: statusFilter, environment: envFilter, committer: committerFilter, @@ -105,14 +104,14 @@ export const SecretApprovalRequest = () => { const secretApprovalRequests = data?.approvals ?? []; const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } = - useGetSecretApprovalRequestCount({ workspaceId }); + useGetSecretApprovalRequestCount({ projectId }); const { user: userSession } = useUser(); const search = useSearch({ from: ROUTE_PATHS.SecretManager.ApprovalPage.id }); const { permission } = useProjectPermission(); - const { data: members } = useGetWorkspaceUsers(workspaceId); + const { data: members } = useGetWorkspaceUsers(projectId); const isSecretApprovalScreen = Boolean(selectedApprovalId); const { requestId } = search; @@ -143,7 +142,6 @@ export const SecretApprovalRequest = () => { exit={{ opacity: 0, translateX: 30 }} > @@ -241,7 +239,7 @@ export const SecretApprovalRequest = () => { Select an Environment - {currentWorkspace?.environments.map(({ slug, name }) => ( + {currentProject?.environments.map(({ slug, name }) => ( setEnvFilter((state) => (state === slug ? undefined : slug))} key={`request-filter-${slug}`} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx index ee45a442b..9bafb36d4 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx @@ -13,6 +13,7 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, FormControl, Input } from "@app/components/v2"; +import { useProject } from "@app/context"; import { usePerformSecretApprovalRequestMerge, useUpdateSecretApprovalRequestStatus @@ -28,7 +29,6 @@ type Props = { canApprove?: boolean; isBypasser: boolean; statusChangeByEmail?: string; - workspaceId: string; enforcementLevel: EnforcementLevel; }; @@ -39,11 +39,11 @@ export const SecretApprovalRequestAction = ({ isMergable, approvals, statusChangeByEmail, - workspaceId, enforcementLevel, canApprove, isBypasser }: Props) => { + const { projectId } = useProject(); const { mutateAsync: performSecretApprovalMerge, isPending: isMerging } = usePerformSecretApprovalRequestMerge(); @@ -62,7 +62,7 @@ export const SecretApprovalRequestAction = ({ try { await performSecretApprovalMerge({ id: approvalRequestId, - workspaceId, + projectId, bypassReason: byPassApproval ? bypassReason : undefined }); createNotification({ @@ -83,7 +83,7 @@ export const SecretApprovalRequestAction = ({ await updateSecretStatusChange({ id: approvalRequestId, status: reqState, - workspaceId + projectId }); createNotification({ type: "success", diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 636952c33..0cce12c47 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -33,7 +33,7 @@ import { TextArea, Tooltip } from "@app/components/v2"; -import { useUser, useWorkspace } from "@app/context"; +import { useProject, useUser } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useGetSecretApprovalRequestDetails, @@ -92,7 +92,6 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => { }; type Props = { - workspaceId: string; approvalRequestId: string; onGoBack: () => void; }; @@ -104,13 +103,9 @@ const reviewFormSchema = z.object({ type TReviewFormSchema = z.infer; -export const SecretApprovalRequestChanges = ({ - approvalRequestId, - onGoBack, - workspaceId -}: Props) => { +export const SecretApprovalRequestChanges = ({ approvalRequestId, onGoBack }: Props) => { const { user: userSession } = useUser(); - const { currentWorkspace } = useWorkspace(); + const { projectId } = useProject(); const { data: secretApprovalRequestDetails, isSuccess: isSecretApprovalRequestSuccess, @@ -123,7 +118,7 @@ export const SecretApprovalRequestChanges = ({ ); const { data: secretImports } = useGetSecretImports({ environment: secretApprovalRequestDetails?.environment || "", - projectId: currentWorkspace.id, + projectId, path: approvalSecretPath }); @@ -526,7 +521,6 @@ export const SecretApprovalRequestChanges = ({ isMergable={isMergable} statusChangeByEmail={secretApprovalRequestDetails.statusChangedByUser?.email} enforcementLevel={secretApprovalRequestDetails.policy.enforcementLevel} - workspaceId={workspaceId} />
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index a6ae7be62..c926450eb 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -25,8 +25,8 @@ import { ProjectPermissionActions, ProjectPermissionDynamicSecretActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { ProjectPermissionCommitsActions, @@ -50,10 +50,10 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard"; import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types"; import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits"; import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { ProjectVersion } from "@app/hooks/api/projects/types"; import { PendingAction } from "@app/hooks/api/secretFolders/types"; import { useCreateCommit } from "@app/hooks/api/secrets/mutations"; import { SecretV3RawSanitized } from "@app/hooks/api/types"; -import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { usePathAccessPolicies } from "@app/hooks/usePathAccessPolicies"; import { useResizableColWidth } from "@app/hooks/useResizableColWidth"; import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; @@ -94,7 +94,7 @@ const LOADER_TEXT = [ ]; const Page = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path }); @@ -142,15 +142,15 @@ const Page = () => { ] as const); // env slug - const workspaceId = currentWorkspace?.id || ""; - const projectSlug = currentWorkspace?.slug || ""; + const projectId = currentProject?.id || ""; + const projectSlug = currentProject?.slug || ""; const secretPath = (routerQueryParams.secretPath as string) || "/"; useEffect(() => { - if (isBatchMode && workspaceId && environment && secretPath) { - loadPendingChanges({ workspaceId, environment, secretPath }); + if (isBatchMode && projectId && environment && secretPath) { + loadPendingChanges({ projectId, environment, secretPath }); } - }, [isBatchMode, workspaceId, environment, secretPath, loadPendingChanges]); + }, [isBatchMode, projectId, environment, secretPath, loadPendingChanges]); const canReadSecret = hasSecretReadValueOrDescribePermission( permission, @@ -240,7 +240,7 @@ const Page = () => { const { togglePopUp } = usePopUpAction(); useEffect(() => { - if (!currentWorkspace?.environments.find((env) => env.slug === environment)) { + if (!currentProject?.environments.find((env) => env.slug === environment)) { createNotification({ text: "No environment found with given slug", type: "error" @@ -248,11 +248,11 @@ const Page = () => { navigate({ to: "/projects/secret-management/$projectId/overview", params: { - projectId: workspaceId + projectId } }); } - }, [currentWorkspace, environment]); + }, [currentProject, environment]); const isResourceTypeFiltered = Object.values(filter.include).some(Boolean); const { @@ -262,7 +262,7 @@ const Page = () => { isFetched } = useGetProjectSecretsDetails({ environment, - projectId: workspaceId, + projectId, secretPath, offset, limit, @@ -316,7 +316,7 @@ const Page = () => { // fetch imported secrets to show user the overriden ones const { data: importedSecrets } = useGetImportedSecretsSingleEnv({ - projectId: workspaceId, + projectId, environment, path: secretPath, options: { @@ -326,13 +326,13 @@ const Page = () => { // fetch tags const { data: tags } = useGetWsTags( - permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : "" + permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? projectId : "" ); const { pathPolicies, hasPathPolicies } = usePathAccessPolicies({ secretPath, environment }); const { data: boardPolicy } = useGetSecretApprovalPolicyOfABoard({ - workspaceId, + projectId, environment, secretPath }); @@ -341,7 +341,7 @@ const Page = () => { const handleCreateCommit = async (changes: PendingChanges, message: string) => { try { await createCommit({ - workspaceId, + projectId, environment, secretPath, pendingChanges: changes, @@ -368,7 +368,7 @@ const Page = () => { fetchNextPage: fetchNextSnapshotList, hasNextPage: hasNextSnapshotListPage } = useGetWorkspaceSnapshotList({ - workspaceId, + projectId, directory: secretPath, environment, isPaused: !popUp.snapshots.isOpen || !canDoReadRollback, @@ -381,7 +381,7 @@ const Page = () => { isFetching: isFolderCommitsCountFetching } = useGetFolderCommitsCount({ directory: secretPath, - workspaceId, + projectId, environment, isPaused: !canReadCommits }); @@ -391,13 +391,13 @@ const Page = () => { isPending: isSnapshotCountLoading, isFetching: isSnapshotCountFetching } = useGetWsSnapshotCount({ - workspaceId, + projectId, environment, directory: secretPath, isPaused: !canDoReadRollback }); - const isPITEnabled = !currentWorkspace?.showSnapshotsLegacy; + const isPITEnabled = !currentProject?.showSnapshotsLegacy; const changesCount = useMemo(() => { return isPITEnabled ? folderCommitsCount : snapshotCount; @@ -416,7 +416,7 @@ const Page = () => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId", params: { - projectId: workspaceId, + projectId, folderId, environment }, @@ -647,7 +647,7 @@ const Page = () => { ? change.tags?.map((tag) => ({ id: tag.id, slug: tag.slug, - projectId: workspaceId, + projectId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), __v: 0 @@ -734,7 +734,7 @@ const Page = () => { const mergedSecrets = getMergedSecretsWithPending(); const mergedFolders = getMergedFoldersWithPending(); - if (!(currentWorkspace?.version === ProjectVersion.V3)) + if (!(currentProject?.version === ProjectVersion.V3)) return (
@@ -794,8 +794,6 @@ const Page = () => { <> { secretImports={imports} isFetching={isDetailsFetching} environment={environment} - workspaceId={workspaceId} + projectId={projectId} secretPath={secretPath} importedSecrets={importedSecrets} /> @@ -966,7 +964,7 @@ const Page = () => { { tags={tags} isVisible={isVisible} environment={environment} - workspaceId={workspaceId} + projectId={projectId} secretPath={secretPath} isProtectedBranch={isProtectedBranch} importedBy={importedBy} @@ -1001,7 +999,7 @@ const Page = () => { @@ -1052,9 +1050,9 @@ const Page = () => { > @@ -1073,10 +1071,10 @@ const Page = () => { )} { { - return `${workspaceId}_${environment}_${secretPath}`; +const generateContextKey = (projectId: string, environment: string, secretPath: string) => { + return `${projectId}_${environment}_${secretPath}`; }; const createBatchModeStore: StateCreator = (set, get) => ({ @@ -272,7 +272,7 @@ const createBatchModeStore: StateCreator addPendingChange: (change: PendingChange, context: BatchContext) => set((state) => { const contextKey = generateContextKey( - context.workspaceId, + context.projectId, context.environment, context.secretPath ); @@ -551,7 +551,7 @@ const createBatchModeStore: StateCreator const currentChanges = contextKey === generateContextKey( - state.currentContext?.workspaceId || context.workspaceId, + state.currentContext?.projectId || context.projectId, state.currentContext?.environment || context.environment, state.currentContext?.secretPath || context.secretPath ) @@ -568,7 +568,7 @@ const createBatchModeStore: StateCreator removePendingChange: (changeId: string, resourceType: string, context: BatchContext) => set((state) => { const contextKey = generateContextKey( - context.workspaceId, + context.projectId, context.environment, context.secretPath ); @@ -593,7 +593,7 @@ const createBatchModeStore: StateCreator state.currentContext && contextKey === generateContextKey( - state.currentContext.workspaceId, + state.currentContext.projectId, state.currentContext.environment, state.currentContext.secretPath ); @@ -606,7 +606,7 @@ const createBatchModeStore: StateCreator loadPendingChanges: (context) => { const contextKey = generateContextKey( - context.workspaceId, + context.projectId, context.environment, context.secretPath ); @@ -626,7 +626,7 @@ const createBatchModeStore: StateCreator clearAllPendingChanges: (context) => { const contextKey = generateContextKey( - context.workspaceId, + context.projectId, context.environment, context.secretPath ); @@ -641,7 +641,7 @@ const createBatchModeStore: StateCreator state.currentContext && contextKey === generateContextKey( - state.currentContext.workspaceId, + state.currentContext.projectId, state.currentContext.environment, state.currentContext.secretPath ); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 9252d71a1..226230c1a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -55,9 +55,9 @@ import { ProjectPermissionActions, ProjectPermissionDynamicSecretActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { ProjectPermissionCommitsActions, @@ -109,9 +109,6 @@ type TSecOverwriteOpt = { update: TParsedEnv; create: TParsedEnv }; type Props = { // switch the secrets type as it gets decrypted after api call environment: string; - // @depreciated will be moving all these details to zustand - workspaceId: string; - projectSlug: string; secretPath?: string; filter: Filter; tags?: WsTag[]; @@ -142,8 +139,6 @@ type Props = { export const ActionBar = ({ environment, - workspaceId, - projectSlug, secretPath = "/", filter, tags = [], @@ -164,6 +159,7 @@ export const ActionBar = ({ hasPathPolicies, onClearFilters }: Props) => { + const { projectId, currentProject } = useProject(); const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addFolder", "addDynamicSecret", @@ -196,7 +192,6 @@ export const ActionBar = ({ const { reset: resetSelectedSecret } = useSelectedSecretActions(); const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length); - const { currentWorkspace } = useWorkspace(); const { permission } = useProjectPermission(); const handleFolderCreate = async (folderName: string, description: string | null) => { @@ -214,7 +209,7 @@ export const ActionBar = ({ }; addPendingChange(pendingFolderCreate, { - workspaceId, + projectId, environment, secretPath }); @@ -227,7 +222,7 @@ export const ActionBar = ({ name: folderName, path: secretPath, environment, - projectId: workspaceId, + projectId, description }); handlePopUpClose("addFolder"); @@ -247,7 +242,7 @@ export const ActionBar = ({ const handleSecretDownload = async () => { try { const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({ - workspaceId, + projectId, expandSecretReferences: true, includeImports: true, environment, @@ -317,7 +312,7 @@ export const ActionBar = ({ try { await deleteBatchSecretV3({ secretPath, - workspaceId, + projectId, environment, secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) }); @@ -348,13 +343,12 @@ export const ActionBar = ({ try { const secretsToMove = Object.values(selectedSecrets); const { isDestinationUpdated, isSourceUpdated } = await moveSecrets({ - projectSlug, shouldOverwrite, sourceEnvironment: environment, sourceSecretPath: secretPath, destinationEnvironment, destinationSecretPath, - projectId: workspaceId, + projectId, secretIds: secretsToMove.map((sec) => sec.id) }); @@ -448,7 +442,7 @@ export const ActionBar = ({ const { secrets: batchSecrets } = await fetchDashboardProjectSecretsByKeys({ secretPath: normalizedPath, environment, - projectId: workspaceId, + projectId, keys: batch }); @@ -566,7 +560,7 @@ export const ActionBar = ({ name: folderName, path: parentPath, environment, - projectId: workspaceId + projectId }); createdFolders.add(fullPath); @@ -598,7 +592,7 @@ export const ActionBar = ({ Object.entries(groupedCreateSecrets).map(([path, secrets]) => createSecretBatch({ secretPath: path, - workspaceId, + projectId, environment, secrets }) @@ -628,7 +622,7 @@ export const ActionBar = ({ Object.entries(groupedUpdateSecrets).map(([path, secrets]) => updateSecretBatch({ secretPath: path, - workspaceId, + projectId, environment, secrets }) @@ -638,13 +632,12 @@ export const ActionBar = ({ // Invalidate appropriate queries to refresh UI queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) }); + queryClient.invalidateQueries({}); + dashboardKeys.getDashboardSecrets({ projectId, secretPath }); queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId: workspaceId, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ workspaceId }) + queryKey: secretApprovalRequestKeys.count({ projectId }) }); // Close the modal and show notification @@ -677,8 +670,8 @@ export const ActionBar = ({ className="w-2/5" value={filter.searchFilter} onChange={onSearchChange} - environments={[currentWorkspace.environments.find((env) => env.slug === environment)!]} - projectId={workspaceId} + environments={[currentProject.environments.find((env) => env.slug === environment)!]} + projectId={projectId} tags={tags} />
@@ -1134,8 +1127,8 @@ export const ActionBar = ({ {/* all the side triggers from actions like modals etc */} handlePopUpOpen("upgradePlan")} isOpen={popUp.addSecretImport.isOpen} onClose={() => handlePopUpClose("addSecretImport")} @@ -1144,7 +1137,7 @@ export const ActionBar = ({ handlePopUpToggle("addDynamicSecret", isOpen)} - projectSlug={projectSlug} + projectSlug={currentProject.slug} environments={[{ slug: environment, name: environment, id: "not-used" }]} secretPath={secretPath} isSingleEnvironmentMode @@ -1190,8 +1183,8 @@ export const ActionBar = ({ onToggle={(isOpen) => handlePopUpToggle("replicateFolder", isOpen)} onParsedEnv={handleParsedEnvMultiFolder} environment={environment} - environments={currentWorkspace.environments} - workspaceId={workspaceId} + environments={currentProject.environments} + projectId={projectId} secretPath={secretPath} /> {subscription && ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx index 84cf7f7b5..e458f347c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx @@ -19,7 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -63,7 +63,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx index f9ed9967c..a50352f58 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx @@ -20,7 +20,7 @@ import { DynamicSecretAwsIamAuth, DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { MetadataForm } from "../../DynamicSecretListView/MetadataForm"; @@ -112,7 +112,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx index 771523828..6c4ccd65b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx @@ -18,7 +18,7 @@ import { Tooltip } from "@app/components/v2/Tooltip"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; const formSchema = z.object({ selectedUsers: z.array( @@ -66,7 +66,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx index 8f99d368c..0ef4d092d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx @@ -19,7 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -66,7 +66,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx index fd2eecaa6..1b42dc7e0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx @@ -24,7 +24,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; // Component for managing scopes and collections within a bucket @@ -265,7 +265,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx index abe14d82d..dd169727d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx @@ -24,7 +24,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { Modal, ModalContent } from "@app/components/v2"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm"; import { AwsIamInputForm } from "./AwsIamInputForm"; @@ -51,7 +51,7 @@ type Props = { isOpen?: boolean; onToggle: (isOpen: boolean) => void; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; secretPath: string; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index 51dcea68b..390909e81 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -20,7 +20,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const authMethods = [ @@ -87,7 +87,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx index f47273aa1..e7518830d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx @@ -8,7 +8,7 @@ import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; const validateTTL = (val: string, ctx: z.RefinementCtx) => { if (!val) return; @@ -49,7 +49,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx index 800bc677a..acac4e0aa 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx @@ -16,7 +16,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -44,7 +44,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index 2b0838246..945e46777 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -28,7 +28,7 @@ import { DynamicSecretProviders, KubernetesDynamicSecretCredentialType } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; enum RoleType { @@ -150,7 +150,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index f2f5c9233..62f8cdd96 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -16,7 +16,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; enum CredentialType { @@ -89,7 +89,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index 821730b08..05109e5a3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -23,7 +23,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -77,7 +77,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx index 5a89f4649..646755a8f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx @@ -18,7 +18,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -67,7 +67,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx index f80c374d8..1c2db7d2a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -18,7 +18,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -71,7 +71,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx index 4d0c51194..06bf4e41a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx @@ -19,7 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -64,7 +64,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx index 413bbe0b6..507e67820 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx @@ -18,7 +18,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -62,7 +62,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx index deea1ada1..38ddbc7f0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx @@ -19,7 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -64,7 +64,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx index 3120d7cef..30f01c13f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx @@ -18,7 +18,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ @@ -62,7 +62,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx index 050a51754..b0c18fbd3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx @@ -29,7 +29,7 @@ import { } from "@app/context/OrgPermissionContext/types"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; import { MetadataForm } from "../../DynamicSecretListView/MetadataForm"; @@ -110,7 +110,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx index 1ec9f8a0c..63680f524 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx @@ -13,7 +13,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; enum ConfigType { @@ -66,7 +66,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx index d5443b1ed..ca417adb6 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx @@ -27,7 +27,7 @@ import { } from "@app/context/OrgPermissionContext/types"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; -import { WorkspaceEnv } from "@app/hooks/api/types"; +import { ProjectEnv } from "@app/hooks/api/types"; const passwordRequirementsSchema = z .object({ @@ -91,7 +91,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environments: WorkspaceEnv[]; + environments: ProjectEnv[]; isSingleEnvironmentMode?: boolean; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx index 9bdace648..68d2d8cd0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx @@ -14,7 +14,7 @@ import { SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; -import { useSubscription, useWorkspace } from "@app/context"; +import { useProject, useSubscription } from "@app/context"; import { useCreateSecretImport } from "@app/hooks/api"; const typeSchema = z.object({ @@ -32,7 +32,7 @@ type TFormSchema = z.infer; type Props = { environment: string; - workspaceId: string; + projectId: string; secretPath?: string; // modal props isOpen?: boolean; @@ -43,7 +43,7 @@ type Props = { export const CreateSecretImportForm = ({ environment, - workspaceId, + projectId, secretPath = "/", isOpen, onClose, @@ -57,8 +57,8 @@ export const CreateSecretImportForm = ({ watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(typeSchema) }); - const { currentWorkspace } = useWorkspace(); - const environments = currentWorkspace?.environments || []; + const { currentProject } = useProject(); + const environments = currentProject?.environments || []; const selectedEnvironment = watch("environment"); const { subscription } = useSubscription(); @@ -77,7 +77,7 @@ export const CreateSecretImportForm = ({ await createSecretImport({ environment, - projectId: workspaceId, + projectId, path: secretPath, isReplication, import: { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/MoveSecretsModal.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/MoveSecretsModal.tsx index 606f23fac..05f00315d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/MoveSecretsModal.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/MoveSecretsModal.tsx @@ -12,7 +12,7 @@ import { SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -47,8 +47,8 @@ export const MoveSecretsModal = ({ popUp, handlePopUpToggle, onMoveApproved }: P formState: { isSubmitting } } = useForm({ resolver: zodResolver(formSchema) }); - const { currentWorkspace } = useWorkspace(); - const environments = currentWorkspace?.environments || []; + const { currentProject } = useProject(); + const environments = currentProject?.environments || []; const selectedEnvironment = watch("environment"); const handleFormSubmit = (data: TFormSchema) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx index 339612a89..5e4cee953 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx @@ -48,7 +48,7 @@ type Props = { env: Record> ) => void; environments?: { name: string; slug: string }[]; - workspaceId: string; + projectId: string; environment: string; secretPath: string; }; @@ -64,7 +64,7 @@ type SecretStructure = { export const ReplicateFolderFromBoard = ({ environments = [], - workspaceId, + projectId, isOpen, onToggle, onParsedEnv @@ -82,14 +82,14 @@ export const ReplicateFolderFromBoard = ({ const [debouncedEnvCopySecretPath] = useDebounce(envCopySecPath); const { data: accessibleSecrets } = useGetAccessibleSecrets({ - projectId: workspaceId, + projectId, secretPath: "/", environment: selectedEnvSlug.slug, recursive: true, filterByAction: shouldIncludeValues ? ProjectPermissionSecretActions.ReadValue : ProjectPermissionSecretActions.DescribeSecret, - options: { enabled: Boolean(workspaceId) && Boolean(selectedEnvSlug) && isOpen } + options: { enabled: Boolean(projectId) && Boolean(selectedEnvSlug) && isOpen } }); const restructureSecrets = useMemo(() => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx index c3f5e74ee..f81cd7b2d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx @@ -25,14 +25,14 @@ interface CommitFormProps { onCommit: (changes: PendingChanges, commitMessage: string) => Promise; isCommitting?: boolean; environment: string; - workspaceId: string; + projectId: string; secretPath: string; } interface ResourceChangeProps { change: PendingChange; environment: string; - workspaceId: string; + projectId: string; secretPath: string; } @@ -237,7 +237,7 @@ const RenderFolderChanges = ({ onDiscard, change }: RenderResourceProps) => { const ResourceChange: React.FC = ({ change, environment, - workspaceId, + projectId, secretPath }) => { const { removePendingChange } = useBatchModeActions(); @@ -245,7 +245,7 @@ const ResourceChange: React.FC = ({ const handleDeletePending = useCallback( (changeType: string, id: string) => { removePendingChange(id, changeType, { - workspaceId, + projectId, environment, secretPath }); @@ -272,7 +272,7 @@ export const CommitForm: React.FC = ({ onCommit, isCommitting = false, environment, - workspaceId, + projectId, secretPath }) => { const { isBatchMode, pendingChanges, totalChangesCount } = useBatchMode(); @@ -291,7 +291,7 @@ export const CommitForm: React.FC = ({ } await onCommit(pendingChanges, commitMessage); clearAllPendingChanges({ - workspaceId, + projectId, environment, secretPath }); @@ -335,9 +335,7 @@ export const CommitForm: React.FC = ({
); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx index 0832dfe9d..611504a14 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx @@ -4,15 +4,15 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateWsEnvironment } from "@app/hooks/api"; -import { WorkspaceEnv } from "@app/hooks/api/workspace/types"; +import { ProjectEnv } from "@app/hooks/api/projects/types"; import { slugSchema } from "@app/lib/schemas"; type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; - onComplete?: (environment: WorkspaceEnv) => void; + onComplete?: (environment: ProjectEnv) => void; }; const schema = z.object({ @@ -25,11 +25,11 @@ const schema = z.object({ export type FormData = z.infer; type ContentProps = { - onComplete: (environment: WorkspaceEnv) => void; + onComplete: (environment: ProjectEnv) => void; }; const Content = ({ onComplete }: ContentProps) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync, isPending } = useCreateWsEnvironment(); const { control, handleSubmit } = useForm({ resolver: zodResolver(schema) @@ -37,10 +37,10 @@ const Content = ({ onComplete }: ContentProps) => { const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; const env = await mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, name: environmentName, slug: environmentSlug }); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx index 34acf0c7b..016266e69 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx @@ -8,9 +8,9 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, + useProject, useProjectPermission, - useSubscription, - useWorkspace + useSubscription } from "@app/context"; import { useDeleteWsEnvironment } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -21,14 +21,14 @@ import { UpdateEnvironmentModal } from "./UpdateEnvironmentModal"; export const EnvironmentSection = () => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { permission } = useProjectPermission(); const deleteWsEnvironment = useDeleteWsEnvironment(); const isMoreEnvironmentsAllowed = - subscription?.environmentLimit && currentWorkspace?.environments - ? currentWorkspace.environments.length < subscription.environmentLimit + subscription?.environmentLimit && currentProject?.environments + ? currentProject.environments.length < subscription.environmentLimit : true; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -40,10 +40,10 @@ export const EnvironmentSection = () => { const onEnvDeleteSubmit = async (id: string) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await deleteWsEnvironment.mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, id }); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index 0626db281..0908d8377 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -18,8 +18,8 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, - useSubscription, - useWorkspace + useProject, + useSubscription } from "@app/context"; import { useUpdateWsEnvironment } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -40,17 +40,17 @@ type Props = { }; export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { subscription } = useSubscription(); const updateEnvironment = useUpdateWsEnvironment(); const handleReorderEnv = async (id: string, position: number) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await updateEnvironment.mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, id, position }); @@ -69,13 +69,13 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { }; const isMoreEnvironmentsAllowed = - subscription?.environmentLimit && currentWorkspace?.environments - ? currentWorkspace.environments.length <= subscription.environmentLimit + subscription?.environmentLimit && currentProject?.environments + ? currentProject.environments.length <= subscription.environmentLimit : true; const environmentsOverPlanLimit = - subscription?.environmentLimit && currentWorkspace?.environments - ? Math.max(0, currentWorkspace.environments.length - subscription.environmentLimit) + subscription?.environmentLimit && currentProject?.environments + ? Math.max(0, currentProject.environments.length - subscription.environmentLimit) : 0; return ( @@ -89,7 +89,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { - {currentWorkspace.environments.map(({ name, slug, id }, pos) => ( + {currentProject.environments.map(({ name, slug, id }, pos) => ( {name} {slug} @@ -102,15 +102,12 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { - handleReorderEnv( - id, - Math.min(currentWorkspace.environments.length, pos + 2) - ) + handleReorderEnv(id, Math.min(currentProject.environments.length, pos + 2)) } colorSchema="primary" variant="plain" ariaLabel="update" - isDisabled={pos === currentWorkspace.environments.length - 1 || !isAllowed} + isDisabled={pos === currentProject.environments.length - 1 || !isAllowed} > @@ -183,7 +180,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { ))} - {currentWorkspace.environments?.length === 0 && ( + {currentProject.environments?.length === 0 && ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx index f40091303..e2d64ee72 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx @@ -4,7 +4,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useUpdateWsEnvironment } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { slugSchema } from "@app/lib/schemas"; @@ -23,7 +23,7 @@ const schema = z.object({ export type FormData = z.infer; export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync, isPending } = useUpdateWsEnvironment(); const { control, handleSubmit, reset } = useForm({ resolver: zodResolver(schema), @@ -34,10 +34,10 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog const onFormSubmit = async ({ name, slug }: FormData) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await mutateAsync({ - workspaceId: currentWorkspace.id, + projectId: currentProject.id, name, slug, id: oldEnvId diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx index 288500d26..bd8d15887 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx @@ -4,9 +4,9 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; +import { useUpdateProject } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { useUpdateWorkspaceVersionLimit } from "@app/hooks/api/workspace/queries"; const formSchema = z.object({ pitVersionLimit: z.coerce.number().min(1).max(100) @@ -15,9 +15,9 @@ const formSchema = z.object({ type TForm = z.infer; export const PointInTimeVersionLimitSection = () => { - const { mutateAsync: updatePitVersion } = useUpdateWorkspaceVersionLimit(); + const { mutateAsync: updateProject } = useUpdateProject(); - const { currentWorkspace } = useWorkspace(); + const { currentProject, projectId } = useProject(); const { membership } = useProjectPermission(); const { @@ -27,17 +27,17 @@ export const PointInTimeVersionLimitSection = () => { } = useForm({ resolver: zodResolver(formSchema), values: { - pitVersionLimit: currentWorkspace?.pitVersionLimit || 10 + pitVersionLimit: currentProject?.pitVersionLimit || 10 } }); - if (!currentWorkspace) return null; + if (!currentProject) return null; const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => { try { - await updatePitVersion({ + await updateProject({ pitVersionLimit, - projectSlug: currentWorkspace.slug + projectId }); createNotification({ diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx index 5869aa127..a7c6bbc36 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx @@ -7,7 +7,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, IconButton, Input } from "@app/components/v2"; -import { useProjectPermission, useWorkspace } from "@app/context"; +import { useProject, useProjectPermission } from "@app/context"; import { useUpdateProject } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; @@ -23,7 +23,7 @@ const formSchema = z.object({ type TForm = z.infer; export const SecretDetectionIgnoreValuesSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { membership } = useProjectPermission(); const { mutateAsync: updateProject } = useUpdateProject(); @@ -45,19 +45,19 @@ export const SecretDetectionIgnoreValuesSection = () => { }); useEffect(() => { - const existingIgnoreValues = currentWorkspace?.secretDetectionIgnoreValues || []; + const existingIgnoreValues = currentProject?.secretDetectionIgnoreValues || []; reset({ ignoreValues: existingIgnoreValues.length > 0 ? existingIgnoreValues.map((value) => ({ value })) : [{ value: "" }] // Show one empty field by default }); - }, [currentWorkspace?.secretDetectionIgnoreValues, reset]); + }, [currentProject?.secretDetectionIgnoreValues, reset]); const handleIgnoreValuesSubmit = async ({ ignoreValues }: TForm) => { try { await updateProject({ - projectID: currentWorkspace.id, + projectId: currentProject.id, secretDetectionIgnoreValues: ignoreValues.map((item) => item.value) }); @@ -75,7 +75,7 @@ export const SecretDetectionIgnoreValuesSection = () => { const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); - if (!currentWorkspace) return null; + if (!currentProject) return null; return (
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx index 0dd145e41..83639d204 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx @@ -3,11 +3,11 @@ import { useState } from "react"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Checkbox } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useUpdateProject } from "@app/hooks/api/workspace/queries"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; +import { useUpdateProject } from "@app/hooks/api/projects/queries"; export const SecretSharingSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: updateProject } = useUpdateProject(); const [isLoading, setIsLoading] = useState(false); @@ -16,13 +16,13 @@ export const SecretSharingSection = () => { setIsLoading(true); try { - if (!currentWorkspace?.id) { + if (!currentProject?.id) { setIsLoading(false); return; } await updateProject({ - projectID: currentWorkspace.id, + projectId: currentProject.id, secretSharing: state }); @@ -50,7 +50,7 @@ export const SecretSharingSection = () => { handleToggle(state as boolean)} > This feature enables your project members to securely share secrets. diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx index 69c2bcbfd..90b866e96 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx @@ -3,11 +3,11 @@ import { useState } from "react"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Checkbox } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useUpdateProject } from "@app/hooks/api/workspace/queries"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; +import { useUpdateProject } from "@app/hooks/api/projects/queries"; export const SecretSnapshotsLegacySection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync: updateProject } = useUpdateProject(); const [isLoading, setIsLoading] = useState(false); @@ -16,13 +16,13 @@ export const SecretSnapshotsLegacySection = () => { setIsLoading(true); try { - if (!currentWorkspace?.id) { + if (!currentProject?.id) { setIsLoading(false); return; } await updateProject({ - projectID: currentWorkspace.id, + projectId: currentProject.id, showSnapshotsLegacy: state }); @@ -50,7 +50,7 @@ export const SecretSnapshotsLegacySection = () => { handleToggle(state as boolean)} > This feature enables your project members to view secret snapshots in the legacy diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx index 96c667050..1d4c8e492 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx @@ -4,7 +4,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateWsTag } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { slugSchema } from "@app/lib/schemas"; @@ -27,7 +27,7 @@ type Props = { }; export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const createWsTag = useCreateWsTag(); const { control, @@ -40,10 +40,10 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } const onFormSubmit = async ({ slug }: FormData) => { try { - if (!currentWorkspace?.id) return; + if (!currentProject?.id) return; await createWsTag.mutateAsync({ - workspaceID: currentWorkspace?.id, + projectId: currentProject?.id, tagSlug: slug, tagColor: "" }); @@ -73,7 +73,7 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } }} >
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx index 26be8eb17..55da0f03b 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx @@ -7,8 +7,8 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, - useProjectPermission, - useWorkspace + useProject, + useProjectPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteWsTag } from "@app/hooks/api"; @@ -23,7 +23,7 @@ export const SecretTagsSection = (): JSX.Element => { "CreateSecretTag", "deleteTagConfirmation" ] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { permission } = useProjectPermission(); const deleteWsTag = useDeleteWsTag(); @@ -31,7 +31,7 @@ export const SecretTagsSection = (): JSX.Element => { const onDeleteApproved = async () => { try { await deleteWsTag.mutateAsync({ - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id }); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx index d0633eb12..15d8ef7cb 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx @@ -24,7 +24,7 @@ import { THead, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getUserTablePreference, PreferenceKey, @@ -53,8 +53,8 @@ enum TagsOrderBy { } export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const { data: tags = [], isPending } = useGetWsTags(currentWorkspace?.id ?? ""); + const { currentProject } = useProject(); + const { data: tags = [], isPending } = useGetWsTags(currentProject?.id ?? ""); const { search, diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx index 8c8d75f47..79fc7aafc 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -18,7 +18,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { withProjectPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { @@ -35,14 +35,14 @@ export const WebhooksTab = withProjectPermission( () => { const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ "addWebhook", "deleteWebhook" ] as const); - const { data: webhooks, isPending: isWebhooksLoading } = useGetWebhooks(workspaceId); + const { data: webhooks, isPending: isWebhooksLoading } = useGetWebhooks(projectId); // mutation const { mutateAsync: createWebhook } = useCreateWebhook(); @@ -62,7 +62,7 @@ export const WebhooksTab = withProjectPermission( try { await createWebhook({ ...data, - workspaceId + projectId }); handlePopUpClose("addWebhook"); createNotification({ @@ -82,7 +82,7 @@ export const WebhooksTab = withProjectPermission( try { await updateWebhook({ webhookId, - workspaceId, + projectId, isDisabled }); createNotification({ @@ -103,7 +103,7 @@ export const WebhooksTab = withProjectPermission( const webhookId = popUp?.deleteWebhook?.data as string; await deleteWebhook({ webhookId, - workspaceId + projectId }); handlePopUpClose("deleteWebhook"); createNotification({ @@ -123,7 +123,7 @@ export const WebhooksTab = withProjectPermission( try { await testWebhook({ webhookId, - workspaceId + projectId }); createNotification({ type: "success", @@ -302,7 +302,7 @@ export const WebhooksTab = withProjectPermission(
handlePopUpToggle("addWebhook", isOpen)} onCreateWebhook={handleWebhookCreate} diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx index 5b5f92bbb..ce8a3a70c 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx @@ -15,7 +15,7 @@ import { THead, Tr } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects, useWorkspace } from "@app/context"; +import { OrgPermissionActions, OrgPermissionSubjects, useProject } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useDeleteProjectWorkflowIntegration, @@ -47,16 +47,16 @@ export const WorkflowIntegrationTab = () => { "editIntegration" ] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: slackConfig, isPending: isSlackConfigLoading } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.SLACK }); const { data: microsoftTeamsConfig, isPending: isMicrosoftTeamsConfigLoading } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS }); @@ -66,12 +66,12 @@ export const WorkflowIntegrationTab = () => { integrationType: WorkflowIntegrationPlatform, integrationId: string ) => { - if (!currentWorkspace.id) { + if (!currentProject.id) { return; } await deleteIntegration({ - projectId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: integrationType, integrationId }); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/AddWorkflowIntegrationModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/AddWorkflowIntegrationModal.tsx index 8601c1c02..28a3c83d3 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/AddWorkflowIntegrationModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/AddWorkflowIntegrationModal.tsx @@ -6,7 +6,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; import { Modal, ModalContent } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useGetWorkspaceWorkflowIntegrationConfig } from "@app/hooks/api"; import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types"; @@ -40,14 +40,14 @@ export const AddWorkflowIntegrationModal = ({ isOpen, onToggle }: Props) => { const [wizardStep, setWizardStep] = useState(WizardSteps.SelectPlatform); const [selectedPlatform, setSelectedPlatform] = useState(null); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: microsoftTeamsConfig } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS }); const { data: slackConfig } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.SLACK }); 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..2bfe8cece 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 @@ -19,7 +19,7 @@ import { SelectItem, Switch } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub @@ -95,13 +95,13 @@ type Props = { }; export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: microsoftTeamsConfig } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS }); const { data: microsoftTeamsIntegrations } = useGetMicrosoftTeamsIntegrations( - currentWorkspace?.orgId + currentProject?.orgId ); const { mutateAsync: updateProjectMicrosoftTeamsConfig } = @@ -131,12 +131,12 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => { const handleIntegrationSave = async (data: TMicrosoftTeamsConfigForm) => { try { - if (!currentWorkspace) { + if (!currentProject) { return; } await updateProjectMicrosoftTeamsConfig({ - workspaceId: currentWorkspace.id, + projectId: currentProject.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..26cae7f61 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 @@ -19,7 +19,7 @@ import { SelectItem, Switch } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub @@ -47,12 +47,12 @@ type Props = { }; export const SlackIntegrationForm = ({ onClose }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: slackConfig } = useGetWorkspaceWorkflowIntegrationConfig({ - workspaceId: currentWorkspace?.id ?? "", + projectId: currentProject?.id ?? "", integration: WorkflowIntegrationPlatform.SLACK }); - const { data: workflowIntegrations } = useGetWorkflowIntegrations(currentWorkspace?.orgId); + const { data: workflowIntegrations } = useGetWorkflowIntegrations(currentProject?.orgId); const { mutateAsync: updateProjectSlackConfig } = useUpdateProjectWorkflowIntegrationConfig(); const slackIntegrations = workflowIntegrations?.filter( @@ -77,13 +77,13 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { const handleIntegrationSave = async (data: TSlackConfigForm) => { try { - if (!currentWorkspace) { + if (!currentProject) { return; } await updateProjectSlackConfig({ ...data, - workspaceId: currentWorkspace.id, + projectId: currentProject.id, integration: WorkflowIntegrationPlatform.SLACK, integrationId: data.slackIntegrationId, accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "), diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx index e67e1e328..16493ad0f 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx @@ -16,7 +16,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; enum AwsAuthType { @@ -40,7 +40,7 @@ type TForm = z.infer; export const AWSParameterStoreAuthorizeIntegrationPage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useSaveIntegrationAccessToken(); const { control, handleSubmit, formState, watch } = useForm({ @@ -55,7 +55,7 @@ export const AWSParameterStoreAuthorizeIntegrationPage = () => { const handleFormSubmit = async (data: TForm) => { try { const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "aws-parameter-store", ...(data.type === AwsAuthType.AssumeRole ? { @@ -70,7 +70,7 @@ export const AWSParameterStoreAuthorizeIntegrationPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/aws-parameter-store/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx index bbdd2d71b..5b5d97f6a 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx @@ -26,7 +26,7 @@ import { Tabs } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; @@ -77,7 +77,7 @@ export const AWSParameterStoreConfigurePage = () => { from: ROUTE_PATHS.SecretManager.Integratons.AwsParameterStoreConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: integrationAuth, isPending: isintegrationAuthLoading } = useGetIntegrationAuthById( (integrationAuthId as string) ?? "" @@ -97,11 +97,11 @@ export const AWSParameterStoreConfigurePage = () => { const [kmsKeyId, setKmsKeyId] = useState(""); useEffect(() => { - if (currentWorkspace) { - setSelectedSourceEnvironment(currentWorkspace.environments[0].slug); + if (currentProject) { + setSelectedSourceEnvironment(currentProject.environments[0].slug); setSelectedAWSRegion(awsRegions[0].slug); } - }, [currentWorkspace]); + }, [currentProject]); const { data: integrationAuthAwsKmsKeys, isPending: isIntegrationAuthAwsKmsKeysLoading } = useGetIntegrationAuthAwsKmsKeys({ @@ -158,7 +158,7 @@ export const AWSParameterStoreConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -226,7 +226,7 @@ export const AWSParameterStoreConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { ; export const AWSSecretManagerAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, formState, watch } = useForm({ resolver: zodResolver(formSchema), @@ -54,7 +54,7 @@ export const AWSSecretManagerAuthorizePage = () => { const handleFormSubmit = async (data: TForm) => { try { const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "aws-secret-manager", ...(data.type === AwsAuthType.AssumeRole ? { @@ -68,7 +68,7 @@ export const AWSSecretManagerAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/aws-secret-manager/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx index bd73fc596..6c82b7edd 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx @@ -30,7 +30,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; @@ -150,7 +150,7 @@ export const AwsSecretManagerConfigurePage = () => { select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: integrationAuth, isPending: isintegrationAuthLoading } = useGetIntegrationAuthById( (integrationAuthId as string) ?? "" ); @@ -162,11 +162,11 @@ export const AwsSecretManagerConfigurePage = () => { }); useEffect(() => { - if (currentWorkspace) { - setValue("sourceEnvironment", currentWorkspace.environments[0].slug); + if (currentProject) { + setValue("sourceEnvironment", currentProject.environments[0].slug); setValue("awsRegion", awsRegions[0].slug); } - }, [currentWorkspace]); + }, [currentProject]); const handleButtonClick = async ({ secretName, @@ -206,7 +206,7 @@ export const AwsSecretManagerConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -286,7 +286,7 @@ export const AwsSecretManagerConfigurePage = () => { field.onChange(val); }} > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { isError={Boolean(error)} > { from: ROUTE_PATHS.SecretManager.Integratons.AzureAppConfigurationsConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: workspace } = useGetWorkspaceById(currentProject.id); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); useEffect(() => { @@ -133,7 +133,7 @@ export const AzureAppConfigurationConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations diff --git a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx index d15fea903..046fac913 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useAuthorizeIntegration } from "@app/hooks/api"; export const AzureAppConfigurationOauthCallbackPage = () => { @@ -12,7 +12,7 @@ export const AzureAppConfigurationOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.AzureAppConfigurationsOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -22,7 +22,7 @@ export const AzureAppConfigurationOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "azure-app-configuration" }); @@ -30,7 +30,7 @@ export const AzureAppConfigurationOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/azure-app-configuration/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx index 25cc9c617..4a03c9c3e 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const AzureDevopsAuthorizePage = () => { @@ -16,7 +16,7 @@ export const AzureDevopsAuthorizePage = () => { const [devopsOrgName, setDevopsOrgName] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleButtonClick = async () => { try { @@ -31,7 +31,7 @@ export const AzureDevopsAuthorizePage = () => { localStorage.setItem("azure-devops-org-name", devopsOrgName); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "azure-devops", accessToken: btoa(`:${apiKey}`) // This is a base64 encoding of the API key without any username }); @@ -41,7 +41,7 @@ export const AzureDevopsAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/azure-devops/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx index b06859e1f..3d092fa26 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx @@ -14,13 +14,13 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; -import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { useGetWorkspaceById } from "@app/hooks/api/projects"; import { IntegrationsListPageTabs } from "@app/types/integrations"; export const AzureDevopsConfigurePage = () => { @@ -31,9 +31,9 @@ export const AzureDevopsConfigurePage = () => { from: ROUTE_PATHS.SecretManager.Integratons.AzureDevopsConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: workspace } = useGetWorkspaceById(currentProject.id); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: integrationAuthApps } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "", @@ -82,7 +82,7 @@ export const AzureDevopsConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx index e5ccd3dfa..91f0d5440 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx @@ -11,11 +11,11 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; -import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { useGetWorkspaceById } from "@app/hooks/api/projects"; import { IntegrationsListPageTabs } from "@app/types/integrations"; const initialSyncBehaviors = [ @@ -38,9 +38,9 @@ export const AzureKeyVaultConfigurePage = () => { from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); - const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: workspace } = useGetWorkspaceById(currentProject.id); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); @@ -90,7 +90,7 @@ export const AzureKeyVaultConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx index ef98d1cba..90a93edce 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useAuthorizeIntegration } from "@app/hooks/api"; export const AzureKeyVaultOauthCallbackPage = () => { @@ -12,7 +12,7 @@ export const AzureKeyVaultOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -22,7 +22,7 @@ export const AzureKeyVaultOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "azure-key-vault" }); @@ -30,7 +30,7 @@ export const AzureKeyVaultOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/azure-key-vault/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx index 10dafdb76..022a8e8d7 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx @@ -16,7 +16,7 @@ import { Spinner } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthApps, @@ -96,7 +96,7 @@ export const BitbucketConfigurePage = () => { from: ROUTE_PATHS.SecretManager.Integratons.BitbucketConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: bitbucketWorkspaces, isPending: isBitbucketWorkspacesLoading } = useGetIntegrationAuthBitbucketWorkspaces((integrationAuthId as string) ?? ""); @@ -150,7 +150,7 @@ export const BitbucketConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -171,18 +171,18 @@ export const BitbucketConfigurePage = () => { bitbucketRepo || !bitbucketRepos || !bitbucketWorkspaces || - !currentWorkspace + !currentProject ) return; reset({ targetRepo: bitbucketRepos[0], targetWorkspace: bitbucketWorkspaces[0], - sourceEnvironment: currentWorkspace.environments[0], + sourceEnvironment: currentProject.environments[0], secretPath: "/", scope: ScopeOptions[0] }); - }, [bitbucketWorkspaces, bitbucketRepos, currentWorkspace]); + }, [bitbucketWorkspaces, bitbucketRepos, currentProject]); if (isBitbucketWorkspacesLoading || isBitbucketReposLoading) return ( @@ -217,9 +217,9 @@ export const BitbucketConfigurePage = () => { value={value} getOptionLabel={(option) => option.name} onChange={onChange} - options={currentWorkspace?.environments} + options={currentProject?.environments} placeholder="Select a project environment" - isDisabled={!currentWorkspace?.environments.length} + isDisabled={!currentProject?.environments.length} /> )} diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx index 1e3d302f2..8b23f46ab 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useAuthorizeIntegration } from "@app/hooks/api"; export const BitbucketOauthCallbackPage = () => { @@ -11,7 +11,7 @@ export const BitbucketOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.BitbucketOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -21,7 +21,7 @@ export const BitbucketOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "bitbucket" }); @@ -29,7 +29,7 @@ export const BitbucketOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/bitbucket/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx index 5dd711fc0..43521fd2e 100644 --- a/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx @@ -5,13 +5,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const ChecklyAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [accessToken, setAccessToken] = useState(""); const [accessTokenErrorText, setAccessTokenErrorText] = useState(""); @@ -28,7 +28,7 @@ export const ChecklyAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "checkly", accessToken }); @@ -38,7 +38,7 @@ export const ChecklyAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/checkly/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx index 2e02f7cf2..e09a1361a 100644 --- a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx @@ -19,14 +19,14 @@ import { Tabs } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById, useGetIntegrationAuthChecklyGroups } from "@app/hooks/api/integrationAuth"; -import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { useGetWorkspaceById } from "@app/hooks/api/projects"; import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { @@ -42,7 +42,7 @@ export const ChecklyConfigurePage = () => { from: ROUTE_PATHS.SecretManager.Integratons.ChecklyConfigurePage.id, select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -53,7 +53,7 @@ export const ChecklyConfigurePage = () => { const [isLoading, setIsLoading] = useState(false); - const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: workspace } = useGetWorkspaceById(currentProject.id); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: integrationAuthApps, isPending: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ @@ -115,7 +115,7 @@ export const ChecklyConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx index e64227fc3..088c22e1d 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const CircleCIAuthorizePage = () => { @@ -15,7 +15,7 @@ export const CircleCIAuthorizePage = () => { const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleButtonClick = async () => { try { @@ -28,7 +28,7 @@ export const CircleCIAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "circleci", accessToken: apiKey }); @@ -38,7 +38,7 @@ export const CircleCIAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/circleci/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx index f03f35af8..36777af29 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -18,7 +18,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthCircleCIOrganizations } from "@app/hooks/api"; import { CircleCiScope } from "@app/hooks/api/integrationAuth/types"; import { IntegrationsListPageTabs } from "@app/types/integrations"; @@ -45,7 +45,7 @@ type TFormData = z.infer; export const CircleCIConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync, isPending: isCreatingIntegration } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.CircleConfigurePage.id, @@ -56,7 +56,7 @@ export const CircleCIConfigurePage = () => { resolver: zodResolver(formSchema), defaultValues: { secretPath: "/", - sourceEnvironment: currentWorkspace?.environments[0], + sourceEnvironment: currentProject?.environments[0], scope: CircleCiScope.Project } }); @@ -105,7 +105,7 @@ export const CircleCIConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -179,9 +179,9 @@ export const CircleCIConfigurePage = () => { value={value} getOptionLabel={(option) => option.name} onChange={onChange} - options={currentWorkspace?.environments} + options={currentProject?.environments} placeholder="Select a project environment" - isDisabled={!currentWorkspace?.environments.length} + isDisabled={!currentProject?.environments.length} /> )} diff --git a/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx index 8fc9dd5ca..1a464cadc 100644 --- a/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx @@ -2,14 +2,14 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const Cloud66AuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -26,7 +26,7 @@ export const Cloud66AuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "cloud-66", accessToken: apiKey }); @@ -36,7 +36,7 @@ export const Cloud66AuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/cloud-66/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx index 6de5e3bc3..68535d70e 100644 --- a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -28,7 +28,7 @@ export const Cloud66ConfigurePage = () => { select: (el) => el.integrationAuthId }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: integrationAuthApps } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" @@ -40,10 +40,10 @@ export const Cloud66ConfigurePage = () => { const [isLoading, setIsLoading] = useState(false); useEffect(() => { - if (currentWorkspace) { - setSelectedSourceEnvironment(currentWorkspace.environments[0].slug); + if (currentProject) { + setSelectedSourceEnvironment(currentProject.environments[0].slug); } - }, [currentWorkspace]); + }, [currentProject]); useEffect(() => { if (integrationAuthApps) { @@ -77,7 +77,7 @@ export const Cloud66ConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -98,7 +98,7 @@ export const Cloud66ConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -14,7 +14,7 @@ export const CloudflarePagesAuthorizePage = () => { const [accountIdErrorText, setAccountIdErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); const handleButtonClick = async () => { @@ -30,7 +30,7 @@ export const CloudflarePagesAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "cloudflare-pages", accessId: accountId, accessToken: accessKey @@ -43,7 +43,7 @@ export const CloudflarePagesAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/cloudflare-pages/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx index 1ff6e75d5..69225d9ae 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx @@ -14,7 +14,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -31,7 +31,7 @@ export const CloudflarePagesConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.CloudflarePagesConfigurePage.id, @@ -39,7 +39,7 @@ export const CloudflarePagesConfigurePage = () => { }); const [secretPath, setSecretPath] = useState("/"); - const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: workspace } = useGetWorkspaceById(currentProject.id); const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); const { data: integrationAuthApps } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" @@ -96,7 +96,7 @@ export const CloudflarePagesConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx index e2829c76a..dd0b16a6b 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx @@ -2,13 +2,13 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const CloudflareWorkersAuthorizePage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useSaveIntegrationAccessToken(); const [accessKey, setAccessKey] = useState(""); @@ -30,7 +30,7 @@ export const CloudflareWorkersAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "cloudflare-workers", accessId: accountId, accessToken: accessKey @@ -43,7 +43,7 @@ export const CloudflareWorkersAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/cloudflare-workers/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx index bfc4b766a..a5d28bb95 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx @@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications"; import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -17,7 +17,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations"; export const CloudflareWorkersConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.CloudflareWorkersConfigurePage.id, @@ -29,7 +29,7 @@ export const CloudflareWorkersConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); @@ -69,7 +69,7 @@ export const CloudflareWorkersConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -107,7 +107,7 @@ export const CloudflareWorkersConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -12,7 +12,7 @@ export const CodefreshAuthorizePage = () => { const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleButtonClick = async () => { try { @@ -25,7 +25,7 @@ export const CodefreshAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "codefresh", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const CodefreshAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/codefresh/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx index e99ad4ea8..80a54b621 100644 --- a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -23,7 +23,7 @@ export const CodefreshConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.CodefreshConfigurePage.id, select: (el) => el.integrationAuthId @@ -35,7 +35,7 @@ export const CodefreshConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -73,7 +73,7 @@ export const CodefreshConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -94,7 +94,7 @@ export const CodefreshConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -17,7 +17,7 @@ export const DatabricksAuthorizePage = () => { const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [instanceURLErrorText, setInstanceURLErrorText] = useState(""); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleButtonClick = async () => { try { @@ -33,7 +33,7 @@ export const DatabricksAuthorizePage = () => { } const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "databricks", url: instanceURL.replace(/\/$/, ""), accessToken: apiKey @@ -42,7 +42,7 @@ export const DatabricksAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/databricks/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx index 0f3d09728..a140a0339 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx @@ -20,7 +20,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -32,7 +32,7 @@ export const DatabricksConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync, isPending } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.DatabricksConfigurePage.id, @@ -49,7 +49,7 @@ export const DatabricksConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetScope, setTargetScope] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -89,7 +89,7 @@ export const DatabricksConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -143,7 +143,7 @@ export const DatabricksConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -12,7 +12,7 @@ export const DigitalOceanAppPlatformAuthorizePage = () => { const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const handleButtonClick = async () => { try { @@ -25,7 +25,7 @@ export const DigitalOceanAppPlatformAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "digital-ocean-app-platform", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const DigitalOceanAppPlatformAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx index e88d98976..2c6f08348 100644 --- a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -23,7 +23,7 @@ export const DigitalOceanAppPlatformConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.DigitalOceanAppPlatformConfigurePage.id, @@ -36,7 +36,7 @@ export const DigitalOceanAppPlatformConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -74,7 +74,7 @@ export const DigitalOceanAppPlatformConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -95,7 +95,7 @@ export const DigitalOceanAppPlatformConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( ; export const FlyioAuthorizePage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit } = useForm({ resolver: zodResolver(schema), @@ -37,7 +37,7 @@ export const FlyioAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "flyio", accessToken }); @@ -46,7 +46,7 @@ export const FlyioAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/flyio/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx index 0beb61bdf..c034ce3f1 100644 --- a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx @@ -21,7 +21,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -34,7 +34,7 @@ export const FlyioConfigurePage = () => { const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.FlyioConfigurePage.id, @@ -50,7 +50,7 @@ export const FlyioConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); @@ -88,7 +88,7 @@ export const FlyioConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -144,7 +144,7 @@ export const FlyioConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { }); const { data: cloudIntegrations } = useGetCloudIntegrations(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useSaveIntegrationAccessToken(); @@ -47,7 +47,7 @@ export const GcpSecretManagerAuthorizePage = () => { const state = crypto.randomBytes(16).toString("hex"); localStorage.setItem("latestCSRFToken", state); - localStorageService.setIntegrationProjectId(currentWorkspace.id); + localStorageService.setIntegrationProjectId(currentProject.id); if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); @@ -63,7 +63,7 @@ export const GcpSecretManagerAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "gcp-secret-manager", refreshToken: accessToken }); @@ -72,7 +72,7 @@ export const GcpSecretManagerAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/gcp-secret-manager/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx index c4f8403bd..85af1961f 100644 --- a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx @@ -26,7 +26,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; import { @@ -59,7 +59,7 @@ export const GcpSecretManagerConfigurePage = () => { "confirmIntegration" ] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, setValue, watch } = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -69,7 +69,7 @@ export const GcpSecretManagerConfigurePage = () => { shouldLabel: false, labelName: "managed-by", labelValue: "infisical", - selectedSourceEnvironment: currentWorkspace.environments[0].slug + selectedSourceEnvironment: currentProject.environments[0].slug } }); @@ -153,7 +153,7 @@ export const GcpSecretManagerConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -241,7 +241,7 @@ export const GcpSecretManagerConfigurePage = () => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -12,7 +12,7 @@ export const GcpSecretManagerOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.GcpSecretManagerOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -22,7 +22,7 @@ export const GcpSecretManagerOauthCallbackPage = () => { if (state !== localStorage.getItem("latestCSRFToken")) return; localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "gcp-secret-manager" }); @@ -30,7 +30,7 @@ export const GcpSecretManagerOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/gcp-secret-manager/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/GithubAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/GithubAuthorizePage.tsx index 623c5762c..e4fe73feb 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/GithubAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/GithubAuthorizePage.tsx @@ -15,7 +15,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { localStorageService } from "@app/helpers/localStorage"; import { useGetCloudIntegrations } from "@app/hooks/api"; @@ -31,7 +31,7 @@ export const GithubAuthorizePage = () => { const { data: cloudIntegrations } = useGetCloudIntegrations(); const githubIntegration = cloudIntegrations?.find((integration) => integration.slug === "github"); const [selectedAuthMethod, setSelectedAuthMethod] = useState(AuthMethod.APP); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); return (
@@ -84,7 +84,7 @@ export const GithubAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/select-integration-auth", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationSlug: "github" @@ -102,7 +102,7 @@ export const GithubAuthorizePage = () => { const state = crypto.randomBytes(16).toString("hex"); localStorage.setItem("latestCSRFToken", state); - localStorageService.setIntegrationProjectId(currentWorkspace.id); + localStorageService.setIntegrationProjectId(currentProject.id); window.location.assign( `https://github.com/login/oauth/authorize?client_id=${githubIntegration?.clientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}` ); diff --git a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx index aa71c793e..7dad0d29a 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx @@ -37,7 +37,7 @@ import { Tabs } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthApps, @@ -142,7 +142,7 @@ export const GithubConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.GithubConfigurePage.id, select: (el) => el.integrationAuthId @@ -166,7 +166,7 @@ export const GithubConfigurePage = () => { repoIds: [], visibility: "all", shouldEnableDelete: false, - selectedSourceEnvironment: currentWorkspace.environments[0].slug + selectedSourceEnvironment: currentProject.environments[0].slug } }); @@ -269,7 +269,7 @@ export const GithubConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -367,7 +367,7 @@ export const GithubConfigurePage = () => { onValueChange={onChange} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -16,7 +16,7 @@ export const GithubOauthCallbackPage = () => { } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.GithubOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -29,7 +29,7 @@ export const GithubOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, installationId, integration: "github" @@ -38,7 +38,7 @@ export const GithubOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/github/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/GitlabAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/GitlabAuthorizePage.tsx index 2ce82d97c..cf77aa312 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/GitlabAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/GitlabAuthorizePage.tsx @@ -8,7 +8,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { localStorageService } from "@app/helpers/localStorage"; import { useGetCloudIntegrations } from "@app/hooks/api"; @@ -29,7 +29,7 @@ export const GitlabAuthorizePage = () => { }); const { data: cloudIntegrations } = useGetCloudIntegrations(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const onFormSubmit = ({ gitLabURL }: FormData) => { if (!cloudIntegrations) return; @@ -49,12 +49,12 @@ export const GitlabAuthorizePage = () => { const csrfToken = crypto.randomBytes(16).toString("hex"); localStorage.setItem("latestCSRFToken", csrfToken); - localStorageService.setIntegrationProjectId(currentWorkspace.id); + localStorageService.setIntegrationProjectId(currentProject.id); const state = `${csrfToken}|${ (gitLabURL as string).trim() === "" ? "" : (gitLabURL as string).trim() }`; - localStorageService.setIntegrationProjectId(currentWorkspace.id); + localStorageService.setIntegrationProjectId(currentProject.id); const link = `${baseURL}/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`; window.location.assign(link); diff --git a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx index 6fb388b71..13d2081ad 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx @@ -26,7 +26,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; import { @@ -76,7 +76,7 @@ export const GitlabConfigurePage = () => { const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ "confirmIntegration" ] as const); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, setValue, watch } = useForm({ resolver: zodResolver(schema), @@ -85,7 +85,7 @@ export const GitlabConfigurePage = () => { secretPath: "/", secretPrefix: "", secretSuffix: "", - selectedSourceEnvironment: currentWorkspace.environments[0].slug, + selectedSourceEnvironment: currentProject.environments[0].slug, initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE } }); @@ -177,7 +177,7 @@ export const GitlabConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -267,7 +267,7 @@ export const GitlabConfigurePage = () => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.GitlabOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -65,7 +65,7 @@ export const GitLabOAuthCallbackPage = () => { }); } else { appConnection = await createAppConnection({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, ...connectionData }); } @@ -87,7 +87,7 @@ export const GitLabOAuthCallbackPage = () => { }); } })(); - }, [code, state, navigate, createAppConnection, updateAppConnection, currentWorkspace.id]); + }, [code, state, navigate, createAppConnection, updateAppConnection, currentProject.id]); return (
diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx index ae200968a..8a8cbbc4c 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx @@ -9,7 +9,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, Card, CardBody, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; const formSchema = z.object({ @@ -25,7 +25,7 @@ export const HashicorpVaultAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, @@ -43,7 +43,7 @@ export const HashicorpVaultAuthorizePage = () => { const handleFormSubmit = async (formData: TForm) => { try { const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "hashicorp-vault", accessId: formData.vaultRoleID, accessToken: formData.vaultSecretID, @@ -53,7 +53,7 @@ export const HashicorpVaultAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/hashicorp-vault/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx index af9af585e..48b31dd80 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx @@ -25,7 +25,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { isValidPath } from "@app/helpers/string"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; @@ -61,7 +61,7 @@ export const HashicorpVaultConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.HashicorpVaultConfigurePage.id, select: (el) => el.integrationAuthId @@ -72,8 +72,8 @@ export const HashicorpVaultConfigurePage = () => { ); const formSchema = useMemo(() => { - return generateFormSchema(currentWorkspace?.environments.map((env) => env.slug) ?? []); - }, [currentWorkspace?.environments]); + return generateFormSchema(currentProject?.environments.map((env) => env.slug) ?? []); + }, [currentProject?.environments]); const { control, @@ -103,7 +103,7 @@ export const HashicorpVaultConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -177,7 +177,7 @@ export const HashicorpVaultConfigurePage = () => { onValueChange={field.onChange} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const [isLoading, setIsLoading] = useState(false); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit } = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -36,7 +36,7 @@ export const HasuraCloudAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "hasura-cloud", accessToken }); @@ -45,7 +45,7 @@ export const HasuraCloudAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/hasura-cloud/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx index a42451e8e..cfcba8058 100644 --- a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx @@ -9,7 +9,7 @@ import { z } from "zod"; import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -38,7 +38,7 @@ export const HasuraCloudConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.HasuraCloudConfigurePage.id, select: (el) => el.integrationAuthId @@ -72,7 +72,7 @@ export const HasuraCloudConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -137,7 +137,7 @@ export const HasuraCloudConfigurePage = () => { field.onChange(val); }} > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( ; export const HerokuConfigurePage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, setValue, watch } = useForm({ resolver: zodResolver(schema), defaultValues: { secretPath: "/", initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE, - selectedSourceEnvironment: currentWorkspace.environments[0].slug + selectedSourceEnvironment: currentProject.environments[0].slug } }); @@ -100,7 +100,7 @@ export const HerokuConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -158,7 +158,7 @@ export const HerokuConfigurePage = () => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { from: ROUTE_PATHS.SecretManager.Integratons.HerokuOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -67,7 +67,7 @@ export const HerokuOAuthCallbackPage = () => { }); } else { appConnection = await createAppConnection({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, ...connectionData }); } @@ -88,7 +88,7 @@ export const HerokuOAuthCallbackPage = () => { }); } })(); - }, [code, state, navigate, createAppConnection, updateAppConnection, currentWorkspace.id]); + }, [code, state, navigate, createAppConnection, updateAppConnection, currentProject.id]); return (
diff --git a/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/LaravelForgeAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/LaravelForgeAuthorizePage.tsx index d67a6ce1d..c607472c7 100644 --- a/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/LaravelForgeAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/LaravelForgeAuthorizePage.tsx @@ -2,13 +2,13 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const LaravelForgeAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [serverId, setServerId] = useState(""); @@ -33,7 +33,7 @@ export const LaravelForgeAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "laravel-forge", accessId: serverId, accessToken: apiKey @@ -44,7 +44,7 @@ export const LaravelForgeAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/laravel-forge/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx index 91963e749..55a5ac360 100644 --- a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -23,7 +23,7 @@ export const LaravelForgeConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.LaravelForgeConfigurePage.id, select: (el) => el.integrationAuthId @@ -35,7 +35,7 @@ export const LaravelForgeConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -74,7 +74,7 @@ export const LaravelForgeConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -95,7 +95,7 @@ export const LaravelForgeConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.NetlifyConfigurePage.id, select: (el) => el.integrationAuthId @@ -42,7 +42,7 @@ export const NetlifyConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [targetEnvironment, setTargetEnvironment] = useState(netlifyEnvironments[0].slug); @@ -81,7 +81,7 @@ export const NetlifyConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -106,7 +106,7 @@ export const NetlifyConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -12,7 +12,7 @@ export const NetlifyOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.NetlifyOuathCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -22,7 +22,7 @@ export const NetlifyOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "netlify" }); @@ -30,7 +30,7 @@ export const NetlifyOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/netlify/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/NorthflankAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/NorthflankAuthorizePage.tsx index b203a02dc..005a4914e 100644 --- a/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/NorthflankAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/NorthflankAuthorizePage.tsx @@ -2,13 +2,13 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const NorthflankAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -25,7 +25,7 @@ export const NorthflankAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "northflank", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const NorthflankAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/northflank/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx index 7f01c32ee..9ff1e18c8 100644 --- a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -24,9 +24,9 @@ export const NorthflankConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); const [targetAppId, setTargetAppId] = useState(""); @@ -95,7 +95,7 @@ export const NorthflankConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -116,7 +116,7 @@ export const NorthflankConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( ; export const OctopusDeployAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync, isPending } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit } = useForm({ resolver: zodResolver(formSchema) @@ -31,7 +31,7 @@ export const OctopusDeployAuthorizePage = () => { const onSubmit = async ({ instanceUrl, apiKey }: TForm) => { try { const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "octopus-deploy", url: removeTrailingSlash(instanceUrl), accessToken: apiKey @@ -40,7 +40,7 @@ export const OctopusDeployAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/octopus-deploy/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx index 458c4a997..bddd884f4 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx @@ -16,7 +16,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthApps } from "@app/hooks/api"; import { useGetIntegrationAuthOctopusDeployScopeValues, @@ -56,7 +56,7 @@ export const OctopusDeployConfigurePage = () => { } }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.OctopusDeployCloudConfigurePage.id, select: (el) => el.integrationAuthId @@ -137,7 +137,7 @@ export const OctopusDeployConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -153,16 +153,16 @@ export const OctopusDeployConfigurePage = () => { }; useEffect(() => { - if (!octopusDeployResources || !octopusDeploySpaces || !currentWorkspace) return; + if (!octopusDeployResources || !octopusDeploySpaces || !currentProject) return; reset({ targetResource: octopusDeployResources[0], targetSpace: octopusDeploySpaces.find((space) => space.IsDefault), - sourceEnvironment: currentWorkspace.environments[0], + sourceEnvironment: currentProject.environments[0], secretPath: "/", scope: OctopusDeployScope.Project }); - }, [octopusDeploySpaces, octopusDeployResources, currentWorkspace]); + }, [octopusDeploySpaces, octopusDeployResources, currentProject]); if (isLoadingOctopusDeploySpaces || isOctopusDeployResourcesLoading) return ( @@ -196,9 +196,9 @@ export const OctopusDeployConfigurePage = () => { value={value} getOptionLabel={(option) => option.name} onChange={onChange} - options={currentWorkspace?.environments} + options={currentProject?.environments} placeholder="Select a project environment" - isDisabled={!currentWorkspace?.environments.length} + isDisabled={!currentProject?.environments.length} /> )} diff --git a/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/QoveryAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/QoveryAuthorizePage.tsx index 607b1962b..369046df9 100644 --- a/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/QoveryAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/QoveryAuthorizePage.tsx @@ -5,12 +5,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const QoveryAuthorizePage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useSaveIntegrationAccessToken(); const [accessToken, setAccessToken] = useState(""); @@ -28,7 +28,7 @@ export const QoveryAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "qovery", accessToken }); @@ -38,7 +38,7 @@ export const QoveryAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/qovery/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx index bc0f24c90..977f4fdd1 100644 --- a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx @@ -19,7 +19,7 @@ import { Tabs } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { @@ -55,7 +55,7 @@ enum TabSections { export const QoveryConfigurePage = () => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useCreateIntegration(); const integrationAuthId = useSearch({ @@ -67,7 +67,7 @@ export const QoveryConfigurePage = () => { const [scope, setScope] = useState("application"); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); @@ -178,7 +178,7 @@ export const QoveryConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -241,7 +241,7 @@ export const QoveryConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -25,7 +25,7 @@ export const RailwayAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "railway", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const RailwayAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/railway/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx index 835e59277..1d24f7ce7 100644 --- a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -25,13 +25,13 @@ export const RailwayConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [targetAppId, setTargetAppId] = useState(""); const [targetEnvironmentId, setTargetEnvironmentId] = useState(""); const [targetServiceId, setTargetServiceId] = useState(""); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); const [isLoading, setIsLoading] = useState(false); @@ -110,7 +110,7 @@ export const RailwayConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -139,7 +139,7 @@ export const RailwayConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -28,7 +28,7 @@ export const RenderAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "render", accessToken: apiKey }); @@ -38,7 +38,7 @@ export const RenderAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/render/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx index 3d156cdac..4a2ef30d1 100644 --- a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx @@ -23,7 +23,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -44,13 +44,13 @@ export const RenderConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit, setValue, watch } = useForm({ resolver: zodResolver(schema), defaultValues: { secretPath: "/", shouldAutoRedeploy: false, - selectedSourceEnvironment: currentWorkspace.environments[0].slug + selectedSourceEnvironment: currentProject.environments[0].slug } }); @@ -106,7 +106,7 @@ export const RenderConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -167,7 +167,7 @@ export const RenderConfigurePage = () => { onValueChange={(e) => onChange(e)} className="w-full" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const [isLoading, setIsLoading] = useState(false); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { control, handleSubmit } = useForm({ resolver: zodResolver(schema), @@ -40,7 +40,7 @@ export const RundeckAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "rundeck", accessToken: authToken, url: rundeckURL.trim() @@ -50,7 +50,7 @@ export const RundeckAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/rundeck/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx index 7c8dada9f..555a5cc5c 100644 --- a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx @@ -17,7 +17,7 @@ import { } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationsListPageTabs } from "@app/types/integrations"; @@ -44,7 +44,7 @@ export const RundeckConfigurePage = () => { }); const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.RundeckConfigurePage.id, @@ -73,7 +73,7 @@ export const RundeckConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -138,7 +138,7 @@ export const RundeckConfigurePage = () => { field.onChange(val); }} > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { isError={Boolean(error)} > { const navigate = useNavigate(); const { data: cloudIntegrations } = useGetCloudIntegrations(); const { currentOrg } = useOrganization(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const orgId = currentOrg?.id || ""; const integrationSlug = useSearch({ @@ -50,10 +50,10 @@ export const SelectIntegrationAuthPage = () => { if (integrationSlug === "github") { const sameProjectIntegrationAuths = filteredIntegrationAuths.filter( - (auth) => auth.projectId === currentWorkspace?.id + (auth) => auth.projectId === currentProject?.id ); const differentProjectIntegrationAuths = filteredIntegrationAuths.filter( - (auth) => auth.projectId !== currentWorkspace?.id + (auth) => auth.projectId !== currentProject?.id ); const installationIds = new Set(); @@ -117,11 +117,11 @@ export const SelectIntegrationAuthPage = () => { const handleConnectionSelect = async (integrationAuth: IntegrationAuth) => { if (integrationSlug === "github") { - if (integrationAuth.projectId === currentWorkspace?.id) { + if (integrationAuth.projectId === currentProject?.id) { navigate({ to: "/projects/secret-management/$projectId/integrations/github/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id @@ -130,14 +130,14 @@ export const SelectIntegrationAuthPage = () => { } else { // we create a copy of the existing integration auth from another project to the current project const newIntegrationAuth = await duplicateIntegrationAuth({ - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", integrationAuthId: integrationAuth.id }); navigate({ to: "/projects/secret-management/$projectId/integrations/github/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: newIntegrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/SupabaseAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/SupabaseAuthorizePage.tsx index 5ec969e11..210efea1c 100644 --- a/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/SupabaseAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/SupabaseAuthorizePage.tsx @@ -2,13 +2,13 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const SupabaseAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -25,7 +25,7 @@ export const SupabaseAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "supabase", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const SupabaseAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/supabase/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx index 333c61a8e..38183a5ad 100644 --- a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -22,7 +22,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations"; export const SupabaseConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.SupabaseConfigurePage.id, select: (el) => el.integrationAuthId @@ -34,7 +34,7 @@ export const SupabaseConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); const [targetApp, setTargetApp] = useState(""); @@ -73,7 +73,7 @@ export const SupabaseConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -94,7 +94,7 @@ export const SupabaseConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [serverUrl, setServerUrl] = useState(""); @@ -37,7 +37,7 @@ export const TeamcityAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "teamcity", accessToken: apiKey, url: serverUrl @@ -48,7 +48,7 @@ export const TeamcityAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/teamcity/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx index 9291d0d5f..8e7d0fe97 100644 --- a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx @@ -14,7 +14,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -26,7 +26,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations"; export const TeamcityConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.TeamcityConfigurePage.id, @@ -34,7 +34,7 @@ export const TeamcityConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetAppId, setTargetAppId] = useState(""); const [targetBuildConfigId, setTargetBuildConfigId] = useState(""); @@ -92,7 +92,7 @@ export const TeamcityConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -149,7 +149,7 @@ export const TeamcityConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); @@ -37,7 +37,7 @@ export const TerraformCloudAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "terraform-cloud", accessId: workspacesId, accessToken: apiKey @@ -48,7 +48,7 @@ export const TerraformCloudAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/terraform-cloud/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx index a2d73feca..ba44dfaa4 100644 --- a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx @@ -14,7 +14,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -44,7 +44,7 @@ export const TerraformCloudConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.TerraformCloudConfigurePage.id, @@ -56,7 +56,7 @@ export const TerraformCloudConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -107,7 +107,7 @@ export const TerraformCloudConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -160,7 +160,7 @@ export const TerraformCloudConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { mutateAsync } = useSaveIntegrationAccessToken(); const [apiKey, setApiKey] = useState(""); @@ -25,7 +25,7 @@ export const TravisCIAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "travisci", accessToken: apiKey }); @@ -35,7 +35,7 @@ export const TravisCIAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/travisci/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx index 66c3a8022..e71a6ab31 100644 --- a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -23,7 +23,7 @@ export const TravisCIConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.TravisCIConfigurePage.id, @@ -36,7 +36,7 @@ export const TravisCIConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [targetApp, setTargetApp] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -74,7 +74,7 @@ export const TravisCIConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -95,7 +95,7 @@ export const TravisCIConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); const [initialSyncBehavior, setInitialSyncBehavior] = useState( @@ -134,7 +134,7 @@ export const VercelConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -206,7 +206,7 @@ export const VercelConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { @@ -12,7 +12,7 @@ export const VercelOauthCallbackPage = () => { const { code, state } = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.VercelOauthCallbackPage.id }); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); useEffect(() => { (async () => { @@ -22,7 +22,7 @@ export const VercelOauthCallbackPage = () => { localStorage.removeItem("latestCSRFToken"); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, code: code as string, integration: "vercel" }); @@ -30,7 +30,7 @@ export const VercelOauthCallbackPage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/vercel/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx index 36e791e56..3cd3ff335 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx @@ -2,14 +2,14 @@ import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { isInfisicalCloud } from "@app/helpers/platform"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const WindmillAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); const [apiUrl, setApiUrl] = useState(null); @@ -68,7 +68,7 @@ export const WindmillAuthorizePage = () => { setIsLoading(true); const integrationAuth = await mutateAsync({ - workspaceId: currentWorkspace.id, + workspaceId: currentProject.id, integration: "windmill", accessToken: apiKey, url: apiUrl ?? undefined @@ -79,7 +79,7 @@ export const WindmillAuthorizePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations/windmill/create", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { integrationAuthId: integrationAuth.id diff --git a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx index 17929b2a3..7f781c89e 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx @@ -11,7 +11,7 @@ import { SelectItem } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -23,7 +23,7 @@ export const WindmillConfigurePage = () => { const navigate = useNavigate(); const { mutateAsync } = useCreateIntegration(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const integrationAuthId = useSearch({ from: ROUTE_PATHS.SecretManager.Integratons.WindmillConfigurePage.id, @@ -36,7 +36,7 @@ export const WindmillConfigurePage = () => { }); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( - currentWorkspace.environments[0].slug + currentProject.environments[0].slug ); const [secretPath, setSecretPath] = useState("/"); const [targetApp, setTargetApp] = useState(""); @@ -76,7 +76,7 @@ export const WindmillConfigurePage = () => { navigate({ to: "/projects/secret-management/$projectId/integrations", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations @@ -97,7 +97,7 @@ export const WindmillConfigurePage = () => { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {currentWorkspace?.environments.map((sourceEnvironment) => ( + {currentProject?.environments.map((sourceEnvironment) => ( { const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId) + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId) }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId + projectId: params.projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + queryFn: () => fetchUserProjectPermissions({ projectId: params.projectId }) }); return { diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx index 6dc7ceb35..804c4d790 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx @@ -18,7 +18,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useToggle } from "@app/hooks"; import { SecretScanningScanStatus, @@ -41,7 +41,7 @@ export const SecretScanningScanRow = ({ scan }: Props) => { resolvedFindings, type } = scan; - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const totalFindings = resolvedFindings + unresolvedFindings; const navigate = useNavigate(); @@ -97,7 +97,7 @@ export const SecretScanningScanRow = ({ scan }: Props) => { navigate({ to: "/projects/secret-scanning/$projectId/findings", params: { - projectId: currentWorkspace.id + projectId: currentProject.id }, search: { search: `scanId:${id}` diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx index 019ca3467..03c387440 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx @@ -5,7 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreateSecretScanningDataSourceModal } from "@app/components/secret-scanning"; import { Button, Spinner } from "@app/components/v2"; -import { ProjectPermissionSub, useSubscription, useWorkspace } from "@app/context"; +import { ProjectPermissionSub, useProject, useSubscription } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { useListSecretScanningDataSources } from "@app/hooks/api/secretScanningV2"; @@ -20,10 +20,10 @@ export const SecretScanningDataSourcesSection = () => { const { subscription } = useSubscription(); - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: dataSources = [], isPending: isDataSourcesPending } = - useListSecretScanningDataSources(currentWorkspace.id, { + useListSecretScanningDataSources(currentProject.id, { refetchInterval: 30000, enabled: subscription.secretScanning }); diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/route.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/route.tsx index b13c99db7..87b2b43fb 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/route.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/route.tsx @@ -1,11 +1,19 @@ import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; import { SecretScanningDataSourcesPage } from "./SecretScanningDataSourcesPage"; +const SecretScanningDataSourcesPageQueryParamsSchema = z.object({ + connectionId: z.string().optional(), + connectionName: z.string().optional() +}); + export const Route = createFileRoute( "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources/" )({ component: SecretScanningDataSourcesPage, + validateSearch: zodValidator(SecretScanningDataSourcesPageQueryParamsSchema), beforeLoad: ({ context }) => { return { breadcrumbs: [ diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx index 82ea049f9..4a9cf1ad0 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx @@ -2,16 +2,16 @@ import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-sv import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ContentLoader } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useListSecretScanningFindings } from "@app/hooks/api/secretScanningV2"; import { SecretScanningFindingsTable } from "./SecretScanningFindingsTable"; export const SecretScanningFindingsSection = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: findings = [], isPending: isFindingsPending } = useListSecretScanningFindings( - currentWorkspace.id, + currentProject.id, { refetchInterval: 30000 } diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx index e8d3206d8..4cf9e2b22 100644 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx +++ b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx @@ -1,16 +1,16 @@ import { faBan } from "@fortawesome/free-solid-svg-icons"; import { AccessRestrictedBanner, ContentLoader, EmptyState } from "@app/components/v2"; -import { useSubscription, useWorkspace } from "@app/context"; +import { useProject, useSubscription } from "@app/context"; import { useGetSecretScanningConfig } from "@app/hooks/api/secretScanningV2"; import { SecretScanningConfigForm } from "./SecretScanningConfigForm"; export const ProjectScanningConfigTab = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { subscription } = useSubscription(); const { data: config, isPending: isConfigPending } = useGetSecretScanningConfig( - currentWorkspace.id, + currentProject.id, { enabled: subscription.secretScanning } ); diff --git a/frontend/src/pages/secret-scanning/layout.tsx b/frontend/src/pages/secret-scanning/layout.tsx index 38899a233..13a43f895 100644 --- a/frontend/src/pages/secret-scanning/layout.tsx +++ b/frontend/src/pages/secret-scanning/layout.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; import { BreadcrumbTypes } from "@app/components/v2"; -import { workspaceKeys } from "@app/hooks/api"; +import { projectKeys } from "@app/hooks/api"; +import { fetchProjectById } from "@app/hooks/api/projects/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; -import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect"; import { SecretScanningLayout } from "@app/layouts/SecretScanningLayout"; @@ -13,15 +13,15 @@ export const Route = createFileRoute( component: SecretScanningLayout, beforeLoad: async ({ params, context }) => { const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId) + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId) }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId + projectId: params.projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + queryFn: () => fetchUserProjectPermissions({ projectId: params.projectId }) }); return { diff --git a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx index 503717326..7b00b3ea0 100644 --- a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx +++ b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Select, SelectItem } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useGetProjectSshConfig, useListWorkspaceSshCas, @@ -23,9 +23,9 @@ const schema = z export type FormData = z.infer; export const ProjectSshConfigCasSection = () => { - const { currentWorkspace } = useWorkspace(); - const { data: sshConfig } = useGetProjectSshConfig(currentWorkspace.id); - const { data: sshCas } = useListWorkspaceSshCas(currentWorkspace.id); + const { currentProject } = useProject(); + const { data: sshConfig } = useGetProjectSshConfig(currentProject.id); + const { data: sshCas } = useListWorkspaceSshCas(currentProject.id); const { mutate: updateProjectSshConfig } = useUpdateProjectSshConfig(); const { @@ -49,7 +49,7 @@ export const ProjectSshConfigCasSection = () => { const onFormSubmit = async ({ defaultUserSshCaId, defaultHostSshCaId }: FormData) => { try { await updateProjectSshConfig({ - projectId: currentWorkspace.id, + projectId: currentProject.id, defaultUserSshCaId: defaultUserSshCaId || undefined, defaultHostSshCaId: defaultHostSshCaId || undefined }); diff --git a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx index 8bed6e647..88a8c7f60 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx @@ -15,7 +15,7 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useDeleteSshCa, useGetSshCaById } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -23,9 +23,9 @@ import { SshCaModal } from "../SshCasPage/components/SshCaModal"; import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components"; const Page = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); - const projectId = currentWorkspace?.id || ""; + const projectId = currentProject?.id || ""; const caId = useParams({ from: ROUTE_PATHS.Ssh.SshCaByIDPage.id, select: (el) => el.caId diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx index 5c4673931..6e0baa502 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx @@ -14,7 +14,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { SshCertTemplateStatus, useGetSshCertTemplate, @@ -71,8 +71,8 @@ enum SshCertificateOperation { } export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const [operation, setOperation] = useState( SshCertificateOperation.SIGN_SSH_KEY ); diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx index 55d4104ac..17c7a552e 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx @@ -16,7 +16,7 @@ import { SelectItem, Switch } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateSshCertTemplate, useGetSshCaById, @@ -77,7 +77,7 @@ type Props = { }; export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId }: Props) => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const { data: ca } = useGetSshCaById(sshCaId); @@ -85,7 +85,7 @@ export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId (popUp?.sshCertificateTemplate?.data as { id: string })?.id || "" ); - const { data: cas } = useListWorkspaceSshCas(currentWorkspace?.id || ""); + const { data: cas } = useListWorkspaceSshCas(currentProject?.id || ""); const { mutateAsync: createSshCertTemplate } = useCreateSshCertTemplate(); const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate(); diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx index 758af52a9..4c8ce4e19 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx @@ -15,7 +15,7 @@ import { SelectItem, TextArea } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateSshCa, useGetSshCaById, useUpdateSshCa } from "@app/hooks/api"; import { SshCaKeySource, @@ -54,8 +54,8 @@ export type FormData = z.infer; export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; const { data: ca } = useGetSshCaById((popUp?.sshCa?.data as { caId: string })?.caId || ""); const { mutateAsync: createMutateAsync } = useCreateSshCa(); diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx index 37a086f37..c94f73664 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx @@ -21,7 +21,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { SshCaStatus, useListWorkspaceSshCas } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -35,8 +35,8 @@ type Props = { export const SshCaTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListWorkspaceSshCas(currentWorkspace?.id || ""); + const { currentProject } = useProject(); + const { data, isPending } = useListWorkspaceSshCas(currentProject?.id || ""); return (
@@ -63,7 +63,7 @@ export const SshCaTable = ({ handlePopUpOpen }: Props) => { navigate({ to: "/projects/ssh/$projectId/ca/$caId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, caId: ca.id } }) diff --git a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx index c48b407ac..1ebf3112c 100644 --- a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx +++ b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx @@ -15,7 +15,7 @@ import { THead, Tr } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useListWorkspaceSshCertificates } from "@app/hooks/api"; import { getSshCertStatusBadgeDetails } from "./SshCertificatesTable.utils"; @@ -23,12 +23,12 @@ import { getSshCertStatusBadgeDetails } from "./SshCertificatesTable.utils"; const PER_PAGE_INIT = 25; export const SshCertificatesTable = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); const { data, isPending } = useListWorkspaceSshCertificates({ - projectId: currentWorkspace?.id || "", + projectId: currentProject?.id || "", offset: (page - 1) * perPage, limit: perPage }); diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx index 6b43e8324..e030a8fa0 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx @@ -16,7 +16,7 @@ import { Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useDeleteSshHostGroup, useGetSshHostGroupById } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -24,9 +24,9 @@ import { SshHostGroupModal } from "../SshHostsPage/components/SshHostGroupModal" import { SshHostGroupDetailsSection, SshHostGroupHostsSection } from "./components"; const Page = () => { - const { currentWorkspace } = useWorkspace(); + const { currentProject } = useProject(); const navigate = useNavigate(); - const projectId = currentWorkspace?.id || ""; + const projectId = currentProject?.id || ""; const sshHostGroupId = useParams({ from: ROUTE_PATHS.Ssh.SshHostGroupDetailsByIDPage.id, select: (el) => el.sshHostGroupId diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx index 84be5a96d..4905695d4 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx @@ -17,7 +17,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateSshHostGroup, useGetSshHostGroupById, @@ -56,8 +56,8 @@ const schema = z export type FormData = z.infer; export const SshHostGroupModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace.id; + const { currentProject } = useProject(); + const projectId = currentProject.id; const { data: sshHostGroups } = useListWorkspaceSshHostGroups(projectId); const { data: members = [] } = useGetWorkspaceUsers(projectId); const { data: groups = [] } = useListWorkspaceGroups(projectId); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx index daedabdc9..af3a97dc6 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx @@ -28,7 +28,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useListWorkspaceSshHostGroups } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -41,8 +41,8 @@ type Props = { export const SshHostGroupsTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListWorkspaceSshHostGroups(currentWorkspace?.id || ""); + const { currentProject } = useProject(); + const { data, isPending } = useListWorkspaceSshHostGroups(currentProject?.id || ""); return (
@@ -69,7 +69,7 @@ export const SshHostGroupsTable = ({ handlePopUpOpen }: Props) => { navigate({ to: "/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, sshHostGroupId: group.id } }) @@ -170,7 +170,7 @@ export const SshHostGroupsTable = ({ handlePopUpOpen }: Props) => { navigate({ to: "/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId", params: { - projectId: currentWorkspace.id, + projectId: currentProject.id, sshHostGroupId: group.id } }); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx index 5619a75c4..e1ea75143 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx @@ -18,7 +18,7 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { useProject } from "@app/context"; import { useCreateSshHost, useGetSshHostById, @@ -68,9 +68,9 @@ const schema = z export type FormData = z.infer; export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; - const { data: sshHosts } = useListWorkspaceSshHosts(currentWorkspace.id); + const { currentProject } = useProject(); + const projectId = currentProject?.id || ""; + const { data: sshHosts } = useListWorkspaceSshHosts(currentProject.id); const { data: members = [] } = useGetWorkspaceUsers(projectId); const { data: groups = [] } = useListWorkspaceGroups(projectId); const [expandedMappings, setExpandedMappings] = useState>({}); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx index 62f563cce..f29f0147e 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx @@ -30,7 +30,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionSshHostActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSshHostActions, ProjectPermissionSub, useProject } from "@app/context"; import { fetchSshHostUserCaPublicKey, useListWorkspaceSshHosts } from "@app/hooks/api"; import { LoginMappingSource } from "@app/hooks/api/sshHost/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -43,8 +43,8 @@ type Props = { }; export const SshHostsTable = ({ handlePopUpOpen }: Props) => { - const { currentWorkspace } = useWorkspace(); - const { data, isPending } = useListWorkspaceSshHosts(currentWorkspace?.id || ""); + const { currentProject } = useProject(); + const { data, isPending } = useListWorkspaceSshHosts(currentProject?.id || ""); const downloadTxtFile = (filename: string, content: string) => { const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); diff --git a/frontend/src/pages/ssh/layout.tsx b/frontend/src/pages/ssh/layout.tsx index 008aca393..9b1a0057b 100644 --- a/frontend/src/pages/ssh/layout.tsx +++ b/frontend/src/pages/ssh/layout.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; import { BreadcrumbTypes } from "@app/components/v2"; -import { workspaceKeys } from "@app/hooks/api"; +import { projectKeys } from "@app/hooks/api"; +import { fetchProjectById } from "@app/hooks/api/projects/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; -import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect"; import { SshLayout } from "@app/layouts/SshLayout"; @@ -13,15 +13,15 @@ export const Route = createFileRoute( component: SshLayout, beforeLoad: async ({ params, context }) => { const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId) + queryKey: projectKeys.getProjectById(params.projectId), + queryFn: () => fetchProjectById(params.projectId) }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId + projectId: params.projectId }), - queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + queryFn: () => fetchUserProjectPermissions({ projectId: params.projectId }) }); return { diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 1f62ba7b9..4d01ff47d 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -82,12 +82,15 @@ import { Route as organizationSettingsPageOauthCallbackPageRouteImport } from '. import { Route as projectAuditLogsPageRouteSshImport } from './pages/project/AuditLogsPage/route-ssh' import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh' import { Route as projectAuditLogsPageRouteSecretScanningImport } from './pages/project/AuditLogsPage/route-secret-scanning' +import { Route as projectAppConnectionsPageRouteSecretScanningImport } from './pages/project/AppConnectionsPage/route-secret-scanning' import { Route as projectAccessControlPageRouteSecretScanningImport } from './pages/project/AccessControlPage/route-secret-scanning' import { Route as projectAuditLogsPageRouteSecretManagerImport } from './pages/project/AuditLogsPage/route-secret-manager' +import { Route as projectAppConnectionsPageRouteSecretManagerImport } from './pages/project/AppConnectionsPage/route-secret-manager' import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager' import { Route as projectAuditLogsPageRouteKmsImport } from './pages/project/AuditLogsPage/route-kms' import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms' import { Route as projectAuditLogsPageRouteCertManagerImport } from './pages/project/AuditLogsPage/route-cert-manager' +import { Route as projectAppConnectionsPageRouteCertManagerImport } from './pages/project/AppConnectionsPage/route-cert-manager' import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager' import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route' import { Route as sshSshHostsPageRouteImport } from './pages/ssh/SshHostsPage/route' @@ -920,6 +923,13 @@ const projectAuditLogsPageRouteSecretScanningRoute = getParentRoute: () => secretScanningLayoutRoute, } as any) +const projectAppConnectionsPageRouteSecretScanningRoute = + projectAppConnectionsPageRouteSecretScanningImport.update({ + id: '/app-connections', + path: '/app-connections', + getParentRoute: () => secretScanningLayoutRoute, + } as any) + const projectAccessControlPageRouteSecretScanningRoute = projectAccessControlPageRouteSecretScanningImport.update({ id: '/access-management', @@ -943,6 +953,13 @@ const projectAuditLogsPageRouteSecretManagerRoute = getParentRoute: () => secretManagerLayoutRoute, } as any) +const projectAppConnectionsPageRouteSecretManagerRoute = + projectAppConnectionsPageRouteSecretManagerImport.update({ + id: '/app-connections', + path: '/app-connections', + getParentRoute: () => secretManagerLayoutRoute, + } as any) + const projectAccessControlPageRouteSecretManagerRoute = projectAccessControlPageRouteSecretManagerImport.update({ id: '/access-management', @@ -989,6 +1006,13 @@ const projectAuditLogsPageRouteCertManagerRoute = getParentRoute: () => certManagerLayoutRoute, } as any) +const projectAppConnectionsPageRouteCertManagerRoute = + projectAppConnectionsPageRouteCertManagerImport.update({ + id: '/app-connections', + path: '/app-connections', + getParentRoute: () => certManagerLayoutRoute, + } as any) + const projectAccessControlPageRouteCertManagerRoute = projectAccessControlPageRouteCertManagerImport.update({ id: '/access-management', @@ -2730,6 +2754,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport parentRoute: typeof certManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections' + path: '/app-connections' + fullPath: '/projects/cert-management/$projectId/app-connections' + preLoaderRoute: typeof projectAppConnectionsPageRouteCertManagerImport + parentRoute: typeof certManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': { id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs' path: '/audit-logs' @@ -2772,6 +2803,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport parentRoute: typeof secretManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections' + path: '/app-connections' + fullPath: '/projects/secret-management/$projectId/app-connections' + preLoaderRoute: typeof projectAppConnectionsPageRouteSecretManagerImport + parentRoute: typeof secretManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': { id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs' path: '/audit-logs' @@ -2793,6 +2831,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSecretScanningImport parentRoute: typeof secretScanningLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections' + path: '/app-connections' + fullPath: '/projects/secret-scanning/$projectId/app-connections' + preLoaderRoute: typeof projectAppConnectionsPageRouteSecretScanningImport + parentRoute: typeof secretScanningLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': { id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs' path: '/audit-logs' @@ -3822,6 +3867,7 @@ interface certManagerLayoutRouteChildren { certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute + projectAppConnectionsPageRouteCertManagerRoute: typeof projectAppConnectionsPageRouteCertManagerRoute projectAuditLogsPageRouteCertManagerRoute: typeof projectAuditLogsPageRouteCertManagerRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren @@ -3841,6 +3887,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, projectAccessControlPageRouteCertManagerRoute: projectAccessControlPageRouteCertManagerRoute, + projectAppConnectionsPageRouteCertManagerRoute: + projectAppConnectionsPageRouteCertManagerRoute, projectAuditLogsPageRouteCertManagerRoute: projectAuditLogsPageRouteCertManagerRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute: @@ -4212,6 +4260,7 @@ interface secretManagerLayoutRouteChildren { secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute + projectAppConnectionsPageRouteSecretManagerRoute: typeof projectAppConnectionsPageRouteSecretManagerRoute projectAuditLogsPageRouteSecretManagerRoute: typeof projectAuditLogsPageRouteSecretManagerRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute @@ -4233,6 +4282,8 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = { secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute, projectAccessControlPageRouteSecretManagerRoute: projectAccessControlPageRouteSecretManagerRoute, + projectAppConnectionsPageRouteSecretManagerRoute: + projectAppConnectionsPageRouteSecretManagerRoute, projectAuditLogsPageRouteSecretManagerRoute: projectAuditLogsPageRouteSecretManagerRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute: @@ -4290,6 +4341,7 @@ interface secretScanningLayoutRouteChildren { secretScanningSecretScanningFindingsPageRouteRoute: typeof secretScanningSecretScanningFindingsPageRouteRoute secretScanningSettingsPageRouteRoute: typeof secretScanningSettingsPageRouteRoute projectAccessControlPageRouteSecretScanningRoute: typeof projectAccessControlPageRouteSecretScanningRoute + projectAppConnectionsPageRouteSecretScanningRoute: typeof projectAppConnectionsPageRouteSecretScanningRoute projectAuditLogsPageRouteSecretScanningRoute: typeof projectAuditLogsPageRouteSecretScanningRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren projectGroupDetailsByIDPageRouteSecretScanningRoute: typeof projectGroupDetailsByIDPageRouteSecretScanningRoute @@ -4304,6 +4356,8 @@ const secretScanningLayoutRouteChildren: secretScanningLayoutRouteChildren = { secretScanningSettingsPageRouteRoute: secretScanningSettingsPageRouteRoute, projectAccessControlPageRouteSecretScanningRoute: projectAccessControlPageRouteSecretScanningRoute, + projectAppConnectionsPageRouteSecretScanningRoute: + projectAppConnectionsPageRouteSecretScanningRoute, projectAuditLogsPageRouteSecretScanningRoute: projectAuditLogsPageRouteSecretScanningRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: @@ -4689,15 +4743,18 @@ export interface FileRoutesByFullPath { '/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/projects/cert-management/$projectId/app-connections': typeof projectAppConnectionsPageRouteCertManagerRoute '/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/projects/cert-management/$projectId/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/projects/cert-management/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren '/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute '/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/projects/secret-management/$projectId/app-connections': typeof projectAppConnectionsPageRouteSecretManagerRoute '/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/projects/secret-management/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/projects/secret-scanning/$projectId/app-connections': typeof projectAppConnectionsPageRouteSecretScanningRoute '/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/projects/secret-scanning/$projectId/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren '/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute @@ -4902,12 +4959,15 @@ export interface FileRoutesByTo { '/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/projects/cert-management/$projectId/app-connections': typeof projectAppConnectionsPageRouteCertManagerRoute '/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute '/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/projects/secret-management/$projectId/app-connections': typeof projectAppConnectionsPageRouteSecretManagerRoute '/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/projects/secret-scanning/$projectId/app-connections': typeof projectAppConnectionsPageRouteSecretScanningRoute '/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute '/projects/ssh/$projectId/audit-logs': typeof projectAuditLogsPageRouteSshRoute @@ -5128,15 +5188,18 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections': typeof projectAppConnectionsPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections': typeof projectAppConnectionsPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections': typeof projectAppConnectionsPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute @@ -5351,15 +5414,18 @@ export interface FileRouteTypes { | '/projects/ssh/$projectId/overview' | '/projects/ssh/$projectId/settings' | '/projects/cert-management/$projectId/access-management' + | '/projects/cert-management/$projectId/app-connections' | '/projects/cert-management/$projectId/audit-logs' | '/projects/cert-management/$projectId/certificate-templates' | '/projects/cert-management/$projectId/subscribers' | '/projects/kms/$projectId/access-management' | '/projects/kms/$projectId/audit-logs' | '/projects/secret-management/$projectId/access-management' + | '/projects/secret-management/$projectId/app-connections' | '/projects/secret-management/$projectId/audit-logs' | '/projects/secret-management/$projectId/integrations' | '/projects/secret-scanning/$projectId/access-management' + | '/projects/secret-scanning/$projectId/app-connections' | '/projects/secret-scanning/$projectId/audit-logs' | '/projects/secret-scanning/$projectId/data-sources' | '/projects/ssh/$projectId/access-management' @@ -5563,12 +5629,15 @@ export interface FileRouteTypes { | '/projects/ssh/$projectId/overview' | '/projects/ssh/$projectId/settings' | '/projects/cert-management/$projectId/access-management' + | '/projects/cert-management/$projectId/app-connections' | '/projects/cert-management/$projectId/audit-logs' | '/projects/kms/$projectId/access-management' | '/projects/kms/$projectId/audit-logs' | '/projects/secret-management/$projectId/access-management' + | '/projects/secret-management/$projectId/app-connections' | '/projects/secret-management/$projectId/audit-logs' | '/projects/secret-scanning/$projectId/access-management' + | '/projects/secret-scanning/$projectId/app-connections' | '/projects/secret-scanning/$projectId/audit-logs' | '/projects/ssh/$projectId/access-management' | '/projects/ssh/$projectId/audit-logs' @@ -5787,15 +5856,18 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers' | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management' | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources' | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management' @@ -6388,6 +6460,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers", @@ -6424,6 +6497,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug", @@ -6441,6 +6515,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId", @@ -6547,6 +6622,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections": { + "filePath": "project/AppConnectionsPage/route-cert-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs": { "filePath": "project/AuditLogsPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" @@ -6578,6 +6657,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-secret-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections": { + "filePath": "project/AppConnectionsPage/route-secret-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs": { "filePath": "project/AuditLogsPage/route-secret-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout" @@ -6670,6 +6753,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-secret-scanning.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections": { + "filePath": "project/AppConnectionsPage/route-secret-scanning.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs": { "filePath": "project/AuditLogsPage/route-secret-scanning.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 70c34edc6..76680a851 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -63,6 +63,7 @@ const secretManagerRoutes = route("/projects/secret-management/$projectId", [ ]), route("/audit-logs", "project/AuditLogsPage/route-secret-manager.tsx"), route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"), + route("/app-connections", "project/AppConnectionsPage/route-secret-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"), route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-manager.tsx"), @@ -313,6 +314,7 @@ const certManagerRoutes = route("/projects/cert-management/$projectId", [ route("/settings", "cert-manager/SettingsPage/route.tsx"), route("/audit-logs", "project/AuditLogsPage/route-cert-manager.tsx"), route("/access-management", "project/AccessControlPage/route-cert-manager.tsx"), + route("/app-connections", "project/AppConnectionsPage/route-cert-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-cert-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-cert-manager.tsx"), route("/members/$membershipId", "project/MemberDetailsByIDPage/route-cert-manager.tsx"), @@ -361,6 +363,7 @@ const secretScanningRoutes = route("/projects/secret-scanning/$projectId", [ route("/settings", "secret-scanning/SettingsPage/route.tsx"), route("/audit-logs", "project/AuditLogsPage/route-secret-scanning.tsx"), route("/access-management", "project/AccessControlPage/route-secret-scanning.tsx"), + route("/app-connections", "project/AppConnectionsPage/route-secret-scanning.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-scanning.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-scanning.tsx"), route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-scanning.tsx"),