+export const Tag = ({ children, className, colorSchema = "gray", size = "sm", onClose }: Props) => (
+
{children}
+ {onClose && (
+
+ )}
);
diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx
index eb49f0d88..89030931d 100644
--- a/frontend/src/components/v2/index.tsx
+++ b/frontend/src/components/v2/index.tsx
@@ -2,6 +2,7 @@ export * from "./Accordion";
export * from "./Button";
export * from "./Card";
export * from "./Checkbox";
+export * from "./ContentLoader";
export * from "./DatePicker";
export * from "./DeleteActionModal";
export * from "./Drawer";
diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts
index 6a1efc135..13352c586 100644
--- a/frontend/src/helpers/project.ts
+++ b/frontend/src/helpers/project.ts
@@ -3,77 +3,75 @@ import crypto from "crypto";
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
import { uploadWsKey } from "@app/hooks/api/keys/queries";
-import { createSecret } from "@app/hooks/api/secrets/queries";
+import { createSecret } from "@app/hooks/api/secrets/mutations";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
import { createWorkspace } from "@app/hooks/api/workspace/queries";
const secretsToBeAdded = [
- {
- pos: 0,
- key: "DATABASE_URL",
- // eslint-disable-next-line no-template-curly-in-string
- value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
- valueOverride: undefined,
- comment: "Secret referencing example",
- id: "",
- tags: []
- },
- {
- pos: 1,
- key: "DB_USERNAME",
- value: "OVERRIDE_THIS",
- valueOverride: undefined,
- comment:
- "Override secrets with personal value",
- id: "",
- tags: []
- },
- {
- pos: 2,
- key: "DB_PASSWORD",
- value: "OVERRIDE_THIS",
- valueOverride: undefined,
- comment:
- "Another secret override",
- id: "",
- tags: []
- },
- {
- pos: 3,
- key: "DB_USERNAME",
- value: "user1234",
- valueOverride: "user1234",
- comment: "",
- id: "",
- tags: []
- },
- {
- pos: 4,
- key: "DB_PASSWORD",
- value: "example_password",
- valueOverride: "example_password",
- comment: "",
- id: "",
- tags: []
- },
- {
- pos: 5,
- key: "TWILIO_AUTH_TOKEN",
- value: "example_twillio_token",
- valueOverride: undefined,
- comment: "",
- id: "",
- tags: []
- },
- {
- pos: 6,
- key: "WEBSITE_URL",
- value: "http://localhost:3000",
- valueOverride: undefined,
- comment: "",
- id: "",
- tags: []
- }
+ {
+ pos: 0,
+ key: "DATABASE_URL",
+ // eslint-disable-next-line no-template-curly-in-string
+ value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
+ valueOverride: undefined,
+ comment: "Secret referencing example",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 1,
+ key: "DB_USERNAME",
+ value: "OVERRIDE_THIS",
+ valueOverride: undefined,
+ comment: "Override secrets with personal value",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 2,
+ key: "DB_PASSWORD",
+ value: "OVERRIDE_THIS",
+ valueOverride: undefined,
+ comment: "Another secret override",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 3,
+ key: "DB_USERNAME",
+ value: "user1234",
+ valueOverride: "user1234",
+ comment: "",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 4,
+ key: "DB_PASSWORD",
+ value: "example_password",
+ valueOverride: "example_password",
+ comment: "",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 5,
+ key: "TWILIO_AUTH_TOKEN",
+ value: "example_twillio_token",
+ valueOverride: undefined,
+ comment: "",
+ id: "",
+ tags: []
+ },
+ {
+ pos: 6,
+ key: "WEBSITE_URL",
+ value: "http://localhost:3000",
+ valueOverride: undefined,
+ comment: "",
+ id: "",
+ tags: []
+ }
];
/**
@@ -85,30 +83,32 @@ const secretsToBeAdded = [
* @returns {Project} project - new project
*/
const initProjectHelper = async ({
- organizationId,
- projectName
+ organizationId,
+ projectName
}: {
- organizationId: string;
- projectName: string;
+ organizationId: string;
+ projectName: string;
}) => {
// create new project
- const { data: { workspace } } = await createWorkspace({
- workspaceName: projectName,
- organizationId
+ const {
+ data: { workspace }
+ } = await createWorkspace({
+ workspaceName: projectName,
+ organizationId
});
-
+
// create and upload new (encrypted) project key
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
-
+
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
const user = await fetchUserDetails();
const { ciphertext, nonce } = encryptAssymmetric({
- plaintext: randomBytes,
- publicKey: user.publicKey,
- privateKey: PRIVATE_KEY
+ plaintext: randomBytes,
+ publicKey: user.publicKey,
+ privateKey: PRIVATE_KEY
});
await uploadWsKey({
@@ -120,11 +120,11 @@ const initProjectHelper = async ({
// encrypt and upload secrets to new project
const secrets = await encryptSecrets({
- secretsToEncrypt: secretsToBeAdded,
- workspaceId: workspace._id,
- env: "dev"
+ secretsToEncrypt: secretsToBeAdded,
+ workspaceId: workspace._id,
+ env: "dev"
});
-
+
secrets?.forEach((secret) => {
createSecret({
workspaceId: workspace._id,
@@ -146,10 +146,8 @@ const initProjectHelper = async ({
}
});
});
-
- return workspace;
-}
-export {
- initProjectHelper
-}
\ No newline at end of file
+ return workspace;
+};
+
+export { initProjectHelper };
diff --git a/frontend/src/hooks/api/secretFolders/index.tsx b/frontend/src/hooks/api/secretFolders/index.tsx
index 3c0692eb6..0676f5d14 100644
--- a/frontend/src/hooks/api/secretFolders/index.tsx
+++ b/frontend/src/hooks/api/secretFolders/index.tsx
@@ -3,6 +3,5 @@ export {
useDeleteFolder,
useGetFoldersByEnv,
useGetProjectFolders,
- useGetProjectFoldersBatch,
useUpdateFolder
} from "./queries";
diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx
index 36d013b72..e562e0105 100644
--- a/frontend/src/hooks/api/secretFolders/queries.tsx
+++ b/frontend/src/hooks/api/secretFolders/queries.tsx
@@ -1,86 +1,80 @@
import { useCallback, useMemo } from "react";
-import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
+import {
+ useMutation,
+ useQueries,
+ useQuery,
+ useQueryClient,
+ UseQueryOptions
+} from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { secretSnapshotKeys } from "../secretSnapshots/queries";
import {
- CreateFolderDTO,
- DeleteFolderDTO,
- GetProjectFoldersBatchDTO,
- GetProjectFoldersDTO,
+ TCreateFolderDTO,
+ TDeleteFolderDTO,
TGetFoldersByEnvDTO,
+ TGetProjectFoldersDTO,
TSecretFolder,
- UpdateFolderDTO
+ TUpdateFolderDTO
} from "./types";
const queryKeys = {
- getSecretFolders: (workspaceId: string, environment: string, parentFolderId?: string) =>
- ["secret-folders", { workspaceId, environment, parentFolderId }] as const
+ getSecretFolders: ({ workspaceId, environment, directory }: TGetProjectFoldersDTO) =>
+ ["secret-folders", { workspaceId, environment, directory }] as const
};
-const fetchProjectFolders = async (
- workspaceId: string,
- environment: string,
- parentFolderId?: string,
- parentFolderPath?: string
-) => {
- const { data } = await apiRequest.get<{ folders: TSecretFolder[]; dir: TSecretFolder[] }>(
- "/api/v1/folders",
- {
- params: {
- workspaceId,
- environment,
- parentFolderId,
- parentFolderPath
- }
+const fetchProjectFolders = async (workspaceId: string, environment: string, directory = "/") => {
+ const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
+ params: {
+ workspaceId,
+ environment,
+ directory
}
- );
- return data;
+ });
+ return data.folders;
};
export const useGetProjectFolders = ({
workspaceId,
- parentFolderId,
environment,
- isPaused,
- sortDir
-}: GetProjectFoldersDTO) =>
+ directory = "/",
+ options = {}
+}: TGetProjectFoldersDTO & {
+ options?: Omit<
+ UseQueryOptions<
+ TSecretFolder[],
+ unknown,
+ TSecretFolder[],
+ ReturnType
+ >,
+ "queryKey" | "queryFn"
+ >;
+}) =>
useQuery({
- queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderId),
- enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused,
- queryFn: async () => fetchProjectFolders(workspaceId, environment, parentFolderId),
- select: useCallback(
- ({ folders, dir }: { folders: TSecretFolder[]; dir: TSecretFolder[] }) => ({
- dir,
- folders: folders.sort((a, b) =>
- sortDir === "asc"
- ? a?.name?.localeCompare(b?.name || "")
- : b?.name?.localeCompare(a?.name || "")
- )
- }),
- [sortDir]
- )
+ ...options,
+ queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }),
+ enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true),
+ queryFn: async () => fetchProjectFolders(workspaceId, environment, directory)
});
export const useGetFoldersByEnv = ({
- parentFolderPath,
+ directory = "/",
workspaceId,
- environments,
- parentFolderId
+ environments
}: TGetFoldersByEnvDTO) => {
const folders = useQueries({
- queries: environments.map((env) => ({
- queryKey: queryKeys.getSecretFolders(workspaceId, env, parentFolderPath || parentFolderId),
- queryFn: async () => fetchProjectFolders(workspaceId, env, parentFolderId, parentFolderPath),
- enabled: Boolean(workspaceId) && Boolean(env)
+ queries: environments.map((environment) => ({
+ queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }),
+ queryFn: async () => fetchProjectFolders(workspaceId, environment, directory),
+ enabled: Boolean(workspaceId) && Boolean(environment)
}))
});
const folderNames = useMemo(() => {
const names = new Set();
folders?.forEach(({ data }) => {
- data?.folders.forEach(({ name }) => {
+ data?.forEach(({ name }) => {
names.add(name);
});
});
@@ -92,9 +86,7 @@ export const useGetFoldersByEnv = ({
const selectedEnvIndex = environments.indexOf(env);
if (selectedEnvIndex !== -1) {
return Boolean(
- folders?.[selectedEnvIndex]?.data?.folders?.find(
- ({ name: folderName }) => folderName === name
- )
+ folders?.[selectedEnvIndex]?.data?.find(({ name: folderName }) => folderName === name)
);
}
return false;
@@ -105,95 +97,78 @@ export const useGetFoldersByEnv = ({
return { folders, folderNames, isFolderPresentInEnv };
};
-export const useGetProjectFoldersBatch = ({
- folders = [],
- isPaused,
- parentFolderPath
-}: GetProjectFoldersBatchDTO) =>
- useQueries({
- queries: folders.map(({ workspaceId, environment, parentFolderId }) => ({
- queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderPath),
- queryFn: async () =>
- fetchProjectFolders(workspaceId, environment, parentFolderId, parentFolderPath),
- enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused,
- select: (data: { folders: TSecretFolder[]; dir: TSecretFolder[] }) => ({
- environment,
- folders: data.folders,
- dir: data.dir
- })
- }))
- });
-
export const useCreateFolder = () => {
const queryClient = useQueryClient();
- return useMutation<{}, {}, CreateFolderDTO>({
+ return useMutation<{}, {}, TCreateFolderDTO>({
mutationFn: async (dto) => {
const { data } = await apiRequest.post("/api/v1/folders", dto);
return data;
},
- onSuccess: (_, { workspaceId, environment, parentFolderId }) => {
+ onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries(
- queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ queryKeys.getSecretFolders({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.list({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.count({ workspaceId, environment, directory })
);
}
});
};
-export const useUpdateFolder = (parentFolderId: string) => {
+export const useUpdateFolder = () => {
const queryClient = useQueryClient();
- return useMutation<{}, {}, UpdateFolderDTO>({
- mutationFn: async ({ folderId, name, environment, workspaceId }) => {
- const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
+ return useMutation<{}, {}, TUpdateFolderDTO>({
+ mutationFn: async ({ directory = "/", folderName, name, environment, workspaceId }) => {
+ const { data } = await apiRequest.patch(`/api/v1/folders/${folderName}`, {
name,
environment,
- workspaceId
+ workspaceId,
+ directory
});
return data;
},
- onSuccess: (_, { workspaceId, environment }) => {
+ onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries(
- queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ queryKeys.getSecretFolders({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.list({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.count({ workspaceId, environment, directory })
);
}
});
};
-export const useDeleteFolder = (parentFolderId: string) => {
+export const useDeleteFolder = () => {
const queryClient = useQueryClient();
- return useMutation<{}, {}, DeleteFolderDTO>({
- mutationFn: async ({ folderId, environment, workspaceId }) => {
- const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, {
+ return useMutation<{}, {}, TDeleteFolderDTO>({
+ mutationFn: async ({ directory = "/", folderName, environment, workspaceId }) => {
+ const { data } = await apiRequest.delete(`/api/v1/folders/${folderName}`, {
data: {
environment,
- workspaceId
+ workspaceId,
+ directory
}
});
return data;
},
- onSuccess: (_, { workspaceId, environment }) => {
+ onSuccess: (_, { directory = "/", workspaceId, environment }) => {
queryClient.invalidateQueries(
- queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ queryKeys.getSecretFolders({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.list({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ secretSnapshotKeys.count({ workspaceId, environment, directory })
);
}
});
diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts
index c9d707620..c051173e9 100644
--- a/frontend/src/hooks/api/secretFolders/types.ts
+++ b/frontend/src/hooks/api/secretFolders/types.ts
@@ -3,43 +3,36 @@ export type TSecretFolder = {
name: string;
};
-export type GetProjectFoldersDTO = {
+export type TGetProjectFoldersDTO = {
workspaceId: string;
environment: string;
- parentFolderId?: string;
- isPaused?: boolean;
- sortDir?: "asc" | "desc";
-};
-
-export type GetProjectFoldersBatchDTO = {
- folders: Omit[];
- isPaused?: boolean;
- parentFolderPath?: string;
+ directory?: string;
};
export type TGetFoldersByEnvDTO = {
environments: string[];
workspaceId: string;
- parentFolderPath?: string;
- parentFolderId?: string;
+ directory?: string;
};
-export type CreateFolderDTO = {
+export type TCreateFolderDTO = {
workspaceId: string;
environment: string;
folderName: string;
- parentFolderId?: string;
+ directory?: string;
};
-export type UpdateFolderDTO = {
+export type TUpdateFolderDTO = {
workspaceId: string;
environment: string;
name: string;
- folderId: string;
+ folderName: string;
+ directory?: string;
};
-export type DeleteFolderDTO = {
+export type TDeleteFolderDTO = {
workspaceId: string;
environment: string;
- folderId: string;
+ folderName: string;
+ directory?: string;
};
diff --git a/frontend/src/hooks/api/secretImports/mutation.tsx b/frontend/src/hooks/api/secretImports/mutation.tsx
index bb01528ea..184c8dd63 100644
--- a/frontend/src/hooks/api/secretImports/mutation.tsx
+++ b/frontend/src/hooks/api/secretImports/mutation.tsx
@@ -9,21 +9,21 @@ export const useCreateSecretImport = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TCreateSecretImportDTO>({
- mutationFn: async ({ secretImport, environment, workspaceId, folderId }) => {
+ mutationFn: async ({ secretImport, environment, workspaceId, directory }) => {
const { data } = await apiRequest.post("/api/v1/secret-imports", {
secretImport,
environment,
workspaceId,
- folderId
+ directory
});
return data;
},
- onSuccess: (_, { workspaceId, environment, folderId }) => {
+ onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries(
- secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId)
+ secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId)
+ secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
);
}
});
@@ -33,21 +33,21 @@ export const useUpdateSecretImport = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TUpdateSecretImportDTO>({
- mutationFn: async ({ environment, workspaceId, folderId, secretImports, id }) => {
+ mutationFn: async ({ environment, workspaceId, directory, secretImports, id }) => {
const { data } = await apiRequest.put(`/api/v1/secret-imports/${id}`, {
secretImports,
environment,
workspaceId,
- folderId
+ directory
});
return data;
},
- onSuccess: (_, { workspaceId, environment, folderId }) => {
+ onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries(
- secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId)
+ secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId)
+ secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
);
}
});
@@ -66,12 +66,12 @@ export const useDeleteSecretImport = () => {
});
return data;
},
- onSuccess: (_, { workspaceId, environment, folderId }) => {
+ onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries(
- secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId)
+ secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
);
queryClient.invalidateQueries(
- secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId)
+ secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
);
}
});
diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx
index 7bb07488c..75c1820cc 100644
--- a/frontend/src/hooks/api/secretImports/queries.tsx
+++ b/frontend/src/hooks/api/secretImports/queries.tsx
@@ -1,5 +1,5 @@
import { useCallback } from "react";
-import { useQuery } from "@tanstack/react-query";
+import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import {
decryptAssymmetric,
@@ -7,52 +7,68 @@ import {
} from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request";
-import { TGetImportedSecrets, TImportedSecrets, TSecretImports } from "./types";
+import { TGetImportedSecrets, TGetSecretImports, TImportedSecrets, TSecretImports } from "./types";
export const secretImportKeys = {
- getProjectSecretImports: (workspaceId: string, env: string | string[], folderId?: string) => [
- { workspaceId, env, folderId },
- "secrets-imports"
- ],
- getSecretImportSecrets: (workspaceId: string, env: string | string[], folderId?: string) => [
- { workspaceId, env, folderId },
- "secrets-import-sec"
- ]
+ getProjectSecretImports: ({ environment, workspaceId, directory }: TGetSecretImports) =>
+ [{ workspaceId, directory, environment }, "secrets-imports"] as const,
+ getSecretImportSecrets: ({
+ workspaceId,
+ environment,
+ directory
+ }: Omit) =>
+ [{ workspaceId, environment, directory }, "secrets-import-sec"] as const
};
-const fetchSecretImport = async (workspaceId: string, environment: string, folderId?: string) => {
+const fetchSecretImport = async ({ workspaceId, environment, directory }: TGetSecretImports) => {
const { data } = await apiRequest.get<{ secretImport: TSecretImports }>(
"/api/v1/secret-imports",
{
params: {
workspaceId,
environment,
- folderId
+ directory
}
}
);
return data.secretImport;
};
-export const useGetSecretImports = (workspaceId: string, env: string, folderId?: string) =>
+export const useGetSecretImports = ({
+ workspaceId,
+ environment,
+ directory = "/",
+ options = {}
+}: TGetSecretImports & {
+ options?: Omit<
+ UseQueryOptions<
+ TSecretImports,
+ unknown,
+ TSecretImports,
+ ReturnType
+ >,
+ "queryKey" | "queryFn"
+ >;
+}) =>
useQuery({
- enabled: Boolean(workspaceId) && Boolean(env),
- queryKey: secretImportKeys.getProjectSecretImports(workspaceId, env, folderId),
- queryFn: () => fetchSecretImport(workspaceId, env, folderId)
+ ...options,
+ queryKey: secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }),
+ enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true),
+ queryFn: () => fetchSecretImport({ workspaceId, environment, directory })
});
const fetchImportedSecrets = async (
workspaceId: string,
environment: string,
- folderId?: string
+ directory?: string
) => {
- const { data } = await apiRequest.get<{ secrets: TImportedSecrets }>(
+ const { data } = await apiRequest.get<{ secrets: TImportedSecrets[] }>(
"/api/v1/secret-imports/secrets",
{
params: {
workspaceId,
environment,
- folderId
+ directory
}
}
);
@@ -62,15 +78,34 @@ const fetchImportedSecrets = async (
export const useGetImportedSecrets = ({
workspaceId,
environment,
- folderId,
- decryptFileKey
-}: TGetImportedSecrets) =>
+ decryptFileKey,
+ directory,
+ options = {}
+}: TGetImportedSecrets & {
+ options?: Omit<
+ UseQueryOptions<
+ TImportedSecrets[],
+ unknown,
+ TImportedSecrets[],
+ ReturnType
+ >,
+ "queryKey" | "queryFn"
+ >;
+}) =>
useQuery({
- enabled: Boolean(workspaceId) && Boolean(environment) && Boolean(decryptFileKey),
- queryKey: secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId),
- queryFn: () => fetchImportedSecrets(workspaceId, environment, folderId),
+ enabled:
+ Boolean(workspaceId) &&
+ Boolean(environment) &&
+ Boolean(decryptFileKey) &&
+ (options?.enabled ?? true),
+ queryKey: secretImportKeys.getSecretImportSecrets({
+ workspaceId,
+ environment,
+ directory
+ }),
+ queryFn: () => fetchImportedSecrets(workspaceId, environment, directory),
select: useCallback(
- (data: TImportedSecrets) => {
+ (data: TImportedSecrets[]) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
const latestKey = decryptFileKey;
const key = decryptAssymmetric({
@@ -114,7 +149,8 @@ export const useGetImportedSecrets = ({
tags: encSecret.tags,
comment: secretComment,
createdAt: encSecret.createdAt,
- updatedAt: encSecret.updatedAt
+ updatedAt: encSecret.updatedAt,
+ version: encSecret.version
};
})
}));
diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts
index 50884225f..019401092 100644
--- a/frontend/src/hooks/api/secretImports/types.ts
+++ b/frontend/src/hooks/api/secretImports/types.ts
@@ -1,5 +1,5 @@
+import { UserWsKeyPair } from "../keys/types";
import { EncryptedSecret } from "../secrets/types";
-import { UserWsKeyPair } from "../types";
export type TSecretImports = {
_id: string;
@@ -16,19 +16,25 @@ export type TImportedSecrets = {
secretPath: string;
folderId: string;
secrets: EncryptedSecret[];
-}[];
+};
+
+export type TGetSecretImports = {
+ workspaceId: string;
+ environment: string;
+ directory?: string;
+};
export type TGetImportedSecrets = {
workspaceId: string;
environment: string;
- folderId?: string;
+ directory?: string;
decryptFileKey: UserWsKeyPair;
};
export type TCreateSecretImportDTO = {
workspaceId: string;
environment: string;
- folderId?: string;
+ directory?: string;
secretImport: {
environment: string;
secretPath: string;
@@ -39,7 +45,7 @@ export type TUpdateSecretImportDTO = {
id: string;
workspaceId: string;
environment: string;
- folderId?: string;
+ directory?: string;
secretImports: Array<{
environment: string;
secretPath: string;
@@ -50,7 +56,7 @@ export type TDeleteSecretImportDTO = {
id: string;
workspaceId: string;
environment: string;
- folderId?: string;
+ directory?: string;
secretImportPath: string;
secretImportEnv: string;
};
diff --git a/frontend/src/hooks/api/secretSnapshots/index.tsx b/frontend/src/hooks/api/secretSnapshots/index.tsx
index 0e308b6f8..e6d4ec9b0 100644
--- a/frontend/src/hooks/api/secretSnapshots/index.tsx
+++ b/frontend/src/hooks/api/secretSnapshots/index.tsx
@@ -1,6 +1,6 @@
export {
useGetSnapshotSecrets,
- useGetWorkspaceSecretSnapshots,
+ useGetWorkspaceSnapshotList,
useGetWsSnapshotCount,
usePerformSecretRollback
} from "./queries";
diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx
index cf1103910..13db97aea 100644
--- a/frontend/src/hooks/api/secretSnapshots/queries.tsx
+++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx
@@ -9,39 +9,39 @@ import { apiRequest } from "@app/config/request";
import { DecryptedSecret } from "../secrets/types";
import {
- GetWorkspaceSecretSnapshotsDTO,
+ TGetSecretSnapshotsDTO,
TSecretRollbackDTO,
- TSnapshotSecret,
- TSnapshotSecretProps,
- TWorkspaceSecretSnapshot
+ TSecretSnapshot,
+ TSnapshotData,
+ TSnapshotDataProps
} from "./types";
export const secretSnapshotKeys = {
- list: (workspaceId: string, env: string, folderId?: string) =>
- [{ workspaceId, env, folderId }, "secret-snapshot"] as const,
- snapshotSecrets: (snapshotId: string) => [{ snapshotId }, "secret-snapshot"] as const,
- count: (workspaceId: string, env: string, folderId?: string) => [
- { workspaceId, env, folderId },
+ list: ({ workspaceId, environment, directory }: Omit) =>
+ [{ workspaceId, environment, directory }, "secret-snapshot"] as const,
+ snapshotData: (snapshotId: string) => [{ snapshotId }, "secret-snapshot"] as const,
+ count: ({ environment, workspaceId, directory }: Omit) => [
+ { workspaceId, environment, directory },
"count",
"secret-snapshot"
]
};
-const fetchWorkspaceSecretSnaphots = async (
- workspaceId: string,
- environment: string,
- folderId?: string,
+const fetchWorkspaceSnaphots = async ({
+ workspaceId,
+ environment,
+ directory = "/",
limit = 10,
offset = 0
-) => {
- const res = await apiRequest.get<{ secretSnapshots: TWorkspaceSecretSnapshot[] }>(
+}: TGetSecretSnapshotsDTO & { offset: number }) => {
+ const res = await apiRequest.get<{ secretSnapshots: TSecretSnapshot[] }>(
`/api/v1/workspace/${workspaceId}/secret-snapshots`,
{
params: {
limit,
offset,
environment,
- folderId
+ directory
}
}
);
@@ -49,32 +49,25 @@ const fetchWorkspaceSecretSnaphots = async (
return res.data.secretSnapshots;
};
-export const useGetWorkspaceSecretSnapshots = (dto: GetWorkspaceSecretSnapshotsDTO) =>
+export const useGetWorkspaceSnapshotList = (dto: TGetSecretSnapshotsDTO & { isPaused?: boolean }) =>
useInfiniteQuery({
- enabled: Boolean(dto.workspaceId && dto.environment),
- queryKey: secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folder),
- queryFn: ({ pageParam }) =>
- fetchWorkspaceSecretSnaphots(
- dto.workspaceId,
- dto.environment,
- dto?.folder,
- dto.limit,
- pageParam
- ),
+ enabled: Boolean(dto.workspaceId && dto.environment) && !dto.isPaused,
+ queryKey: secretSnapshotKeys.list({ ...dto }),
+ queryFn: ({ pageParam }) => fetchWorkspaceSnaphots({ ...dto, offset: pageParam }),
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * dto.limit : undefined
});
const fetchSnapshotEncSecrets = async (snapshotId: string) => {
- const res = await apiRequest.get<{ secretSnapshot: TSnapshotSecret }>(
+ const res = await apiRequest.get<{ secretSnapshot: TSnapshotData }>(
`/api/v1/secret-snapshot/${snapshotId}`
);
return res.data.secretSnapshot;
};
-export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnapshotSecretProps) =>
+export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnapshotDataProps) =>
useQuery({
- queryKey: secretSnapshotKeys.snapshotSecrets(snapshotId),
+ queryKey: secretSnapshotKeys.snapshotData(snapshotId),
enabled: Boolean(snapshotId && decryptFileKey),
queryFn: () => fetchSnapshotEncSecrets(snapshotId),
select: (data) => {
@@ -117,7 +110,8 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap
comment: secretComment,
createdAt: encSecret.createdAt,
updatedAt: encSecret.updatedAt,
- type: "modified"
+ type: "modified",
+ version: encSecret.version
};
if (encSecret.type === "personal") {
@@ -147,25 +141,30 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap
const fetchWorkspaceSecretSnaphotCount = async (
workspaceId: string,
environment: string,
- folderId?: string
+ directory = "/"
) => {
const res = await apiRequest.get<{ count: number }>(
`/api/v1/workspace/${workspaceId}/secret-snapshots/count`,
{
params: {
environment,
- folderId
+ directory
}
}
);
return res.data.count;
};
-export const useGetWsSnapshotCount = (workspaceId: string, env: string, folderId?: string) =>
+export const useGetWsSnapshotCount = ({
+ workspaceId,
+ environment,
+ directory,
+ isPaused
+}: Omit & { isPaused?: boolean }) =>
useQuery({
- enabled: Boolean(workspaceId && env),
- queryKey: secretSnapshotKeys.count(workspaceId, env, folderId),
- queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, env, folderId)
+ enabled: Boolean(workspaceId && environment) && !isPaused,
+ queryKey: secretSnapshotKeys.count({ workspaceId, environment, directory }),
+ queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, environment, directory)
});
export const usePerformSecretRollback = () => {
@@ -179,10 +178,17 @@ export const usePerformSecretRollback = () => {
);
return data;
},
- onSuccess: (_, { workspaceId, environment, folderId }) => {
- queryClient.invalidateQueries([{ workspaceId, environment }, "secrets"]);
- queryClient.invalidateQueries(secretSnapshotKeys.list(workspaceId, environment, folderId));
- queryClient.invalidateQueries(secretSnapshotKeys.count(workspaceId, environment, folderId));
+ onSuccess: (_, { workspaceId, environment, directory }) => {
+ queryClient.invalidateQueries([
+ { workspaceId, environment, secretPath: directory },
+ "secrets"
+ ]);
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ workspaceId, environment, directory })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ workspaceId, environment, directory })
+ );
}
});
};
diff --git a/frontend/src/hooks/api/secretSnapshots/types.ts b/frontend/src/hooks/api/secretSnapshots/types.ts
index 98f7b5b24..8fa5f9451 100644
--- a/frontend/src/hooks/api/secretSnapshots/types.ts
+++ b/frontend/src/hooks/api/secretSnapshots/types.ts
@@ -1,7 +1,7 @@
import { UserWsKeyPair } from "../keys/types";
import { EncryptedSecretVersion } from "../secrets/types";
-export type TWorkspaceSecretSnapshot = {
+export type TSecretSnapshot = {
_id: string;
workspace: string;
version: number;
@@ -11,27 +11,27 @@ export type TWorkspaceSecretSnapshot = {
__v: number;
};
-export type TSnapshotSecret = Omit & {
+export type TSnapshotData = Omit & {
secretVersions: EncryptedSecretVersion[];
folderVersion: Array<{ name: string; id: string }>;
};
-export type TSnapshotSecretProps = {
+export type TSnapshotDataProps = {
snapshotId: string;
env: string;
decryptFileKey: UserWsKeyPair;
};
-export type GetWorkspaceSecretSnapshotsDTO = {
+export type TGetSecretSnapshotsDTO = {
workspaceId: string;
limit: number;
environment: string;
- folder?: string;
+ directory?: string;
};
export type TSecretRollbackDTO = {
workspaceId: string;
version: number;
environment: string;
- folderId?: string;
+ directory?: string;
};
diff --git a/frontend/src/hooks/api/secrets/index.ts b/frontend/src/hooks/api/secrets/index.ts
index 2e31e4f0b..b24b5ed19 100644
--- a/frontend/src/hooks/api/secrets/index.ts
+++ b/frontend/src/hooks/api/secrets/index.ts
@@ -1,7 +1,9 @@
-export { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "./mutations";
export {
- useBatchSecretsOp,
- useGetProjectSecrets,
- useGetProjectSecretsAllEnv,
- useGetSecretVersion
-} from "./queries";
+ useCreateSecretBatch,
+ useCreateSecretV3,
+ useDeleteSecretBatch,
+ useDeleteSecretV3,
+ useUpdateSecretBatch,
+ useUpdateSecretV3
+} from "./mutations";
+export { useGetProjectSecrets, useGetProjectSecretsAllEnv, useGetSecretVersion } from "./queries";
diff --git a/frontend/src/hooks/api/secrets/mutations.tsx b/frontend/src/hooks/api/secrets/mutations.tsx
index 6df00174c..c1bf59c85 100644
--- a/frontend/src/hooks/api/secrets/mutations.tsx
+++ b/frontend/src/hooks/api/secrets/mutations.tsx
@@ -1,6 +1,6 @@
import crypto from "crypto";
-import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { MutationOptions, useMutation, useQueryClient } from "@tanstack/react-query";
import {
decryptAssymmetric,
@@ -8,8 +8,17 @@ import {
} from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request";
+import { secretSnapshotKeys } from "../secretSnapshots/queries";
import { secretKeys } from "./queries";
-import { TCreateSecretsV3DTO, TDeleteSecretsV3DTO, TUpdateSecretsV3DTO } from "./types";
+import {
+ CreateSecretDTO,
+ TCreateSecretBatchDTO,
+ TCreateSecretsV3DTO,
+ TDeleteSecretBatchDTO,
+ TDeleteSecretsV3DTO,
+ TUpdateSecretBatchDTO,
+ TUpdateSecretsV3DTO
+} from "./types";
const encryptSecret = (randomBytes: string, key: string, value?: string, comment?: string) => {
// encrypt key
@@ -55,7 +64,11 @@ const encryptSecret = (randomBytes: string, key: string, value?: string, comment
};
};
-export const useCreateSecretV3 = () => {
+export const useCreateSecretV3 = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TCreateSecretsV3DTO>({
mutationFn: async ({
@@ -66,7 +79,8 @@ export const useCreateSecretV3 = () => {
secretName,
secretValue,
latestFileKey,
- secretComment
+ secretComment,
+ skipMultilineEncoding
}) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
@@ -84,20 +98,32 @@ export const useCreateSecretV3 = () => {
environment,
type,
secretPath,
- ...encryptSecret(randomBytes, secretName, secretValue, secretComment)
+ ...encryptSecret(randomBytes, secretName, secretValue, secretComment),
+ skipMultilineEncoding
};
const { data } = await apiRequest.post(`/api/v3/secrets/${secretName}`, reqBody);
return data;
},
onSuccess: (_, { workspaceId, environment, secretPath }) => {
queryClient.invalidateQueries(
- secretKeys.getProjectSecret(workspaceId, environment, secretPath)
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
);
- }
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
});
};
-export const useUpdateSecretV3 = () => {
+export const useUpdateSecretV3 = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TUpdateSecretsV3DTO>({
mutationFn: async ({
@@ -107,7 +133,11 @@ export const useUpdateSecretV3 = () => {
workspaceId,
secretName,
secretValue,
- latestFileKey
+ latestFileKey,
+ tags,
+ secretComment,
+ newSecretName,
+ skipMultilineEncoding
}) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
@@ -119,34 +149,40 @@ export const useUpdateSecretV3 = () => {
privateKey: PRIVATE_KEY
})
: crypto.randomBytes(16).toString("hex");
- const { secretValueIV, secretValueTag, secretValueCiphertext } = encryptSecret(
- randomBytes,
- secretName,
- secretValue,
- ""
- );
const reqBody = {
workspaceId,
environment,
type,
secretPath,
- secretValueIV,
- secretValueTag,
- secretValueCiphertext
+ ...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
+ tags,
+ skipMultilineEncoding,
+ secretName: newSecretName
};
const { data } = await apiRequest.patch(`/api/v3/secrets/${secretName}`, reqBody);
return data;
},
onSuccess: (_, { workspaceId, environment, secretPath }) => {
queryClient.invalidateQueries(
- secretKeys.getProjectSecret(workspaceId, environment, secretPath)
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
);
- }
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
});
};
-export const useDeleteSecretV3 = () => {
+export const useDeleteSecretV3 = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TDeleteSecretsV3DTO>({
@@ -165,8 +201,160 @@ export const useDeleteSecretV3 = () => {
},
onSuccess: (_, { workspaceId, environment, secretPath }) => {
queryClient.invalidateQueries(
- secretKeys.getProjectSecret(workspaceId, environment, secretPath)
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
);
- }
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
});
};
+
+export const useCreateSecretBatch = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, TCreateSecretBatchDTO>({
+ mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets, latestFileKey }) => {
+ const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
+ const randomBytes = latestFileKey
+ ? decryptAssymmetric({
+ ciphertext: latestFileKey.encryptedKey,
+ nonce: latestFileKey.nonce,
+ publicKey: latestFileKey.sender.publicKey,
+ privateKey: PRIVATE_KEY
+ })
+ : crypto.randomBytes(16).toString("hex");
+
+ const reqBody = {
+ workspaceId,
+ environment,
+ secretPath,
+ secrets: secrets.map(
+ ({ secretName, secretValue, secretComment, metadata, type, skipMultilineEncoding }) => ({
+ secretName,
+ ...encryptSecret(randomBytes, secretName, secretValue, secretComment),
+ type,
+ metadata,
+ skipMultilineEncoding
+ })
+ )
+ };
+
+ const { data } = await apiRequest.post("/api/v3/secrets/batch", reqBody);
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment, secretPath }) => {
+ queryClient.invalidateQueries(
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
+ });
+};
+
+export const useUpdateSecretBatch = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, TUpdateSecretBatchDTO>({
+ mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets, latestFileKey }) => {
+ const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
+ const randomBytes = latestFileKey
+ ? decryptAssymmetric({
+ ciphertext: latestFileKey.encryptedKey,
+ nonce: latestFileKey.nonce,
+ publicKey: latestFileKey.sender.publicKey,
+ privateKey: PRIVATE_KEY
+ })
+ : crypto.randomBytes(16).toString("hex");
+
+ const reqBody = {
+ workspaceId,
+ environment,
+ secretPath,
+ secrets: secrets.map(
+ ({ secretName, secretValue, secretComment, type, tags, skipMultilineEncoding }) => ({
+ secretName,
+ ...encryptSecret(randomBytes, secretName, secretValue, secretComment),
+ type,
+ tags,
+ skipMultilineEncoding
+ })
+ )
+ };
+
+ const { data } = await apiRequest.patch("/api/v3/secrets/batch", reqBody);
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment, secretPath }) => {
+ queryClient.invalidateQueries(
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
+ });
+};
+
+export const useDeleteSecretBatch = ({
+ options
+}: {
+ options?: Omit, "mutationFn">;
+} = {}) => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, TDeleteSecretBatchDTO>({
+ mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => {
+ const reqBody = {
+ workspaceId,
+ environment,
+ secretPath,
+ secrets
+ };
+
+ const { data } = await apiRequest.delete("/api/v3/secrets/batch", {
+ data: reqBody
+ });
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment, secretPath }) => {
+ queryClient.invalidateQueries(
+ secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath })
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath })
+ );
+ },
+ ...options
+ });
+};
+
+export const createSecret = async (dto: CreateSecretDTO) => {
+ const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto);
+ return data;
+};
diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx
index cd032e58b..a9cbc3b29 100644
--- a/frontend/src/hooks/api/secrets/queries.tsx
+++ b/frontend/src/hooks/api/secrets/queries.tsx
@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import { useCallback, useMemo } from "react";
-import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useQueries, useQuery, UseQueryOptions } from "@tanstack/react-query";
import {
decryptAssymmetric,
@@ -8,218 +8,148 @@ import {
} from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request";
-import { secretSnapshotKeys } from "../secretSnapshots/queries";
+import { UserWsKeyPair } from "../keys/types";
import {
- BatchSecretDTO,
- CreateSecretDTO,
DecryptedSecret,
EncryptedSecret,
EncryptedSecretVersion,
- GetProjectSecretsDTO,
GetSecretVersionsDTO,
- TGetProjectSecretsAllEnvDTO} from "./types";
+ TGetProjectSecretsAllEnvDTO,
+ TGetProjectSecretsDTO,
+ TGetProjectSecretsKey
+} from "./types";
export const secretKeys = {
// this is also used in secretSnapshot part
- getProjectSecret: (workspaceId: string, env: string | string[], folderId?: string) => [
- { workspaceId, env, folderId },
- "secrets"
- ],
- getProjectSecretImports: (workspaceId: string, env: string | string[], folderId?: string) => [
- { workspaceId, env, folderId },
- "secrets-imports"
- ],
- getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"]
+ getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
+ [{ workspaceId, environment, secretPath }, "secrets"] as const,
+ getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const
};
-const fetchProjectEncryptedSecrets = async (
- workspaceId: string,
- env: string | string[],
- folderId?: string,
- secretPath?: string
-) => {
+const decryptSecrets = (encryptedSecrets: EncryptedSecret[], decryptFileKey: UserWsKeyPair) => {
+ const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
+ const key = decryptAssymmetric({
+ ciphertext: decryptFileKey.encryptedKey,
+ nonce: decryptFileKey.nonce,
+ publicKey: decryptFileKey.sender.publicKey,
+ privateKey: PRIVATE_KEY
+ });
+
+ const personalSecrets: Record = {};
+ const secrets: DecryptedSecret[] = [];
+ encryptedSecrets.forEach((encSecret) => {
+ const secretKey = decryptSymmetric({
+ ciphertext: encSecret.secretKeyCiphertext,
+ iv: encSecret.secretKeyIV,
+ tag: encSecret.secretKeyTag,
+ key
+ });
+
+ const secretValue = decryptSymmetric({
+ ciphertext: encSecret.secretValueCiphertext,
+ iv: encSecret.secretValueIV,
+ tag: encSecret.secretValueTag,
+ key
+ });
+
+ const secretComment = decryptSymmetric({
+ ciphertext: encSecret.secretCommentCiphertext,
+ iv: encSecret.secretCommentIV,
+ tag: encSecret.secretCommentTag,
+ key
+ });
+
+ const decryptedSecret: DecryptedSecret = {
+ _id: encSecret._id,
+ env: encSecret.environment,
+ key: secretKey,
+ value: secretValue,
+ tags: encSecret.tags,
+ comment: secretComment,
+ createdAt: encSecret.createdAt,
+ updatedAt: encSecret.updatedAt,
+ version: encSecret.version,
+ skipMultilineEncoding: encSecret.skipMultilineEncoding
+ };
+
+ if (encSecret.type === "personal") {
+ personalSecrets[decryptedSecret.key] = {
+ id: encSecret._id,
+ value: secretValue
+ };
+ } else {
+ secrets.push(decryptedSecret);
+ }
+ });
+
+ secrets.forEach((sec) => {
+ if (personalSecrets?.[sec.key]) {
+ sec.idOverride = personalSecrets[sec.key].id;
+ sec.valueOverride = personalSecrets[sec.key].value;
+ sec.overrideAction = "modified";
+ }
+ });
+
+ return secrets;
+};
+
+const fetchProjectEncryptedSecrets = async ({
+ workspaceId,
+ environment,
+ secretPath
+}: TGetProjectSecretsKey) => {
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>("/api/v3/secrets", {
params: {
- environment: env,
+ environment,
workspaceId,
- folderId: folderId || undefined,
secretPath
}
});
-
+
return data.secrets;
};
-
export const useGetProjectSecrets = ({
workspaceId,
- env,
+ environment,
decryptFileKey,
- isPaused,
- folderId,
- secretPath
-}: GetProjectSecretsDTO) =>
+ secretPath,
+ options
+}: TGetProjectSecretsDTO & {
+ options?: Omit<
+ UseQueryOptions<
+ EncryptedSecret[],
+ unknown,
+ DecryptedSecret[],
+ ReturnType
+ >,
+ "queryKey" | "queryFn"
+ >;
+}) =>
useQuery({
+ ...options,
// wait for all values to be available
- enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
- queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId || secretPath),
- queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath),
- select: useCallback(
- (data: EncryptedSecret[]) => {
- const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
- const latestKey = decryptFileKey;
- const key = decryptAssymmetric({
- ciphertext: latestKey.encryptedKey,
- nonce: latestKey.nonce,
- publicKey: latestKey.sender.publicKey,
- privateKey: PRIVATE_KEY
- });
-
- const sharedSecrets: DecryptedSecret[] = [];
- const personalSecrets: Record = {};
- // this used for add-only mode in dashboard
- // type won't be there thus only one key is shown
- const duplicateSecretKey: Record = {};
- data.forEach((encSecret: EncryptedSecret) => {
- const secretKey = decryptSymmetric({
- ciphertext: encSecret.secretKeyCiphertext,
- iv: encSecret.secretKeyIV,
- tag: encSecret.secretKeyTag,
- key
- });
-
- const secretValue = decryptSymmetric({
- ciphertext: encSecret.secretValueCiphertext,
- iv: encSecret.secretValueIV,
- tag: encSecret.secretValueTag,
- key
- });
-
- const secretComment = decryptSymmetric({
- ciphertext: encSecret.secretCommentCiphertext,
- iv: encSecret.secretCommentIV,
- tag: encSecret.secretCommentTag,
- key
- });
-
- const decryptedSecret = {
- _id: encSecret._id,
- env: encSecret.environment,
- key: secretKey,
- value: secretValue,
- tags: encSecret.tags,
- comment: secretComment,
- createdAt: encSecret.createdAt,
- updatedAt: encSecret.updatedAt
- };
-
- if (encSecret.type === "personal") {
- personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = {
- id: encSecret._id,
- value: secretValue
- };
- } else {
- if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) {
- sharedSecrets.push(decryptedSecret);
- }
- duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true;
- }
- });
- sharedSecrets.forEach((val) => {
- const dupKey = `${val.key}-${val.env}`;
- if (personalSecrets?.[dupKey]) {
- val.idOverride = personalSecrets[dupKey].id;
- val.valueOverride = personalSecrets[dupKey].value;
- val.overrideAction = "modified";
- }
- });
- return { secrets: sharedSecrets };
- },
- [decryptFileKey]
- )
+ enabled: Boolean(decryptFileKey && workspaceId && environment) && (options?.enabled ?? true),
+ queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
+ queryFn: async () => fetchProjectEncryptedSecrets({ workspaceId, environment, secretPath }),
+ select: (secrets: EncryptedSecret[]) => decryptSecrets(secrets, decryptFileKey)
});
export const useGetProjectSecretsAllEnv = ({
workspaceId,
envs,
decryptFileKey,
- folderId,
secretPath
}: TGetProjectSecretsAllEnvDTO) => {
const secrets = useQueries({
- queries: envs.map((env) => ({
- queryKey: secretKeys.getProjectSecret(workspaceId, env, secretPath || folderId),
- enabled: Boolean(decryptFileKey && workspaceId && env),
- queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath),
- select: (data: EncryptedSecret[]) => {
- const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
- const latestKey = decryptFileKey;
- const key = decryptAssymmetric({
- ciphertext: latestKey.encryptedKey,
- nonce: latestKey.nonce,
- publicKey: latestKey.sender.publicKey,
- privateKey: PRIVATE_KEY
- });
-
- const sharedSecrets: Record = {};
- const personalSecrets: Record = {};
- // this used for add-only mode in dashboard
- // type won't be there thus only one key is shown
- const duplicateSecretKey: Record = {};
- data.forEach((encSecret: EncryptedSecret) => {
- const secretKey = decryptSymmetric({
- ciphertext: encSecret.secretKeyCiphertext,
- iv: encSecret.secretKeyIV,
- tag: encSecret.secretKeyTag,
- key
- });
-
- const secretValue = decryptSymmetric({
- ciphertext: encSecret.secretValueCiphertext,
- iv: encSecret.secretValueIV,
- tag: encSecret.secretValueTag,
- key
- });
-
- const secretComment = decryptSymmetric({
- ciphertext: encSecret.secretCommentCiphertext,
- iv: encSecret.secretCommentIV,
- tag: encSecret.secretCommentTag,
- key
- });
-
- const decryptedSecret = {
- _id: encSecret._id,
- env: encSecret.environment,
- key: secretKey,
- value: secretValue,
- tags: encSecret.tags,
- comment: secretComment,
- createdAt: encSecret.createdAt,
- updatedAt: encSecret.updatedAt
- };
-
- if (encSecret.type === "personal") {
- personalSecrets[decryptedSecret.key] = {
- id: encSecret._id,
- value: secretValue
- };
- } else {
- if (!duplicateSecretKey?.[decryptedSecret.key]) {
- sharedSecrets[decryptedSecret.key] = decryptedSecret;
- }
- duplicateSecretKey[decryptedSecret.key] = true;
- }
- });
-
- Object.keys(sharedSecrets).forEach((val) => {
- if (personalSecrets?.[val]) {
- sharedSecrets[val].idOverride = personalSecrets[val].id;
- sharedSecrets[val].valueOverride = personalSecrets[val].value;
- sharedSecrets[val].overrideAction = "modified";
- }
- });
- return sharedSecrets;
- }
+ queries: envs.map((environment) => ({
+ queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
+ enabled: Boolean(decryptFileKey && workspaceId && environment),
+ queryFn: async () => fetchProjectEncryptedSecrets({ workspaceId, environment, secretPath }),
+ select: (secs: EncryptedSecret[]) =>
+ decryptSecrets(secs, decryptFileKey).reduce>(
+ (prev, curr) => ({ ...prev, [curr.key]: curr }),
+ {}
+ )
}))
});
@@ -303,46 +233,3 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
[dto.decryptFileKey]
)
});
-
-export const useBatchSecretsOp = () => {
- const queryClient = useQueryClient();
-
- return useMutation<{}, {}, BatchSecretDTO>({
- mutationFn: async (dto) => {
- const { data } = await apiRequest.post("/api/v2/secrets/batch", dto);
- return data;
- },
- onSuccess: (_, dto) => {
- queryClient.invalidateQueries(
- secretKeys.getProjectSecret(dto.workspaceId, dto.environment, dto.folderId)
- );
- queryClient.invalidateQueries(
- secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folderId)
- );
- queryClient.invalidateQueries(
- secretSnapshotKeys.count(dto.workspaceId, dto.environment, dto?.folderId)
- );
- }
- });
-};
-
-export const createSecret = async (dto: CreateSecretDTO) => {
- const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto);
- return data;
-}
-
-export const useCreateSecret = () => {
- const queryClient = useQueryClient();
-
- return useMutation<{}, {}, CreateSecretDTO>({
- mutationFn: async (dto) => {
- const data = createSecret(dto);
- return data;
- },
- onSuccess: (_, dto) => {
- queryClient.invalidateQueries(
- secretKeys.getProjectSecret(dto.workspaceId, dto.environment)
- );
- }
- });
-};
diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts
index 2bf304f4e..253ce77a6 100644
--- a/frontend/src/hooks/api/secrets/types.ts
+++ b/frontend/src/hooks/api/secrets/types.ts
@@ -16,6 +16,7 @@ export type EncryptedSecret = {
__v: number;
createdAt: string;
updatedAt: string;
+ skipMultilineEncoding?: boolean;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
@@ -24,6 +25,7 @@ export type EncryptedSecret = {
export type DecryptedSecret = {
_id: string;
+ version: number;
key: string;
value: string;
comment: string;
@@ -35,6 +37,7 @@ export type DecryptedSecret = {
idOverride?: string;
overrideAction?: string;
folderId?: string;
+ skipMultilineEncoding?: boolean;
};
export type EncryptedSecretVersion = {
@@ -53,55 +56,21 @@ export type EncryptedSecretVersion = {
secretValueTag: string;
tags: WsTag[];
__v: number;
+ skipMultilineEncoding?: boolean;
createdAt: string;
updatedAt: string;
};
// dto
-type SecretTagArg = { _id: string; name: string; slug: string };
-
-export type UpdateSecretArg = {
- _id: string;
- folderId?: string;
- type: "shared" | "personal";
- secretName: string;
- secretKeyCiphertext: string;
- secretKeyIV: string;
- secretKeyTag: string;
- secretValueCiphertext: string;
- secretValueIV: string;
- secretValueTag: string;
- secretCommentCiphertext: string;
- secretCommentIV: string;
- secretCommentTag: string;
- tags: SecretTagArg[];
-};
-
-export type CreateSecretArg = Omit;
-
-export type DeleteSecretArg = { _id: string, secretName: string; };
-
-export type BatchSecretDTO = {
+export type TGetProjectSecretsKey = {
workspaceId: string;
- folderId: string;
environment: string;
- requests: Array<
- | { method: "POST"; secret: CreateSecretArg }
- | { method: "PATCH"; secret: UpdateSecretArg }
- | { method: "DELETE"; secret: DeleteSecretArg }
- >;
+ secretPath?: string;
};
-export type GetProjectSecretsDTO = {
- workspaceId: string;
- env: string | string[];
+export type TGetProjectSecretsDTO = {
decryptFileKey: UserWsKeyPair;
- folderId?: string;
- secretPath?: string;
- isPaused?: boolean;
- include_imports?: boolean;
- onSuccess?: (data: DecryptedSecret[]) => void;
-};
+} & TGetProjectSecretsKey;
export type TGetProjectSecretsAllEnvDTO = {
workspaceId: string;
@@ -124,6 +93,7 @@ export type TCreateSecretsV3DTO = {
secretName: string;
secretValue: string;
secretComment: string;
+ skipMultilineEncoding?: boolean;
secretPath: string;
workspaceId: string;
environment: string;
@@ -136,19 +106,63 @@ export type TUpdateSecretsV3DTO = {
environment: string;
type: string;
secretPath: string;
+ skipMultilineEncoding?: boolean;
+ newSecretName?: string;
secretName: string;
secretValue: string;
+ secretComment?: string;
+ tags?: string[];
};
export type TDeleteSecretsV3DTO = {
workspaceId: string;
environment: string;
- type: string;
+ type: "shared" | "personal";
secretPath: string;
secretName: string;
};
-// --- v3
+export type TCreateSecretBatchDTO = {
+ workspaceId: string;
+ environment: string;
+ secretPath: string;
+ latestFileKey: UserWsKeyPair;
+ secrets: Array<{
+ secretName: string;
+ secretValue: string;
+ secretComment: string;
+ skipMultilineEncoding?: boolean;
+ type: "shared" | "personal";
+ metadata?: {
+ source?: string;
+ };
+ }>;
+};
+
+export type TUpdateSecretBatchDTO = {
+ workspaceId: string;
+ environment: string;
+ secretPath: string;
+ latestFileKey: UserWsKeyPair;
+ secrets: Array<{
+ type: "shared" | "personal";
+ secretName: string;
+ skipMultilineEncoding?: boolean;
+ secretValue: string;
+ secretComment: string;
+ tags?: string[];
+ }>;
+};
+
+export type TDeleteSecretBatchDTO = {
+ workspaceId: string;
+ environment: string;
+ secretPath: string;
+ secrets: Array<{
+ secretName: string;
+ type: "shared" | "personal";
+ }>;
+};
export type CreateSecretDTO = {
workspaceId: string;
@@ -167,5 +181,5 @@ export type CreateSecretDTO = {
secretPath: string;
metadata?: {
source?: string;
- }
-}
\ No newline at end of file
+ };
+};
diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts
index c91fb551d..b46b5bfab 100644
--- a/frontend/src/hooks/api/types.ts
+++ b/frontend/src/hooks/api/types.ts
@@ -5,6 +5,9 @@ export type { TCloudIntegration, TIntegration } from "./integrations/types";
export type { UserWsKeyPair } from "./keys/types";
export type { Organization } from "./organization/types";
export type { TSecretApprovalPolicy } from "./secretApproval/types";
+export type { TSecretFolder } from "./secretFolders/types";
+export type { TImportedSecrets, TSecretImports } from "./secretImports/types";
+export * from "./secrets/types";
export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types";
export type { SubscriptionPlan } from "./subscriptions/types";
export type { WsTag } from "./tags/types";
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 39098bf5c..0dd859ed4 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -315,7 +315,7 @@ module.exports = {
// TODO:(akhilmhdh) remove all these unused and keep the config file as small as possible
// Make the whole color pallelte into simpler
bounce: "bounce 1000ms ease-in-out infinite",
- spin: "spin 4000ms ease-in-out infinite",
+ spin: "spin 1500ms ease-in-out infinite",
cursor: "cursor .6s linear infinite alternate",
type: "type 2.7s ease-out .8s infinite alternate both",
"type-reverse": "type 1.8s ease-out 0s infinite alternate-reverse both",