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/ee/routes/v1/audit-log-stream-routers/audit-log-stream-router.ts b/backend/src/ee/routes/v1/audit-log-stream-routers/audit-log-stream-router.ts
index e0ac0b6af..48eed14c9 100644
--- a/backend/src/ee/routes/v1/audit-log-stream-routers/audit-log-stream-router.ts
+++ b/backend/src/ee/routes/v1/audit-log-stream-routers/audit-log-stream-router.ts
@@ -1,5 +1,9 @@
import { z } from "zod";
+import {
+ AzureProviderListItemSchema,
+ SanitizedAzureProviderSchema
+} from "@app/ee/services/audit-log-stream/azure/azure-provider-schemas";
import {
CriblProviderListItemSchema,
SanitizedCriblProviderSchema
@@ -24,6 +28,7 @@ const SanitizedAuditLogStreamSchema = z.union([
SanitizedCustomProviderSchema,
SanitizedDatadogProviderSchema,
SanitizedSplunkProviderSchema,
+ SanitizedAzureProviderSchema,
SanitizedCriblProviderSchema
]);
@@ -31,6 +36,7 @@ const ProviderOptionsSchema = z.discriminatedUnion("provider", [
CustomProviderListItemSchema,
DatadogProviderListItemSchema,
SplunkProviderListItemSchema,
+ AzureProviderListItemSchema,
CriblProviderListItemSchema
]);
diff --git a/backend/src/ee/routes/v1/audit-log-stream-routers/index.ts b/backend/src/ee/routes/v1/audit-log-stream-routers/index.ts
index f40a82d89..ad338c801 100644
--- a/backend/src/ee/routes/v1/audit-log-stream-routers/index.ts
+++ b/backend/src/ee/routes/v1/audit-log-stream-routers/index.ts
@@ -1,4 +1,9 @@
import { LogProvider } from "@app/ee/services/audit-log-stream/audit-log-stream-enums";
+import {
+ CreateAzureProviderLogStreamSchema,
+ SanitizedAzureProviderSchema,
+ UpdateAzureProviderLogStreamSchema
+} from "@app/ee/services/audit-log-stream/azure/azure-provider-schemas";
import {
CreateCriblProviderLogStreamSchema,
SanitizedCriblProviderSchema,
@@ -26,6 +31,15 @@ export * from "./audit-log-stream-router";
export const AUDIT_LOG_STREAM_REGISTER_ROUTER_MAP: Record Promise> =
{
+ [LogProvider.Azure]: async (server: FastifyZodProvider) => {
+ registerAuditLogStreamEndpoints({
+ server,
+ provider: LogProvider.Azure,
+ sanitizedResponseSchema: SanitizedAzureProviderSchema,
+ createSchema: CreateAzureProviderLogStreamSchema,
+ updateSchema: UpdateAzureProviderLogStreamSchema
+ });
+ },
[LogProvider.Custom]: async (server: FastifyZodProvider) => {
registerAuditLogStreamEndpoints({
server,
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-stream/audit-log-stream-enums.ts b/backend/src/ee/services/audit-log-stream/audit-log-stream-enums.ts
index 78233f774..ebef18574 100644
--- a/backend/src/ee/services/audit-log-stream/audit-log-stream-enums.ts
+++ b/backend/src/ee/services/audit-log-stream/audit-log-stream-enums.ts
@@ -1,4 +1,5 @@
export enum LogProvider {
+ Azure = "azure",
Cribl = "cribl",
Custom = "custom",
Datadog = "datadog",
diff --git a/backend/src/ee/services/audit-log-stream/audit-log-stream-factory.ts b/backend/src/ee/services/audit-log-stream/audit-log-stream-factory.ts
index 21d629a53..8dde0e079 100644
--- a/backend/src/ee/services/audit-log-stream/audit-log-stream-factory.ts
+++ b/backend/src/ee/services/audit-log-stream/audit-log-stream-factory.ts
@@ -1,5 +1,6 @@
import { LogProvider } from "./audit-log-stream-enums";
import { TAuditLogStreamCredentials, TLogStreamFactory } from "./audit-log-stream-types";
+import { AzureProviderFactory } from "./azure/azure-provider-factory";
import { CriblProviderFactory } from "./cribl/cribl-provider-factory";
import { CustomProviderFactory } from "./custom/custom-provider-factory";
import { DatadogProviderFactory } from "./datadog/datadog-provider-factory";
@@ -8,6 +9,7 @@ import { SplunkProviderFactory } from "./splunk/splunk-provider-factory";
type TLogStreamFactoryImplementation = TLogStreamFactory;
export const LOG_STREAM_FACTORY_MAP: Record = {
+ [LogProvider.Azure]: AzureProviderFactory as TLogStreamFactoryImplementation,
[LogProvider.Datadog]: DatadogProviderFactory as TLogStreamFactoryImplementation,
[LogProvider.Splunk]: SplunkProviderFactory as TLogStreamFactoryImplementation,
[LogProvider.Custom]: CustomProviderFactory as TLogStreamFactoryImplementation,
diff --git a/backend/src/ee/services/audit-log-stream/audit-log-stream-fns.ts b/backend/src/ee/services/audit-log-stream/audit-log-stream-fns.ts
index d03a5c8a7..07d833030 100644
--- a/backend/src/ee/services/audit-log-stream/audit-log-stream-fns.ts
+++ b/backend/src/ee/services/audit-log-stream/audit-log-stream-fns.ts
@@ -3,6 +3,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TAuditLogStream, TAuditLogStreamCredentials } from "./audit-log-stream-types";
+import { getAzureProviderListItem } from "./azure/azure-provider-fns";
import { getCriblProviderListItem } from "./cribl/cribl-provider-fns";
import { getCustomProviderListItem } from "./custom/custom-provider-fns";
import { getDatadogProviderListItem } from "./datadog/datadog-provider-fns";
@@ -13,6 +14,7 @@ export const listProviderOptions = () => {
getDatadogProviderListItem(),
getSplunkProviderListItem(),
getCustomProviderListItem(),
+ getAzureProviderListItem(),
getCriblProviderListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
diff --git a/backend/src/ee/services/audit-log-stream/audit-log-stream-types.ts b/backend/src/ee/services/audit-log-stream/audit-log-stream-types.ts
index 1ef33befe..5983e50bf 100644
--- a/backend/src/ee/services/audit-log-stream/audit-log-stream-types.ts
+++ b/backend/src/ee/services/audit-log-stream/audit-log-stream-types.ts
@@ -1,17 +1,19 @@
import { TAuditLogs } from "@app/db/schemas";
import { LogProvider } from "./audit-log-stream-enums";
+import { TAzureProvider, TAzureProviderCredentials } from "./azure/azure-provider-types";
import { TCriblProvider, TCriblProviderCredentials } from "./cribl/cribl-provider-types";
import { TCustomProvider, TCustomProviderCredentials } from "./custom/custom-provider-types";
import { TDatadogProvider, TDatadogProviderCredentials } from "./datadog/datadog-provider-types";
import { TSplunkProvider, TSplunkProviderCredentials } from "./splunk/splunk-provider-types";
-export type TAuditLogStream = TDatadogProvider | TSplunkProvider | TCustomProvider | TCriblProvider;
+export type TAuditLogStream = TDatadogProvider | TSplunkProvider | TCustomProvider | TAzureProvider | TCriblProvider;
export type TAuditLogStreamCredentials =
| TDatadogProviderCredentials
| TSplunkProviderCredentials
| TCustomProviderCredentials
+ | TAzureProviderCredentials
| TCriblProviderCredentials;
export type TCreateAuditLogStreamDTO = {
diff --git a/backend/src/ee/services/audit-log-stream/azure/azure-provider-factory.ts b/backend/src/ee/services/audit-log-stream/azure/azure-provider-factory.ts
new file mode 100644
index 000000000..9a6157666
--- /dev/null
+++ b/backend/src/ee/services/audit-log-stream/azure/azure-provider-factory.ts
@@ -0,0 +1,98 @@
+import { RawAxiosRequestHeaders } from "axios";
+
+import { request } from "@app/lib/config/request";
+import { BadRequestError } from "@app/lib/errors";
+import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
+
+import { AUDIT_LOG_STREAM_TIMEOUT } from "../../audit-log/audit-log-queue";
+import { TLogStreamFactoryStreamLog, TLogStreamFactoryValidateCredentials } from "../audit-log-stream-types";
+import { TAzureProviderCredentials } from "./azure-provider-types";
+
+function createPayload(event: { createdAt?: Date | string } & Record) {
+ return [
+ {
+ ...event,
+ TimeGenerated: (event.createdAt ? new Date(event.createdAt) : new Date()).toISOString()
+ }
+ ];
+}
+
+async function getAzureToken(tenantId: string, clientId: string, clientSecret: string) {
+ const { data } = await request.post<{ access_token: string }>(
+ `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
+ new URLSearchParams({
+ grant_type: "client_credentials",
+ client_id: clientId,
+ client_secret: clientSecret,
+ scope: "https://monitor.azure.com/.default"
+ }),
+ {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ }
+ }
+ );
+
+ return data.access_token;
+}
+
+export const AzureProviderFactory = () => {
+ const validateCredentials: TLogStreamFactoryValidateCredentials = async ({
+ credentials
+ }) => {
+ const { tenantId, clientId, clientSecret, dceUrl, dcrId, cltName } = credentials;
+
+ await blockLocalAndPrivateIpAddresses(dceUrl);
+
+ const token = await getAzureToken(tenantId, clientId, clientSecret);
+
+ const streamHeaders: RawAxiosRequestHeaders = {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`
+ };
+
+ await request
+ .post(
+ `${dceUrl}/dataCollectionRules/${dcrId}/streams/Custom-${cltName}_CL?api-version=2023-01-01`,
+ createPayload({ ping: "ok" }),
+ {
+ headers: streamHeaders,
+ timeout: AUDIT_LOG_STREAM_TIMEOUT,
+ signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
+ }
+ )
+ .catch((err) => {
+ throw new BadRequestError({ message: `Failed to connect with Azure: ${(err as Error)?.message}` });
+ });
+
+ return credentials;
+ };
+
+ const streamLog: TLogStreamFactoryStreamLog = async ({ credentials, auditLog }) => {
+ const { tenantId, clientId, clientSecret, dceUrl, dcrId, cltName } = credentials;
+
+ await blockLocalAndPrivateIpAddresses(dceUrl);
+
+ const token = await getAzureToken(tenantId, clientId, clientSecret);
+
+ const streamHeaders: RawAxiosRequestHeaders = {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`
+ };
+
+ await request.post(
+ `${dceUrl}/dataCollectionRules/${dcrId}/streams/Custom-${cltName}_CL?api-version=2023-01-01`,
+ createPayload(auditLog),
+ {
+ headers: streamHeaders,
+ timeout: AUDIT_LOG_STREAM_TIMEOUT,
+ signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
+ }
+ );
+ };
+
+ return {
+ validateCredentials,
+ streamLog
+ };
+};
diff --git a/backend/src/ee/services/audit-log-stream/azure/azure-provider-fns.ts b/backend/src/ee/services/audit-log-stream/azure/azure-provider-fns.ts
new file mode 100644
index 000000000..e558b69e2
--- /dev/null
+++ b/backend/src/ee/services/audit-log-stream/azure/azure-provider-fns.ts
@@ -0,0 +1,8 @@
+import { LogProvider } from "../audit-log-stream-enums";
+
+export const getAzureProviderListItem = () => {
+ return {
+ name: "Azure" as const,
+ provider: LogProvider.Azure as const
+ };
+};
diff --git a/backend/src/ee/services/audit-log-stream/azure/azure-provider-schemas.ts b/backend/src/ee/services/audit-log-stream/azure/azure-provider-schemas.ts
new file mode 100644
index 000000000..50def1d79
--- /dev/null
+++ b/backend/src/ee/services/audit-log-stream/azure/azure-provider-schemas.ts
@@ -0,0 +1,52 @@
+import RE2 from "re2";
+import { z } from "zod";
+
+import { LogProvider } from "../audit-log-stream-enums";
+import { BaseProviderSchema } from "../audit-log-stream-schemas";
+
+export const AzureProviderCredentialsSchema = z.object({
+ tenantId: z.string().trim().uuid(),
+ clientId: z.string().trim().uuid(),
+ clientSecret: z.string().trim().length(40),
+
+ // Data Collection Endpoint URL
+ dceUrl: z.string().trim().url().min(1).max(255),
+
+ // Data Collection Rule Immutable ID
+ dcrId: z
+ .string()
+ .trim()
+ .refine((val) => new RE2(/^dcr-[0-9a-f]{32}$/).test(val), "DCR ID must be in dcr-*** format"),
+
+ // Custom Log Table Name
+ cltName: z.string().trim().min(1).max(255)
+});
+
+const BaseAzureProviderSchema = BaseProviderSchema.extend({ provider: z.literal(LogProvider.Azure) });
+
+export const AzureProviderSchema = BaseAzureProviderSchema.extend({
+ credentials: AzureProviderCredentialsSchema
+});
+
+export const SanitizedAzureProviderSchema = BaseAzureProviderSchema.extend({
+ credentials: AzureProviderCredentialsSchema.pick({
+ tenantId: true,
+ clientId: true,
+ dceUrl: true,
+ dcrId: true,
+ cltName: true
+ })
+});
+
+export const AzureProviderListItemSchema = z.object({
+ name: z.literal("Azure"),
+ provider: z.literal(LogProvider.Azure)
+});
+
+export const CreateAzureProviderLogStreamSchema = z.object({
+ credentials: AzureProviderCredentialsSchema
+});
+
+export const UpdateAzureProviderLogStreamSchema = z.object({
+ credentials: AzureProviderCredentialsSchema
+});
diff --git a/backend/src/ee/services/audit-log-stream/azure/azure-provider-types.ts b/backend/src/ee/services/audit-log-stream/azure/azure-provider-types.ts
new file mode 100644
index 000000000..0ba5f120d
--- /dev/null
+++ b/backend/src/ee/services/audit-log-stream/azure/azure-provider-types.ts
@@ -0,0 +1,7 @@
+import { z } from "zod";
+
+import { AzureProviderCredentialsSchema, AzureProviderSchema } from "./azure-provider-schemas";
+
+export type TAzureProvider = z.infer;
+
+export type TAzureProviderCredentials = z.infer;
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 88b4480bc..3e2190b65 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",
@@ -249,9 +249,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",
@@ -264,8 +264,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",
@@ -666,8 +666,8 @@ interface DeleteSecretBatchEvent {
};
}
-interface GetWorkspaceKeyEvent {
- type: EventType.GET_WORKSPACE_KEY;
+interface GetProjectKeyEvent {
+ type: EventType.GET_PROJECT_KEY;
metadata: {
keyId: string;
};
@@ -1559,24 +1559,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;
@@ -1715,7 +1715,7 @@ interface DeleteSecretImportEvent {
}
interface UpdateUserRole {
- type: EventType.UPDATE_USER_WORKSPACE_ROLE;
+ type: EventType.UPDATE_USER_PROJECT_ROLE;
metadata: {
userId: string;
email: string;
@@ -1725,7 +1725,7 @@ interface UpdateUserRole {
}
interface UpdateUserDeniedPermissions {
- type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
+ type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS;
metadata: {
userId: string;
email: string;
@@ -3496,7 +3496,7 @@ export type Event =
| MoveSecretsEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
- | GetWorkspaceKeyEvent
+ | GetProjectKeyEvent
| AuthorizeIntegrationEvent
| UpdateIntegrationAuthEvent
| UnauthorizeIntegrationEvent
@@ -3585,9 +3585,9 @@ export type Event =
| GetEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
- | AddWorkspaceMemberEvent
- | AddBatchWorkspaceMemberEvent
- | RemoveWorkspaceMemberEvent
+ | AddProjectMemberEvent
+ | AddBatchProjectMemberEvent
+ | RemoveProjectMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent
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/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/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index 97aa17874..9a6a71251 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -711,13 +711,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.",
@@ -729,10 +729,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.",
@@ -759,10 +759,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."
@@ -815,15 +815,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."
}
@@ -877,31 +877,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)",
@@ -913,7 +913,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.",
@@ -927,12 +927,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."
@@ -964,7 +964,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.",
@@ -984,7 +984,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."
@@ -992,7 +992,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.",
@@ -1011,7 +1011,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.",
@@ -1025,11 +1025,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."
},
@@ -1043,7 +1043,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."
},
@@ -1053,7 +1053,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: {
@@ -1070,10 +1070,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."
diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts
index 1df89cd19..b9dc65676 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();
@@ -2295,6 +2296,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/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 5c5af7523..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
);
@@ -1093,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
@@ -1133,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
}
@@ -1154,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/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-service.ts b/backend/src/services/project/project-service.ts
index 2232a0411..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
diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts
index bf7b633a9..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;
diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts
index c5eb0adde..8e0892bd1 100644
--- a/backend/src/services/secret-folder/secret-folder-service.ts
+++ b/backend/src/services/secret-folder/secret-folder-service.ts
@@ -47,7 +47,7 @@ type TSecretFolderServiceFactoryDep = {
folderCommitService: Pick;
projectDAL: Pick;
secretApprovalPolicyService: Pick;
- secretV2BridgeDAL: Pick;
+ secretV2BridgeDAL: Pick;
};
export type TSecretFolderServiceFactory = ReturnType;
@@ -398,6 +398,7 @@ export const secretFolderServiceFactory = ({
await Promise.all(result.map(async (res) => snapshotService.performSnapshot(res.newFolder.parentId as string)));
+ await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
return {
projectId,
newFolders: result.map((res) => res.newFolder),
@@ -522,6 +523,7 @@ export const secretFolderServiceFactory = ({
}
await snapshotService.performSnapshot(newFolder.parentId as string);
+ await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
return {
folder: { ...newFolder, path: newFolderWithFullPath.path },
old: { ...folder, path: folderWithFullPath.path }
@@ -724,6 +726,7 @@ export const secretFolderServiceFactory = ({
});
await snapshotService.performSnapshot(folder.parentId as string);
+ await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
return folder;
};
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/audit-log-streams/audit-log-streams.mdx b/docs/documentation/platform/audit-log-streams/audit-log-streams.mdx
index 50fa75e92..ad030ec26 100644
--- a/docs/documentation/platform/audit-log-streams/audit-log-streams.mdx
+++ b/docs/documentation/platform/audit-log-streams/audit-log-streams.mdx
@@ -45,6 +45,116 @@ Infisical Audit Log Streaming enables you to transmit your organization's audit
## Example Providers
+
+ Infisical offers a dedicated **Azure** provider to stream your audit logs, enabling seamless integration with services like Microsoft Sentinel.
+
+
+ After setting up all Azure resources, it may take 10-20 minutes for logs to begin streaming.
+
+
+
+
+ Navigate to [Data Collection Endpoints](https://portal.azure.com/#view/HubsExtension/BrowseResource.ReactView/resourceType/microsoft.insights%2Fdatacollectionendpoints) and click **Create**.
+
+ 
+
+ Configure your Data Collection Endpoint by providing an **Endpoint Name**, **Subscription**, and a **Resource group**. Then click **Review + Create**.
+
+ 
+
+ After creation, it may take a few minutes for the Data Collection Endpoint to appear. Once visible, click on it and copy the **Logs Ingestion** URL. You will need this URL in later steps.
+
+ 
+
+
+
+ If you already have a Log Analytics Workspace, you may skip this step.
+
+
+ Navigate to [Log Analytics Workspaces](https://portal.azure.com/#browse/Microsoft.OperationalInsights%2Fworkspaces) and click **Create**.
+
+ 
+
+ Configure your Log Analytics Workspace by providing a **Subscription**, **Resource group**, and a **Name**. Then click **Review + Create**.
+
+ 
+
+ Once the workspace is deployed, click **Go to resource** to access it.
+
+ 
+
+
+ Within your Log Analytics Workspace, navigate to **Tables** and click **Create**. Select **New custom log (DCR-based)** from the dropdown.
+
+ 
+
+ Configure the Custom Log Table: Provide a **Table name** (e.g., `InfisicalLogs`), select the **Data collection endpoint** created in Step 1, and create a new **Data collection rule** as illustrated in the image below. Then, click **Next**.
+
+ 
+
+ On the **Schema and transformation** page, you'll be prompted to upload a **Log Sample**. Create a `.json` file with the following content and upload it:
+
+ ```json
+ {
+ "id": "00000000-0000-0000-0000-000000000000",
+ "actor": "user",
+ "actorMetadata": {
+ "email": "user@example.com",
+ "userId": "00000000-0000-0000-0000-000000000000",
+ "username": "user@example.com"
+ },
+ "ipAddress": "0.0.0.0",
+ "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
+ "userAgentType": "web",
+ "eventType": "get-secrets",
+ "eventMetadata": {},
+ "projectName": "MyProject",
+ "orgId": "00000000-0000-0000-0000-000000000000",
+ "projectId": "00000000-0000-0000-0000-000000000000",
+ "TimeGenerated": "2025-01-01T00:00:00.000Z"
+ }
+ ```
+
+ Optionally, you can add **Transformations** to further destructure the data. For example, to extract actor email and userId:
+
+ ```
+ source
+ | extend
+ ActorEmail = tostring(actorMetadata.email),
+ ActorUserId = tostring(actorMetadata.userId)
+ ```
+
+ On the final step, click **Create**.
+
+
+ It may take a few minutes for your Custom Log Table to be created and appear under Tables.
+
+
+
+ After creating your Data Collection Rule, you'll need its **Immutable ID**.
+
+ Navigate to [Data collection rules](https://portal.azure.com/#view/HubsExtension/BrowseResource.ReactView/resourceType/microsoft.insights%2Fdatacollectionrules). Click on your newly created DCR and copy its **Immutable ID** for the next step.
+
+ 
+
+
+ In Infisical, create a new audit log stream and select the **Azure** provider. Input the following details:
+
+ - **Tenant ID**: Your Tenant ID
+ - **Client ID**: The Client ID of an App Registration
+ - **Client Secret**: The Client Secret of an App Registration
+ - **Data Collection Endpoint URL**: Obtained from Step 1
+ - **Data Collection Rule Immutable ID**: Obtained from Step 4
+ - **Custom Log Table Name**: Defined in Step 3
+
+ 
+
+
+ The App Registration used for authentication must have the **Monitoring Metrics Publisher** role assigned on the **Data Collection Rule** created in Step 3. [See Microsoft Guide](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/tutorial-logs-ingestion-portal#assign-permissions-to-the-dcr).
+
+
+
+
You can stream to Better Stack using a **Custom** log stream.
diff --git a/docs/images/platform/audit-log-streams/azure-configure-dce.png b/docs/images/platform/audit-log-streams/azure-configure-dce.png
new file mode 100644
index 000000000..b0b32f0b2
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-configure-dce.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-configure-law.png b/docs/images/platform/audit-log-streams/azure-configure-law.png
new file mode 100644
index 000000000..11ec8d86f
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-configure-law.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-configure-table.png b/docs/images/platform/audit-log-streams/azure-configure-table.png
new file mode 100644
index 000000000..7dba237b9
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-configure-table.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-create-als.png b/docs/images/platform/audit-log-streams/azure-create-als.png
new file mode 100644
index 000000000..bdfb2f8d6
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-create-als.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-create-dce.png b/docs/images/platform/audit-log-streams/azure-create-dce.png
new file mode 100644
index 000000000..00a546c30
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-create-dce.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-create-law.png b/docs/images/platform/audit-log-streams/azure-create-law.png
new file mode 100644
index 000000000..1150f64ff
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-create-law.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-dce-url.png b/docs/images/platform/audit-log-streams/azure-dce-url.png
new file mode 100644
index 000000000..d8c1dc96d
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-dce-url.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-dcr.png b/docs/images/platform/audit-log-streams/azure-dcr.png
new file mode 100644
index 000000000..441808947
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-dcr.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-go-to-resource.png b/docs/images/platform/audit-log-streams/azure-go-to-resource.png
new file mode 100644
index 000000000..ef197517d
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-go-to-resource.png differ
diff --git a/docs/images/platform/audit-log-streams/azure-new-table.png b/docs/images/platform/audit-log-streams/azure-new-table.png
new file mode 100644
index 000000000..829af3e53
Binary files /dev/null and b/docs/images/platform/audit-log-streams/azure-new-table.png differ
diff --git a/docs/integrations/platforms/kubernetes/infisical-dynamic-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-dynamic-secret-crd.mdx
index 76dd24cf7..848da4055 100644
--- a/docs/integrations/platforms/kubernetes/infisical-dynamic-secret-crd.mdx
+++ b/docs/integrations/platforms/kubernetes/infisical-dynamic-secret-crd.mdx
@@ -223,7 +223,9 @@ spec:
spec:
dynamicSecret:
secretName:
- projectId:
+ # Use either projectId OR projectSlug, not both
+ projectId: # Either projectId or projectSlug is required
+ # projectSlug:
environmentSlug:
secretsPath:
```
@@ -238,8 +240,21 @@ spec:
The project ID of where the dynamic secret is stored in Infisical.
+
+
+ Please note that you can only use either `projectId` or `projectSlug` in the `dynamicSecret` field.
+
+
+ The project slug of where the dynamic secret is stored in Infisical.
+
+
+ Please note that you can only use either `projectId` or `projectSlug` in the `dynamicSecret` field.
+
+
+
+
{" "}
diff --git a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx
index d5a9c1ef4..0affe7841 100644
--- a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx
+++ b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx
@@ -44,7 +44,8 @@ Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes sec
deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted.
destination:
- projectId:
+ projectId: # Either projectId or projectSlug is required
+ projectSlug:
environmentSlug:
secretsPath:
@@ -203,6 +204,18 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
The project ID where you want to create the secrets in Infisical.
+
+
+ Please note that you can only use either `projectId` or `projectSlug` in the `destination` field.
+
+
+
+
+ The project slug where you want to create the secrets in Infisical.
+
+
+ Please note that you can only use either `projectId` or `projectSlug` in the `destination` field.
+
diff --git a/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx
index d0e403b79..d72d58957 100644
--- a/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx
+++ b/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx
@@ -142,7 +142,10 @@ spec:
authentication:
universalAuth:
secretsScope:
+ # either projectSlug or projectId is required
projectSlug: # <-- project slug
+ projectId: # <-- project id
+
envSlug: # "dev", "staging", "prod", etc..
secretsPath: "" # Root is "/"
credentialsRef:
@@ -496,9 +499,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
@@ -545,9 +550,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
@@ -588,9 +595,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
@@ -631,9 +640,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
@@ -675,9 +686,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
@@ -730,9 +743,11 @@ spec:
Make sure to also populate the `secretsScope` field with the project slug
- _`projectSlug`_, environment slug _`envSlug`_, and secrets path
+ _`projectSlug`_, or project ID _`projectId`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
+
+ Please note that you can only use either `projectSlug` or `projectId` in the `secretsScope` field.
## Example
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 dbc2e5271..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 |
| -------- | ------------------------- |
diff --git a/docs/self-hosting/guides/upgrading-infisical.mdx b/docs/self-hosting/guides/upgrading-infisical.mdx
index 60c6edbff..cae8193bf 100644
--- a/docs/self-hosting/guides/upgrading-infisical.mdx
+++ b/docs/self-hosting/guides/upgrading-infisical.mdx
@@ -54,4 +54,62 @@ Now, migrations run automatically during boot-up. This improvement streamlines t
- Once the migration is complete, all instances will operate with the updated schema.
5. **Verify the Upgrade:**
- - Review the logs for any migration errors or warnings.
+ - Review the logs for any migration errors or warnings.
+ - Test basic functionality to ensure the upgrade was successful.
+
+## Troubleshooting
+
+### UI Caching Issues After Upgrade
+
+After upgrading your Infisical instance, you may encounter UI-related issues such as:
+- Strange loading behavior
+- Components not rendering correctly
+- Unexpected errors in the browser console
+- Features appearing broken or unresponsive
+
+These issues are often caused by browser caching of the previous version's static assets.
+
+**Solution:**
+1. **Try a private/incognito browser window first** - This is the quickest way to test if the issue is cache-related.
+2. **Clear your browser cache** if the private window works correctly:
+ - **Chrome/Edge:** Press `Ctrl+Shift+Delete` (Windows/Linux) or `Cmd+Shift+Delete` (Mac)
+ - **Firefox:** Press `Ctrl+Shift+Delete` (Windows/Linux) or `Cmd+Shift+Delete` (Mac)
+ - **Safari:** Press `Cmd+Option+E` or go to Develop menu > Empty Caches
+3. **Hard refresh the page** by pressing `Ctrl+F5` (Windows/Linux) or `Cmd+Shift+R` (Mac)
+
+
+ Caching issues are temporary and typically resolve themselves as the cache expires, but manually clearing the cache provides immediate resolution.
+
+
+## Downgrade Considerations
+
+While we recommend staying up-to-date with the latest version, there may be scenarios where you need to downgrade your Infisical instance.
+
+
+ **Database Compatibility:** Downgrading can be complex due to database schema changes. Always ensure you have proper backups before attempting any version changes.
+
+
+### Safe Downgrade Process
+
+1. **Prepare Database Snapshot:**
+ - Create a database snapshot/backup **before** upgrading to the target version
+ - Ensure the snapshot is from a version compatible with your desired downgrade target
+
+2. **Stop Infisical Services:**
+ - Gracefully shut down all Infisical instances to prevent data corruption
+
+3. **Restore Database:**
+ - Restore your database from the pre-upgrade snapshot
+ - **Critical:** Do not attempt to downgrade with a database that has run migrations from a newer version
+
+4. **Deploy Previous Version:**
+ - Deploy the previous Infisical version
+ - Verify that the version matches the database schema in your snapshot
+
+5. **Verify Functionality:**
+ - Test critical functionality to ensure the downgrade was successful
+ - Monitor logs for any compatibility issues
+
+
+ The safest approach for downgrades is to restore to a known good state (both application and database) rather than attempting to reverse individual migrations.
+
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/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
index a0c5de76f..6d31bf8e6 100644
--- a/frontend/src/components/projects/RequestProjectAccessModal.tsx
+++ b/frontend/src/components/projects/RequestProjectAccessModal.tsx
@@ -3,7 +3,7 @@ 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 { Workspace } from "@app/hooks/api/workspace/types";
+import { Project } from "@app/hooks/api/projects/types";
type ContentProps = {
projectId: string;
@@ -57,7 +57,7 @@ const Content = ({ projectId, onComplete }: ContentProps) => {
type RequestProjectAccessModalProps = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
- project?: Workspace;
+ project?: Project;
onComplete?: () => void;
};
diff --git a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx
index 89ced26b7..41f378b94 100644
--- a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx
+++ b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx
@@ -8,13 +8,13 @@ import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms
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 = {
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 407a473d1..c2064071f 100644
--- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx
+++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ConnectionField.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AppConnectionOption } from "@app/components/app-connections";
import { FilterableSelect, FormControl } from "@app/components/v2";
-import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
+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";
@@ -29,11 +29,11 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }
const rotationType = watch("type");
const app = SECRET_ROTATION_CONNECTION_MAP[rotationType];
- const { currentWorkspace } = useWorkspace();
+ const { currentProject } = useProject();
const { data: availableConnections, isPending } = useListAvailableAppConnections(
app,
- currentWorkspace.id
+ currentProject.id
);
const connectionName = APP_CONNECTION_MAP[app].name;
@@ -117,8 +117,8 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }
localStorage.removeItem("secretRotationFormData");
handlePopUpToggle("addConnection", isOpen);
}}
- projectType={currentWorkspace.type}
- projectId={currentWorkspace.id}
+ projectType={currentProject.type}
+ projectId={currentProject.id}
app={app}
onComplete={(connection) => {
if (connection) {
diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx
index 62efaa8d2..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,7 +32,7 @@ type Props = {
onCancel: () => void;
secretPath: string;
environment?: string;
- environments?: WorkspaceEnv[];
+ environments?: ProjectEnv[];
secretRotation?: TSecretRotationV2;
initialFormData?: Partial;
};
@@ -70,7 +70,7 @@ export const SecretRotationV2Form = ({
}: 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);
@@ -82,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
}
: {
@@ -93,7 +93,7 @@ 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
...(initialFormData as object)
@@ -118,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/forms/SecretScanningDataSourceConnectionField.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx
index a58908da5..2e9e4a2a5 100644
--- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx
+++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConnectionField.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AppConnectionOption } from "@app/components/app-connections";
import { FilterableSelect, FormControl } from "@app/components/v2";
-import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
+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";
@@ -32,11 +32,11 @@ export const SecretScanningDataSourceConnectionField = ({
const dataSourceType = watch("type");
const app = SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSourceType];
- const { currentWorkspace } = useWorkspace();
+ const { currentProject } = useProject();
const { data: availableConnections, isPending } = useListAvailableAppConnections(
app,
- currentWorkspace.id
+ currentProject.id
);
const connectionName = APP_CONNECTION_MAP[app].name;
@@ -119,8 +119,8 @@ export const SecretScanningDataSourceConnectionField = ({
localStorage.removeItem("secretScanningDataSourceFormData");
handlePopUpToggle("addConnection", isOpen);
}}
- projectType={currentWorkspace.type}
- projectId={currentWorkspace.id}
+ projectType={currentProject.type}
+ projectId={currentProject.id}
app={app}
onComplete={(connection) => {
if (connection) {
diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx
index 49568dfec..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,
@@ -46,7 +46,7 @@ export const SecretScanningDataSourceForm = ({
}: 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);
@@ -71,7 +71,7 @@ export const SecretScanningDataSourceForm = ({
: 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/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx
index 082054ad2..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,
@@ -47,7 +47,7 @@ export const CreateSecretSyncForm = ({
initialFormData
}: Props) => {
const createSecretSync = useCreateSecretSync();
- const { currentWorkspace } = useWorkspace();
+ const { currentProject } = useProject();
const { name: destinationName } = SECRET_SYNC_MAP[destination];
const [showConfirmation, setShowConfirmation] = useState(false);
@@ -78,7 +78,7 @@ export const CreateSecretSyncForm = ({
...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 290f36206..62542a570 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AppConnectionOption } from "@app/components/app-connections";
import { FilterableSelect, FormControl } from "@app/components/v2";
-import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
+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";
@@ -28,11 +28,11 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
const destination = watch("destination");
const app = SECRET_SYNC_CONNECTION_MAP[destination];
- const { currentWorkspace } = useWorkspace();
+ const { currentProject } = useProject();
const { data: availableConnections, isPending } = useListAvailableAppConnections(
app,
- currentWorkspace.id
+ currentProject.id
);
const connectionName = APP_CONNECTION_MAP[app].name;
@@ -101,8 +101,8 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
localStorage.removeItem("secretSyncFormData");
handlePopUpToggle("addConnection", isOpen);
}}
- projectType={currentWorkspace.type}
- projectId={currentWorkspace.id}
+ projectType={currentProject.type}
+ projectId={currentProject.id}
app={app}
onComplete={(connection) => {
if (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 60f069c1d..8e4f7514c 100644
--- a/frontend/src/context/OrgPermissionContext/types.ts
+++ b/frontend/src/context/OrgPermissionContext/types.ts
@@ -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]
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/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/auditLogStreams.ts b/frontend/src/helpers/auditLogStreams.ts
index eee2fc8da..faa132dd2 100644
--- a/frontend/src/helpers/auditLogStreams.ts
+++ b/frontend/src/helpers/auditLogStreams.ts
@@ -8,6 +8,7 @@ export const AUDIT_LOG_STREAM_PROVIDER_MAP: Record<
LogProvider,
{ name: string; image?: string; icon?: IconDefinition; size?: number }
> = {
+ [LogProvider.Azure]: { name: "Azure", image: "Microsoft Azure.png", size: 60 },
[LogProvider.Cribl]: { name: "Cribl", image: "Cribl.png", size: 60 },
[LogProvider.Custom]: { name: "Custom", icon: faCode },
[LogProvider.Datadog]: { name: "Datadog", image: "Datadog.png" },
@@ -25,6 +26,8 @@ export function getProviderUrl(
return logStream.credentials.url;
case LogProvider.Splunk:
return `https://${logStream.credentials.hostname}:8088/services/collector/event`;
+ case LogProvider.Azure:
+ return `${logStream.credentials.dceUrl}/dataCollectionRules/${logStream.credentials.dcrId}/streams/Custom-${logStream.credentials.cltName}_CL`;
default:
throw new Error(
`Unhandled provider in getProviderUrl: ${(logStream as TAuditLogStream).provider}`
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/queries.tsx b/frontend/src/hooks/api/appConnections/queries.tsx
index 48c084a4e..a27ffdc10 100644
--- a/frontend/src/hooks/api/appConnections/queries.tsx
+++ b/frontend/src/hooks/api/appConnections/queries.tsx
@@ -14,7 +14,7 @@ import {
TAppConnectionOption,
TAppConnectionOptionMap
} from "@app/hooks/api/appConnections/types/app-options";
-import { ProjectType } from "@app/hooks/api/workspace/types";
+import { ProjectType } from "@app/hooks/api/projects/types";
export const appConnectionKeys = {
all: ["app-connection"] as const,
diff --git a/frontend/src/hooks/api/appConnections/types/root-connection.ts b/frontend/src/hooks/api/appConnections/types/root-connection.ts
index 3a90ca12b..96198265e 100644
--- a/frontend/src/hooks/api/appConnections/types/root-connection.ts
+++ b/frontend/src/hooks/api/appConnections/types/root-connection.ts
@@ -1,4 +1,4 @@
-import { ProjectType } from "@app/hooks/api/workspace/types";
+import { ProjectType } from "@app/hooks/api/projects/types";
export type TRootAppConnection = {
id: string;
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/auditLogStreams/enums.ts b/frontend/src/hooks/api/auditLogStreams/enums.ts
index 78233f774..ebef18574 100644
--- a/frontend/src/hooks/api/auditLogStreams/enums.ts
+++ b/frontend/src/hooks/api/auditLogStreams/enums.ts
@@ -1,4 +1,5 @@
export enum LogProvider {
+ Azure = "azure",
Cribl = "cribl",
Custom = "custom",
Datadog = "datadog",
diff --git a/frontend/src/hooks/api/auditLogStreams/types/index.ts b/frontend/src/hooks/api/auditLogStreams/types/index.ts
index a360cd677..f780510c2 100644
--- a/frontend/src/hooks/api/auditLogStreams/types/index.ts
+++ b/frontend/src/hooks/api/auditLogStreams/types/index.ts
@@ -1,4 +1,5 @@
import { LogProvider } from "../enums";
+import { TAzureProviderLogStream } from "./providers/azure-provider";
import { TCriblProviderLogStream } from "./providers/cribl-provider";
import { TCustomProviderLogStream } from "./providers/custom-provider";
import { TDatadogProviderLogStream } from "./providers/datadog-provider";
@@ -8,9 +9,11 @@ export type TAuditLogStream =
| TCustomProviderLogStream
| TDatadogProviderLogStream
| TSplunkProviderLogStream
+ | TAzureProviderLogStream
| TCriblProviderLogStream;
export type TAuditLogStreamProviderMap = {
+ [LogProvider.Azure]: TAzureProviderLogStream;
[LogProvider.Cribl]: TCriblProviderLogStream;
[LogProvider.Custom]: TCustomProviderLogStream;
[LogProvider.Datadog]: TDatadogProviderLogStream;
diff --git a/frontend/src/hooks/api/auditLogStreams/types/providers/azure-provider.ts b/frontend/src/hooks/api/auditLogStreams/types/providers/azure-provider.ts
new file mode 100644
index 000000000..3086fd7cc
--- /dev/null
+++ b/frontend/src/hooks/api/auditLogStreams/types/providers/azure-provider.ts
@@ -0,0 +1,14 @@
+import { LogProvider } from "../../enums";
+import { TRootProviderLogStream } from "./root-provider";
+
+export type TAzureProviderLogStream = TRootProviderLogStream & {
+ provider: LogProvider.Azure;
+ credentials: {
+ tenantId: string;
+ clientId: string;
+ clientSecret: string;
+ dceUrl: string;
+ dcrId: string;
+ cltName: string;
+ };
+};
diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx
index 1166da805..195f90083 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",
diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx
index 3ea573bdb..7ad592d8c 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",
@@ -58,8 +58,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",
@@ -71,8 +71,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",
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 1ca819e60..f09924d26 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 36f9eae4e..af8e401d7 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