diff --git a/frontend/src/components/utilities/secrets/encryptSecrets.ts b/frontend/src/components/utilities/secrets/encryptSecrets.ts index 4eb59ae9f..121f3ab73 100644 --- a/frontend/src/components/utilities/secrets/encryptSecrets.ts +++ b/frontend/src/components/utilities/secrets/encryptSecrets.ts @@ -1,30 +1,4 @@ -import crypto from "crypto"; - -import { SecretDataProps, Tag } from "public/data/frequentInterfaces"; - -import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; -import { SecretType } from "@app/hooks/api/types"; - -import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto"; - -interface EncryptedSecretProps { - id: string; - createdAt: string; - environment: string; - secretName: string; - secretCommentCiphertext: string; - secretCommentIV: string; - secretCommentTag: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - type: SecretType; - tags: Tag[]; -} - +import { SecretDataProps } from "public/data/frequentInterfaces"; /** * Encypt secrets before pushing the to the DB * @param {object} obj @@ -34,7 +8,6 @@ interface EncryptedSecretProps { */ const encryptSecrets = async ({ secretsToEncrypt, - workspaceId, env }: { secretsToEncrypt: SecretDataProps[]; @@ -43,74 +16,14 @@ const encryptSecrets = async ({ }) => { let secrets; try { - // const sharedKey = await getLatestFileKey({ workspaceId }); - const wsKey = await fetchUserWsKey(workspaceId); - - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - - let randomBytes: string; - if (wsKey) { - // case: a (shared) key exists for the workspace - randomBytes = decryptAssymmetric({ - ciphertext: wsKey.encryptedKey, - nonce: wsKey.nonce, - publicKey: wsKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - } else { - // case: a (shared) key does not exist for the workspace - randomBytes = crypto.randomBytes(16).toString("hex"); - } - secrets = secretsToEncrypt.map((secret) => { - // encrypt key - const { - ciphertext: secretKeyCiphertext, - iv: secretKeyIV, - tag: secretKeyTag - } = encryptSymmetric({ - plaintext: secret.key, - key: randomBytes - }); - - // encrypt value - const { - ciphertext: secretValueCiphertext, - iv: secretValueIV, - tag: secretValueTag - } = encryptSymmetric({ - plaintext: secret.value ?? "", - key: randomBytes - }); - - // encrypt comment - const { - ciphertext: secretCommentCiphertext, - iv: secretCommentIV, - tag: secretCommentTag - } = encryptSymmetric({ - plaintext: secret.comment ?? "", - key: randomBytes - }); - - const result: EncryptedSecretProps = { + const result = { id: secret.id, createdAt: "", environment: env, - secretName: secret.key, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - secretCommentCiphertext, - secretCommentIV, - secretCommentTag, - type: - secret.valueOverride === undefined || secret?.value !== secret?.valueOverride - ? SecretType.Shared - : SecretType.Personal, + secretKey: secret.key, + secretValue: secret.value, + secretComment: secret.comment, tags: secret.tags }; diff --git a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx index a1bf35292..629d94a37 100644 --- a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx +++ b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx @@ -5,7 +5,7 @@ import * as Popover from "@radix-ui/react-popover"; import { useWorkspace } from "@app/context"; import { useDebounce, useToggle } from "@app/hooks"; -import { useGetProjectFolders, useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api"; +import { useGetProjectFolders, useGetProjectSecrets } from "@app/hooks/api"; import { SecretInput } from "../SecretInput"; @@ -76,7 +76,6 @@ export const InfisicalSecretInput = forwardRef( ) => { const { currentWorkspace } = useWorkspace(); const workspaceId = currentWorkspace?.id || ""; - const { data: decryptFileKey } = useGetUserWsKey(workspaceId); const debouncedValue = useDebounce(value, 500); @@ -121,7 +120,6 @@ export const InfisicalSecretInput = forwardRef( const isPopupOpen = Boolean(suggestionSource.isOpen) && isFocused; const { data: secrets } = useGetProjectSecrets({ - decryptFileKey: decryptFileKey!, environment: suggestionSource.environment || "", secretPath: suggestionSource.secretPath || "", workspaceId, @@ -193,8 +191,9 @@ export const InfisicalSecretInput = forwardRef( ); // mid will be computed value inside the interpolation const mid = suggestionSource.isDeep - ? `${suggestionSource.value.slice(0, -suggestionSource.predicate.length || undefined)}${selectedSuggestion.slug - }` + ? `${suggestionSource.value.slice(0, -suggestionSource.predicate.length || undefined)}${ + selectedSuggestion.slug + }` : selectedSuggestion.slug; // whether we should append . or closing bracket on selecting suggestion const closingSymbol = getClosingSymbol( @@ -328,8 +327,9 @@ export const InfisicalSecretInput = forwardRef( key={`secret-reference-secret-${i + 1}`} >
diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 6c4e91ffc..b6338ce35 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -1,81 +1,40 @@ -import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets"; -import { createSecret } from "@app/hooks/api/secrets/mutations"; +import { apiRequest } from "@app/config/request"; import { createWorkspace } from "@app/hooks/api/workspace/queries"; const secretsToBeAdded = [ { - pos: 0, - key: "DATABASE_URL", + secretKey: "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: [] + secretValue: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", + secretComment: "Secret referencing example" }, { - pos: 1, - key: "DB_USERNAME", - value: "OVERRIDE_THIS", - valueOverride: undefined, - comment: "Override secrets with personal value", - id: "", - tags: [] + secretKey: "DB_USERNAME", + secretValue: "OVERRIDE_THIS", + secretComment: "Override secrets with personal value" }, { - pos: 2, - key: "DB_PASSWORD", - value: "OVERRIDE_THIS", - valueOverride: undefined, - comment: "Another secret override", - id: "", - tags: [] + secretKey: "DB_PASSWORD", + secretValue: "OVERRIDE_THIS", + secretComment: "Another secret override" }, { - pos: 3, - key: "DB_USERNAME", - value: "user1234", - valueOverride: "user1234", - comment: "", - id: "", - tags: [] + secretKey: "DB_PASSWORD", + secretValue: "example_password" }, { - pos: 4, - key: "DB_PASSWORD", - value: "example_password", - valueOverride: "example_password", - comment: "", - id: "", - tags: [] + secretKey: "TWILIO_AUTH_TOKEN", + secretValue: "example_twillio_token" }, { - 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: [] + secretKey: "WEBSITE_URL", + secretValue: "http://localhost:3000" } ]; /** * Create and initialize a new project in organization with id [organizationId] * Note: current user should be a member of the organization - * @param {Object} obj - * @param {String} obj.organizationId - id of organization - * @param {String} obj.projectName - name of new project - * @returns {Project} project - new project */ const initProjectHelper = async ({ projectName }: { projectName: string }) => { // create new project @@ -85,37 +44,14 @@ const initProjectHelper = async ({ projectName }: { projectName: string }) => { projectName }); - // encrypt and upload secrets to new project - const secrets = await encryptSecrets({ - secretsToEncrypt: secretsToBeAdded, - workspaceId: project.id, - env: "dev" - }); - try { - await Promise.allSettled( - (secrets || []).map((secret) => - createSecret({ - workspaceId: project.id, - environment: secret.environment, - type: secret.type, - secretKey: secret.secretName, - secretKeyCiphertext: secret.secretKeyCiphertext, - secretKeyIV: secret.secretKeyIV, - secretKeyTag: secret.secretKeyTag, - secretValueCiphertext: secret.secretValueCiphertext, - secretValueIV: secret.secretValueIV, - secretValueTag: secret.secretValueTag, - secretCommentCiphertext: secret.secretCommentCiphertext, - secretCommentIV: secret.secretCommentIV, - secretCommentTag: secret.secretCommentTag, - secretPath: "/", - metadata: { - source: "signup" - } - }) - ) - ); + const { data } = await apiRequest.post("/api/v3/secrets/batch/raw", { + workspaceId: project.id, + environment: "dev", + secretPath: "/", + secrets: secretsToBeAdded + }); + return data; } catch (err) { console.error("Failed to upload secrets", err); } diff --git a/frontend/src/helpers/secret.ts b/frontend/src/helpers/secret.ts deleted file mode 100644 index 607fa4268..000000000 --- a/frontend/src/helpers/secret.ts +++ /dev/null @@ -1,175 +0,0 @@ -import path from "path"; - -import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto"; -import { fetchProjectEncryptedSecrets } from "@app/hooks/api/secrets/queries"; - -const INTERPOLATION_SYNTAX_REG = /\${([^}]+)}/g; -export const interpolateSecrets = ({ - projectId, - secretEncKey -}: { - projectId: string; - secretEncKey: string; -}) => { - const fetchSecretsCrossEnv = () => { - const fetchCache: Record> = {}; - - return async (secRefEnv: string, secRefPath: string[], secRefKey: string) => { - const secRefPathUrl = path.join("/", ...secRefPath); - const uniqKey = `${secRefEnv}-${secRefPathUrl}`; - - if (fetchCache?.[uniqKey]) { - return fetchCache[uniqKey][secRefKey]; - } - - // get secrets by projectId, env, path - const encryptedSecrets = await fetchProjectEncryptedSecrets({ - workspaceId: projectId, - environment: secRefEnv, - secretPath: secRefPathUrl - }); - - const decryptedSec = encryptedSecrets.reduce>((prev, secret) => { - const secretKey = decryptSymmetric({ - ciphertext: secret.secretKeyCiphertext, - iv: secret.secretKeyIV, - tag: secret.secretKeyTag, - key: secretEncKey - }); - const secretValue = decryptSymmetric({ - ciphertext: secret.secretValueCiphertext, - iv: secret.secretValueIV, - tag: secret.secretValueTag, - key: secretEncKey - }); - - // eslint-disable-next-line - prev[secretKey] = secretValue; - return prev; - }, {}); - - fetchCache[uniqKey] = decryptedSec; - - return fetchCache[uniqKey][secRefKey]; - }; - }; - - const recursivelyExpandSecret = async ( - expandedSec: Record, - interpolatedSec: Record, - fetchCrossEnv: (env: string, secPath: string[], secKey: string) => Promise, - recursionChainBreaker: Record, - key: string - ) => { - if (expandedSec?.[key] !== undefined) { - return expandedSec[key]; - } - if (recursionChainBreaker?.[key]) { - return ""; - } - // eslint-disable-next-line - recursionChainBreaker[key] = true; - - let interpolatedValue = interpolatedSec[key]; - if (!interpolatedValue) { - // eslint-disable-next-line no-console - console.error(`Couldn't find referenced value - ${key}`); - return ""; - } - - const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG); - if (refs) { - await Promise.all( - refs.map(async (interpolationSyntax) => { - const interpolationKey = interpolationSyntax.slice(2, interpolationSyntax.length - 1); - const entities = interpolationKey.trim().split("."); - - if (entities.length === 1) { - const val = await recursivelyExpandSecret( - expandedSec, - interpolatedSec, - fetchCrossEnv, - recursionChainBreaker, - interpolationKey - ); - if (val) { - interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val); - } - return; - } - - if (entities.length > 1) { - const secRefEnv = entities[0]; - const secRefPath = entities.slice(1, entities.length - 1); - const secRefKey = entities[entities.length - 1]; - - const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey); - if (val) { - interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val); - } - } - }) - ); - } - - // eslint-disable-next-line - expandedSec[key] = interpolatedValue; - return interpolatedValue; - }; - - // used to convert multi line ones to quotes ones with \n - const formatMultiValueEnv = (val?: string) => { - if (!val) return ""; - if (!val.match("\n")) return val; - return `"${val.replace(/\n/g, "\\n")}"`; - }; - - const expandSecrets = async ( - secrets: Record - ) => { - const expandedSec: Record = {}; - const interpolatedSec: Record = {}; - - const crossSecEnvFetch = fetchSecretsCrossEnv(); - - Object.keys(secrets).forEach((key) => { - if (secrets[key].value.match(INTERPOLATION_SYNTAX_REG)) { - interpolatedSec[key] = secrets[key].value; - } else { - expandedSec[key] = secrets[key].value; - } - }); - - await Promise.all( - Object.keys(secrets).map(async (key) => { - if (expandedSec?.[key]) { - // should not do multi line encoding if user has set it to skip - // eslint-disable-next-line - secrets[key].value = secrets[key].skipMultilineEncoding - ? expandedSec[key] - : formatMultiValueEnv(expandedSec[key]); - return; - } - - // this is to avoid recursion loop. So the graph should be direct graph rather than cyclic - // so for any recursion building if there is an entity two times same key meaning it will be looped - const recursionChainBreaker: Record = {}; - const expandedVal = await recursivelyExpandSecret( - expandedSec, - interpolatedSec, - crossSecEnvFetch, - recursionChainBreaker, - key - ); - - // eslint-disable-next-line - secrets[key].value = secrets[key].skipMultilineEncoding - ? expandedVal - : formatMultiValueEnv(expandedVal); - }) - ); - - return secrets; - }; - return expandSecrets; -}; diff --git a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx index aaf84941a..68db377cf 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/queries.tsx +++ b/frontend/src/hooks/api/secretApprovalRequest/queries.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import { useInfiniteQuery, UseInfiniteQueryOptions, @@ -12,8 +13,7 @@ import { import { apiRequest } from "@app/config/request"; import { UserWsKeyPair } from "../keys/types"; -import { decryptSecrets } from "../secrets/queries"; -import { DecryptedSecret } from "../secrets/types"; +import { EncryptedSecret, SecretType,SecretV3RawSanitized } from "../secrets/types"; import { CommitType, TGetSecretApprovalRequestCount, @@ -45,6 +45,78 @@ export const secretApprovalRequestKeys = { ] }; +export 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: SecretV3RawSanitized[] = []; + 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: SecretV3RawSanitized = { + id: encSecret.id, + env: encSecret.environment, + key: secretKey, + value: secretValue, + tags: encSecret.tags, + comment: secretComment, + reminderRepeatDays: encSecret.secretReminderRepeatDays, + reminderNote: encSecret.secretReminderNote, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt, + version: encSecret.version, + skipMultilineEncoding: encSecret.skipMultilineEncoding + }; + + if (encSecret.type === SecretType.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; +}; + export const decryptSecretApprovalSecret = ( encSecret: TSecretApprovalSecChangeData, decryptFileKey: UserWsKeyPair @@ -173,7 +245,7 @@ export const useGetSecretApprovalRequestDetails = ({ UseQueryOptions< TSecretApprovalRequest, unknown, - TSecretApprovalRequest, + TSecretApprovalRequest, ReturnType >, "queryKey" | "queryFn" diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index a7c510662..95f1621a2 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -1,10 +1,6 @@ import { useCallback } from "react"; import { useQueries, useQuery, UseQueryOptions } from "@tanstack/react-query"; -import { - decryptAssymmetric, - decryptSymmetric -} from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; import { @@ -75,7 +71,7 @@ const fetchImportedSecrets = async ( directory?: string ) => { const { data } = await apiRequest.get<{ secrets: TImportedSecrets[] }>( - "/api/v1/secret-imports/secrets", + "/api/v1/secret-imports/secrets/raw", { params: { workspaceId, @@ -107,7 +103,6 @@ const fetchImportedFolders = async ({ export const useGetImportedSecretsSingleEnv = ({ environment, - decryptFileKey, path, projectId, options = {} @@ -123,78 +118,40 @@ export const useGetImportedSecretsSingleEnv = ({ >; }) => useQuery({ - enabled: - Boolean(projectId) && - Boolean(environment) && - Boolean(decryptFileKey) && - (options?.enabled ?? true), + enabled: Boolean(projectId) && Boolean(environment) && (options?.enabled ?? true), queryKey: secretImportKeys.getSecretImportSecrets({ environment, path, projectId }), queryFn: () => fetchImportedSecrets(projectId, environment, path), - select: useCallback( - (data: TImportedSecrets[]) => { - 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 - }); - - return data.map((el) => ({ - environment: el.environment, - secretPath: el.secretPath, - environmentInfo: el.environmentInfo, - folderId: el.folderId, - secrets: el.secrets.map((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 - }); - - return { - id: encSecret.id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt, - version: encSecret.version - }; - }) - })); - }, - [decryptFileKey] - ) + select: (data: TImportedSecrets[]) => { + return data.map((el) => ({ + environment: el.environment, + secretPath: el.secretPath, + environmentInfo: el.environmentInfo, + folderId: el.folderId, + secrets: el.secrets.map((encSecret) => { + return { + id: encSecret.id, + env: encSecret.environment, + key: encSecret.secretKey, + value: encSecret.secretValue, + tags: encSecret.tags, + comment: encSecret.secretComment, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt, + version: encSecret.version + }; + }) + })); + } }); export const useGetImportedSecretsAllEnvs = ({ projectId, environments, - path = "/", - decryptFileKey + path = "/" }: TGetSecretImportsAllEnvs) => { const secretImports = useQueries({ queries: environments.map((env) => ({ @@ -206,60 +163,27 @@ export const useGetImportedSecretsAllEnvs = ({ queryFn: () => fetchImportedSecrets(projectId, env, path).catch(() => []), enabled: Boolean(projectId) && Boolean(env), // eslint-disable-next-line react-hooks/rules-of-hooks - select: useCallback( - (data: TImportedSecrets[]) => { - 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 - }); - - return data.map((el) => ({ - environment: el.environment, - secretPath: el.secretPath, - environmentInfo: el.environmentInfo, - folderId: el.folderId, - secrets: el.secrets.map((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 - }); - - return { - id: encSecret.id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt, - version: encSecret.version - }; - }) - })); - }, - [decryptFileKey] - ) + select: (data: TImportedSecrets[]) => { + return data.map((el) => ({ + environment: el.environment, + secretPath: el.secretPath, + environmentInfo: el.environmentInfo, + folderId: el.folderId, + secrets: el.secrets.map((encSecret) => { + return { + id: encSecret.id, + env: encSecret.environment, + key: encSecret.secretKey, + value: encSecret.secretValue, + tags: encSecret.tags, + comment: encSecret.secretComment, + createdAt: encSecret.createdAt, + 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 1a6c06dd3..1bf60bd4e 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -1,5 +1,4 @@ -import { UserWsKeyPair } from "../keys/types"; -import { EncryptedSecret } from "../secrets/types"; +import { SecretV3Raw } from "../secrets/types"; import { WorkspaceEnv } from "../workspace/types"; export type TSecretImport = { @@ -28,7 +27,7 @@ export type TImportedSecrets = { environmentInfo: WorkspaceEnv; secretPath: string; folderId: string; - secrets: EncryptedSecret[]; + secrets: SecretV3Raw[]; }; export type TGetSecretImports = { @@ -39,7 +38,6 @@ export type TGetSecretImports = { export type TGetSecretImportsAllEnvs = { projectId: string; - decryptFileKey: UserWsKeyPair; path?: string; environments: string[]; }; @@ -48,7 +46,6 @@ export type TGetImportedSecrets = { projectId: string; environment: string; path?: string; - decryptFileKey: UserWsKeyPair; }; export type TuseGetImportedFoldersByEnv = { diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx index cd81152a1..7709d3785 100644 --- a/frontend/src/hooks/api/secretSnapshots/queries.tsx +++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx @@ -7,7 +7,7 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; -import { DecryptedSecret, SecretType } from "../secrets/types"; +import { SecretType,SecretV3RawSanitized } from "../secrets/types"; import { TGetSecretSnapshotsDTO, TSecretRollbackDTO, @@ -80,7 +80,7 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, snapshotId }: TSnapshotD privateKey: PRIVATE_KEY }); - const sharedSecrets: DecryptedSecret[] = []; + const sharedSecrets: SecretV3RawSanitized[] = []; const personalSecrets: Record = {}; data.secretVersions.forEach((encSecret) => { const secretKey = decryptSymmetric({ diff --git a/frontend/src/hooks/api/secrets/mutations.tsx b/frontend/src/hooks/api/secrets/mutations.tsx index 9c3fd23f0..4f4e00922 100644 --- a/frontend/src/hooks/api/secrets/mutations.tsx +++ b/frontend/src/hooks/api/secrets/mutations.tsx @@ -1,18 +1,11 @@ -import crypto from "crypto"; - import { MutationOptions, useMutation, useQueryClient } from "@tanstack/react-query"; -import { - decryptAssymmetric, - encryptSymmetric -} from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; import { secretApprovalRequestKeys } from "../secretApprovalRequest/queries"; import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { secretKeys } from "./queries"; import { - CreateSecretDTO, TCreateSecretBatchDTO, TCreateSecretsV3DTO, TDeleteSecretBatchDTO, @@ -22,50 +15,6 @@ import { TUpdateSecretsV3DTO } from "./types"; -const encryptSecret = (randomBytes: string, key: string, value?: string, comment?: string) => { - // encrypt key - const { - ciphertext: secretKeyCiphertext, - iv: secretKeyIV, - tag: secretKeyTag - } = encryptSymmetric({ - plaintext: key, - key: randomBytes - }); - - // encrypt value - const { - ciphertext: secretValueCiphertext, - iv: secretValueIV, - tag: secretValueTag - } = encryptSymmetric({ - plaintext: value ?? "", - key: randomBytes - }); - - // encrypt comment - const { - ciphertext: secretCommentCiphertext, - iv: secretCommentIV, - tag: secretCommentTag - } = encryptSymmetric({ - plaintext: comment ?? "", - key: randomBytes - }); - - return { - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - secretCommentCiphertext, - secretCommentIV, - secretCommentTag - }; -}; - export const useCreateSecretV3 = ({ options }: { @@ -78,32 +27,20 @@ export const useCreateSecretV3 = ({ type, environment, workspaceId, - secretName, + secretKey, secretValue, - latestFileKey, secretComment, skipMultilineEncoding }) => { - 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, - type, + const { data } = await apiRequest.post(`/api/v3/secrets/raw/${secretKey}`, { secretPath, - ...encryptSecret(randomBytes, secretName, secretValue, secretComment), + type, + environment, + workspaceId, + secretValue, + secretComment, skipMultilineEncoding - }; - const { data } = await apiRequest.post(`/api/v3/secrets/${secretName}`, reqBody); + }); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { @@ -132,44 +69,30 @@ export const useUpdateSecretV3 = ({ mutationFn: async ({ secretPath = "/", type, - secretId, environment, workspaceId, - secretName, + secretKey, secretValue, - latestFileKey, - tags, + tagIds, secretComment, secretReminderRepeatDays, secretReminderNote, newSecretName, skipMultilineEncoding }) => { - 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 = { + const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, { workspaceId, environment, type, secretReminderNote, secretReminderRepeatDays, secretPath, - secretId, - ...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment), - tags, skipMultilineEncoding, - secretName: newSecretName - }; - const { data } = await apiRequest.patch(`/api/v3/secrets/${secretName}`, reqBody); + newSecretName, + secretComment, + tagIds, + secretValue + }); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { @@ -201,19 +124,17 @@ export const useDeleteSecretV3 = ({ type, environment, workspaceId, - secretName, + secretKey, secretId }) => { - const reqBody = { - workspaceId, - environment, - type, - secretPath, - secretId - }; - - const { data } = await apiRequest.delete(`/api/v3/secrets/${secretName}`, { - data: reqBody + const { data } = await apiRequest.delete(`/api/v3/secrets/raw/${secretKey}`, { + data: { + workspaceId, + environment, + type, + secretPath, + secretId + } }); return data; }, @@ -241,33 +162,13 @@ export const useCreateSecretBatch = ({ 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 = { + mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { + const { data } = await apiRequest.post("/api/v3/secrets/batch/raw", { 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); + secrets + }); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { @@ -294,33 +195,13 @@ export const useUpdateSecretBatch = ({ 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 = { + mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { + const { data } = await apiRequest.patch("/api/v3/secrets/batch/raw", { 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); + secrets + }); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { @@ -348,15 +229,13 @@ export const useDeleteSecretBatch = ({ 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 + const { data } = await apiRequest.delete("/api/v3/secrets/batch/raw", { + data: { + workspaceId, + environment, + secretPath, + secrets + } }); return data; }, @@ -443,7 +322,7 @@ export const useMoveSecrets = ({ }); }; -export const createSecret = async (dto: CreateSecretDTO) => { +export const createSecret = async (dto: TCreateSecretsV3DTO) => { 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 382d01785..5864102d8 100644 --- a/frontend/src/hooks/api/secrets/queries.tsx +++ b/frontend/src/hooks/api/secrets/queries.tsx @@ -8,13 +8,13 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; -import { UserWsKeyPair } from "../keys/types"; import { - DecryptedSecret, - EncryptedSecret, EncryptedSecretVersion, GetSecretVersionsDTO, SecretType, + SecretV3Raw, + SecretV3RawResponse, + SecretV3RawSanitized, TGetProjectSecretsAllEnvDTO, TGetProjectSecretsDTO, TGetProjectSecretsKey @@ -27,61 +27,49 @@ export const secretKeys = { getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const }; -export 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 +export const fetchProjectSecrets = async ({ + workspaceId, + environment, + secretPath, + includeImports, + expandSecretReferences +}: TGetProjectSecretsKey) => { + const { data } = await apiRequest.get("/api/v3/secrets/raw", { + params: { + environment, + workspaceId, + secretPath, + expandSecretReferences, + include_imports: includeImports + } }); + return data; +}; + +export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => { 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, - reminderRepeatDays: encSecret.secretReminderRepeatDays, - reminderNote: encSecret.secretReminderNote, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt, - version: encSecret.version, - skipMultilineEncoding: encSecret.skipMultilineEncoding + const secrets: SecretV3RawSanitized[] = []; + rawSecrets.forEach((el) => { + const decryptedSecret: SecretV3RawSanitized = { + id: el.id, + env: el.environment, + key: el.secretKey, + value: el.secretValue, + tags: el.tags || [], + comment: el.secretComment || "", + reminderRepeatDays: el.secretReminderRepeatDays, + reminderNote: el.secretReminderNote, + createdAt: el.createdAt, + updatedAt: el.updatedAt, + version: el.version, + skipMultilineEncoding: el.skipMultilineEncoding }; - if (encSecret.type === SecretType.Personal) { + if (el.type === SecretType.Personal) { personalSecrets[decryptedSecret.key] = { - id: encSecret.id, - value: secretValue + id: el.id, + value: el.secretValue }; } else { secrets.push(decryptedSecret); @@ -99,33 +87,17 @@ export const decryptSecrets = ( return secrets; }; -export const fetchProjectEncryptedSecrets = async ({ - workspaceId, - environment, - secretPath -}: TGetProjectSecretsKey) => { - const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>("/api/v3/secrets", { - params: { - environment, - workspaceId, - secretPath - } - }); - - return data.secrets; -}; export const useGetProjectSecrets = ({ workspaceId, environment, - decryptFileKey, secretPath, options }: TGetProjectSecretsDTO & { options?: Omit< UseQueryOptions< - EncryptedSecret[], + SecretV3RawResponse, unknown, - DecryptedSecret[], + SecretV3RawSanitized[], ReturnType >, "queryKey" | "queryFn" @@ -134,25 +106,24 @@ export const useGetProjectSecrets = ({ useQuery({ ...options, // wait for all values to be available - enabled: Boolean(decryptFileKey && workspaceId && environment) && (options?.enabled ?? true), + enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true), queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }), - queryFn: async () => fetchProjectEncryptedSecrets({ workspaceId, environment, secretPath }), - select: (secrets: EncryptedSecret[]) => decryptSecrets(secrets, decryptFileKey) + queryFn: async () => fetchProjectSecrets({ workspaceId, environment, secretPath }), + select: ({ secrets }) => mergePersonalSecrets(secrets) }); export const useGetProjectSecretsAllEnv = ({ workspaceId, envs, - decryptFileKey, secretPath }: TGetProjectSecretsAllEnvDTO) => { const secrets = useQueries({ 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>( + enabled: Boolean(workspaceId && environment), + queryFn: async () => fetchProjectSecrets({ workspaceId, environment, secretPath }), + select: (el: SecretV3RawResponse) => + mergePersonalSecrets(el.secrets).reduce>( (prev, curr) => ({ ...prev, [curr.key]: curr }), {} ) diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index de7e1d503..d82e2203b 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -30,15 +30,16 @@ export type EncryptedSecret = { tags: WsTag[]; }; -export type DecryptedSecret = { +// both personal and shared secret stitiched together for dashboard +export type SecretV3RawSanitized = { id: string; version: number; key: string; value: string; - comment: string; + comment?: string; reminderRepeatDays?: number | null; reminderNote?: string | null; - tags: WsTag[]; + tags?: WsTag[]; createdAt: string; updatedAt: string; env: string; @@ -49,6 +50,35 @@ export type DecryptedSecret = { skipMultilineEncoding?: boolean; }; +export type SecretV3Raw = { + id: string; + _id: string; + workspace: string; + environment: string; + version: number; + type: string; + secretKey: string; + secretValue: string; + secretComment?: string; + secretReminderNote?: string; + secretReminderRepeatDays?: number; + skipMultilineEncoding?: boolean; + metadata?: Record; + tags?: WsTag[]; + createdAt: string; + updatedAt: string; +}; + +export type SecretV3RawResponse = { + secrets: SecretV3Raw[]; + imports: { + secretPath: string; + environment: string; + folderId: string; + secrets: SecretV3Raw[]; + }[]; +}; + export type EncryptedSecretVersion = { id: string; secretId: string; @@ -75,16 +105,15 @@ export type TGetProjectSecretsKey = { workspaceId: string; environment: string; secretPath?: string; + includeImports?: boolean; + expandSecretReferences?: boolean; }; -export type TGetProjectSecretsDTO = { - decryptFileKey: UserWsKeyPair; -} & TGetProjectSecretsKey; +export type TGetProjectSecretsDTO = TGetProjectSecretsKey; export type TGetProjectSecretsAllEnvDTO = { workspaceId: string; envs: string[]; - decryptFileKey: UserWsKeyPair; folderId?: string; secretPath?: string; isPaused?: boolean; @@ -98,8 +127,7 @@ export type GetSecretVersionsDTO = { }; export type TCreateSecretsV3DTO = { - latestFileKey: UserWsKeyPair; - secretName: string; + secretKey: string; secretValue: string; secretComment: string; skipMultilineEncoding?: boolean; @@ -110,20 +138,18 @@ export type TCreateSecretsV3DTO = { }; export type TUpdateSecretsV3DTO = { - latestFileKey: UserWsKeyPair; workspaceId: string; environment: string; - type: SecretType; secretPath: string; + type: SecretType; skipMultilineEncoding?: boolean; newSecretName?: string; - secretName: string; - secretId?: string; + secretKey: string; secretValue: string; secretComment?: string; secretReminderRepeatDays?: number | null; secretReminderNote?: string | null; - tags?: string[]; + tagIds?: string[]; }; export type TDeleteSecretsV3DTO = { @@ -131,7 +157,7 @@ export type TDeleteSecretsV3DTO = { environment: string; type: SecretType; secretPath: string; - secretName: string; + secretKey: string; secretId?: string; }; @@ -139,13 +165,13 @@ export type TCreateSecretBatchDTO = { workspaceId: string; environment: string; secretPath: string; - latestFileKey: UserWsKeyPair; secrets: Array<{ - secretName: string; + secretKey: string; secretValue: string; secretComment: string; skipMultilineEncoding?: boolean; type: SecretType; + tagIds?: string[]; metadata?: { source?: string; }; @@ -156,14 +182,16 @@ export type TUpdateSecretBatchDTO = { workspaceId: string; environment: string; secretPath: string; - latestFileKey: UserWsKeyPair; secrets: Array<{ type: SecretType; - secretName: string; - skipMultilineEncoding?: boolean; + secretKey: string; secretValue: string; - secretComment: string; - tags?: string[]; + secretComment?: string; + skipMultilineEncoding?: boolean; + tagIds?: string[]; + metadata?: { + source?: string; + }; }>; }; @@ -172,7 +200,7 @@ export type TDeleteSecretBatchDTO = { environment: string; secretPath: string; secrets: Array<{ - secretName: string; + secretKey: string; type: SecretType; }>; }; @@ -187,23 +215,3 @@ export type TMoveSecretsDTO = { secretIds: string[]; shouldOverwrite: boolean; }; - -export type CreateSecretDTO = { - workspaceId: string; - environment: string; - type: SecretType; - secretKey: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - secretCommentCiphertext: string; - secretCommentIV: string; - secretCommentTag: string; - secretPath: string; - metadata?: { - source?: string; - }; -}; diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx index bdb059a63..0b6e9ac10 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx @@ -13,11 +13,16 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { CommitType, DecryptedSecret, TSecretApprovalSecChange, WsTag } from "@app/hooks/api/types"; +import { + CommitType, + SecretV3RawSanitized, + TSecretApprovalSecChange, + WsTag +} from "@app/hooks/api/types"; export type Props = { op: CommitType; - secretVersion?: DecryptedSecret; + secretVersion?: SecretV3RawSanitized; newVersion?: Omit & { tags?: WsTag[] }; presentSecretVersionNumber: number; hasMerged?: Boolean; diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index 79bc72855..b9258225f 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -100,7 +100,6 @@ export const SecretMainPage = () => { environment, workspaceId, secretPath, - decryptFileKey: decryptFileKey!, options: { enabled: canReadSecret } @@ -131,7 +130,6 @@ export const SecretMainPage = () => { const { data: importedSecrets } = useGetImportedSecretsSingleEnv({ projectId: workspaceId, environment, - decryptFileKey: decryptFileKey!, path: secretPath, options: { enabled: canReadSecret @@ -255,7 +253,6 @@ export const SecretMainPage = () => { <> { { secrets={secrets} environment={environment} workspaceId={workspaceId} - decryptFileKey={decryptFileKey!} secretPath={secretPath} isSmaller={isNotEmtpy} environments={currentWorkspace?.environments} diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx index a4b3eb7c6..855fd393e 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx @@ -25,7 +25,6 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; -import { decryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; import { Button, DeleteActionModal, @@ -46,15 +45,10 @@ import { UpgradePlanModal } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context"; -import { interpolateSecrets } from "@app/helpers/secret"; import { usePopUp } from "@app/hooks"; -import { - useCreateFolder, - useDeleteSecretBatch, - useGetUserWsKey, - useMoveSecrets -} from "@app/hooks/api"; -import { DecryptedSecret, SecretType, TImportedSecrets, WsTag } from "@app/hooks/api/types"; +import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api"; +import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries"; +import { SecretType, SecretV3RawSanitized, WsTag } from "@app/hooks/api/types"; import { debounce } from "@app/lib/fn/debounce"; import { @@ -70,9 +64,8 @@ import { FolderForm } from "./FolderForm"; import { MoveSecretsModal } from "./MoveSecretsModal"; type Props = { - secrets?: DecryptedSecret[]; + secrets?: SecretV3RawSanitized[]; // swtich the secrets type as it gets decrypted after api call - importedSecrets?: Array & { secrets: DecryptedSecret[] }>; environment: string; // @depreciated will be moving all these details to zustand workspaceId: string; @@ -92,7 +85,6 @@ type Props = { export const ActionBar = ({ secrets = [], - importedSecrets = [], environment, workspaceId, projectSlug, @@ -124,7 +116,6 @@ export const ActionBar = ({ const { mutateAsync: createFolder } = useCreateFolder(); const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch(); const { mutateAsync: moveSecrets } = useMoveSecrets(); - const { data: decryptFileKey } = useGetUserWsKey(workspaceId); const selectedSecrets = useSelectedSecrets(); const { reset: resetSelectedSecret } = useSelectedSecretActions(); @@ -155,58 +146,46 @@ export const ActionBar = ({ }; const handleSecretDownload = async () => { - const secPriority: Record = {}; - const downloadedSecrets: Array<{ key: string; value: string; comment?: string }> = []; - - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const workspaceKey = decryptAssymmetric({ - ciphertext: decryptFileKey!.encryptedKey, - nonce: decryptFileKey!.nonce, - publicKey: decryptFileKey!.sender.publicKey, - privateKey: PRIVATE_KEY + const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({ + workspaceId, + expandSecretReferences: true, + includeImports: true, + environment, + secretPath }); - - const expandSecrets = interpolateSecrets({ - projectId: workspaceId, - secretEncKey: workspaceKey - }); - - const secretRecord: Record< - string, - { value: string; comment?: string; skipMultilineEncoding?: boolean } - > = {}; - - // load up secrets in dashboard - secrets?.forEach(({ key, value, valueOverride, comment }) => { - secPriority[key] = true; - downloadedSecrets.push({ key, value: valueOverride || value, comment }); - }); - // now load imported secrets with secPriority - for (let i = importedSecrets.length - 1; i >= 0; i -= 1) { - importedSecrets[i].secrets.forEach(({ key, value, valueOverride, comment }) => { - if (secPriority?.[key]) return; - downloadedSecrets.unshift({ key, value: valueOverride || value, comment }); - secPriority[key] = true; + const secretsPicked = new Set(); + const secretsToDownload: { key: string; value?: string; comment?: string }[] = []; + localSecrets.forEach((el) => { + secretsPicked.add(el.secretKey); + secretsToDownload.push({ + key: el.secretKey, + value: el.secretValue, + comment: el.secretComment }); + }); + + for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) { + for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) { + const secret = localImportedSecrets[i].secrets[j]; + if (!secretsPicked.has(secret.secretKey)) { + secretsToDownload.push({ + key: secret.secretKey, + value: secret.secretValue, + comment: secret.secretComment + }); + } + secretsPicked.add(secret.secretKey); + } } - downloadedSecrets.forEach((secret) => { - secretRecord[secret.key] = { - value: secret.value, - comment: secret.comment - }; - }); - - await expandSecrets(secretRecord); - - const file = downloadedSecrets + const file = secretsToDownload .sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase())) .reduce( - (prev, { key, comment }, index) => + (prev, { key, comment, value }, index) => prev + (comment - ? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${secretRecord[key].value}\n` - : `${key}=${secretRecord[key].value}\n`), + ? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n` + : `${key}=${value}\n`), "" ); @@ -221,7 +200,7 @@ export const ActionBar = ({ secretPath, workspaceId, environment, - secrets: bulkDeletedSecrets.map(({ key }) => ({ secretName: key, type: SecretType.Shared })) + secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) }); resetSelectedSecret(); handlePopUpClose("bulkDeleteSecrets"); diff --git a/frontend/src/views/SecretMainPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/views/SecretMainPage/components/CreateSecretForm/CreateSecretForm.tsx index 571de17a3..c1ce05b4c 100644 --- a/frontend/src/views/SecretMainPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/views/SecretMainPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { useCreateSecretV3 } from "@app/hooks/api"; -import { SecretType, UserWsKeyPair } from "@app/hooks/api/types"; +import { SecretType } from "@app/hooks/api/types"; import { PopUpNames, usePopUpAction, usePopUpState } from "../../SecretMainPage.store"; @@ -20,7 +20,6 @@ type TFormSchema = z.infer; type Props = { environment: string; workspaceId: string; - decryptFileKey: UserWsKeyPair; secretPath?: string; // modal props autoCapitalize?: boolean; @@ -30,7 +29,6 @@ type Props = { export const CreateSecretForm = ({ environment, workspaceId, - decryptFileKey, secretPath = "/", autoCapitalize = true, isProtectedBranch = false @@ -53,11 +51,10 @@ export const CreateSecretForm = ({ environment, workspaceId, secretPath, - secretName: key, + secretKey: key, secretValue: value || "", secretComment: "", - type: SecretType.Shared, - latestFileKey: decryptFileKey + type: SecretType.Shared }); closePopUp(PopUpNames.CreateSecretForm); reset(); diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx index 1d83f591b..547507bdf 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx @@ -32,7 +32,6 @@ import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useDebounce } from "@app/hooks"; import { useGetProjectSecrets } from "@app/hooks/api"; -import { UserWsKeyPair } from "@app/hooks/api/types"; const formSchema = z.object({ environment: z.string().trim(), @@ -54,7 +53,6 @@ type Props = { onParsedEnv: (env: Record) => void; environments?: { name: string; slug: string }[]; workspaceId: string; - decryptFileKey: UserWsKeyPair; environment: string; secretPath: string; }; @@ -62,7 +60,6 @@ type Props = { export const CopySecretsFromBoard = ({ environments = [], workspaceId, - decryptFileKey, environment, secretPath, isOpen, @@ -93,7 +90,6 @@ export const CopySecretsFromBoard = ({ workspaceId, environment: selectedEnvSlug, secretPath: debouncedEnvCopySecretPath, - decryptFileKey, options: { enabled: Boolean(workspaceId) && diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx index ca0923894..09759fba7 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx @@ -16,7 +16,7 @@ import { usePopUp, useToggle } from "@app/hooks"; import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries"; -import { DecryptedSecret, SecretType, UserWsKeyPair } from "@app/hooks/api/types"; +import { SecretType,SecretV3RawSanitized } from "@app/hooks/api/types"; import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store"; import { CopySecretsFromBoard } from "./CopySecretsFromBoard"; @@ -40,10 +40,9 @@ type Props = { isSmaller: boolean; environments?: { name: string; slug: string }[]; workspaceId: string; - decryptFileKey: UserWsKeyPair; environment: string; secretPath: string; - secrets?: DecryptedSecret[]; + secrets?: SecretV3RawSanitized[]; isProtectedBranch?: boolean; }; @@ -51,7 +50,6 @@ export const SecretDropzone = ({ isSmaller, environments = [], workspaceId, - decryptFileKey, environment, secretPath, secrets = [], @@ -165,29 +163,27 @@ export const SecretDropzone = ({ try { if (Object.keys(create || {}).length) { await createSecretBatch({ - latestFileKey: decryptFileKey!, secretPath, workspaceId, environment, - secrets: Object.entries(create).map(([secretName, secData]) => ({ + secrets: Object.entries(create).map(([secretKey, secData]) => ({ type: SecretType.Shared, secretComment: secData.comments.join("\n"), secretValue: secData.value, - secretName + secretKey })) }); } if (Object.keys(update || {}).length) { await updateSecretBatch({ - latestFileKey: decryptFileKey!, secretPath, workspaceId, environment, - secrets: Object.entries(update).map(([secretName, secData]) => ({ + secrets: Object.entries(update).map(([secretKey, secData]) => ({ type: SecretType.Shared, secretComment: secData.comments.join("\n"), secretValue: secData.value, - secretName + secretKey })) }); } @@ -281,7 +277,6 @@ export const SecretDropzone = ({ environment={environment} environments={environments} workspaceId={workspaceId} - decryptFileKey={decryptFileKey} secretPath={secretPath} isSmaller={isSmaller} /> diff --git a/frontend/src/views/SecretMainPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/views/SecretMainPage/components/SecretImportListView/SecretImportListView.tsx index aa2a3cba3..7846639d7 100644 --- a/frontend/src/views/SecretMainPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretImportListView/SecretImportListView.tsx @@ -18,7 +18,7 @@ import { usePopUp } from "@app/hooks"; import { useDeleteSecretImport, useUpdateSecretImport } from "@app/hooks/api"; import { ReservedFolders } from "@app/hooks/api/secretFolders/types"; import { TSecretImport } from "@app/hooks/api/secretImports/types"; -import { DecryptedSecret, WorkspaceEnv } from "@app/hooks/api/types"; +import { SecretV3RawSanitized, WorkspaceEnv } from "@app/hooks/api/types"; import { formatReservedPaths } from "@app/lib/fn/string"; import { SecretImportItem } from "./SecretImportItem"; @@ -29,14 +29,14 @@ type TImportedSecrets = Array<{ environmentInfo: WorkspaceEnv; secretPath: string; folderId: string; - secrets: DecryptedSecret[]; + secrets: SecretV3RawSanitized[]; }>; export const computeImportedSecretRows = ( importedSecEnv: string, importedSecPath: string, importSecrets: TImportedSecrets = [], - secrets: DecryptedSecret[] = [] + secrets: SecretV3RawSanitized[] = [] ) => { const importedSecIndex = importSecrets.findIndex( ({ secretPath, environmentInfo }) => @@ -90,7 +90,7 @@ type Props = { secretPath?: string; secretImports?: TSecretImport[]; isFetching?: boolean; - secrets?: DecryptedSecret[]; + secrets?: SecretV3RawSanitized[]; importedSecrets?: TImportedSecrets; searchTerm: string; }; diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx index 8890174cd..994f171ab 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretDetaiSidebar.tsx @@ -37,7 +37,7 @@ import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context"; import { useToggle } from "@app/hooks"; import { useGetSecretVersion } from "@app/hooks/api"; -import { DecryptedSecret, UserWsKeyPair, WsTag } from "@app/hooks/api/types"; +import { SecretV3RawSanitized, UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { CreateReminderForm } from "./CreateReminderForm"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; @@ -48,12 +48,12 @@ type Props = { secretPath: string; onToggle: (isOpen: boolean) => void; onClose: () => void; - secret: DecryptedSecret; + secret: SecretV3RawSanitized; decryptFileKey: UserWsKeyPair; onDeleteSecret: () => void; onSaveSecret: ( - orgSec: DecryptedSecret, - modSec: Omit & { tags: { id: string }[] }, + orgSec: SecretV3RawSanitized, + modSec: Omit & { tags?: { id: string }[] }, cb?: () => void ) => Promise; tags: WsTag[]; @@ -101,7 +101,7 @@ export const SecretDetailSidebar = ({ control, name: "tags" }); - const selectedTags = watch("tags", []); + const selectedTags = watch("tags", []) || []; const selectedTagsGroupById = selectedTags.reduce>( (prev, curr) => ({ ...prev, [curr.id]: true }), {} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx index 6d45d3da0..22207cb90 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretItem.tsx @@ -28,7 +28,7 @@ import { useWorkspace } from "@app/context"; import { useToggle } from "@app/hooks"; -import { DecryptedSecret } from "@app/hooks/api/secrets/types"; +import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { WsTag } from "@app/hooks/api/types"; import { subject } from "@casl/ability"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -46,14 +46,14 @@ import { } from "./SecretListView.utils"; type Props = { - secret: DecryptedSecret; + secret: SecretV3RawSanitized; onSaveSecret: ( - orgSec: DecryptedSecret, - modSec: Omit & { tags: { id: string }[] }, + orgSec: SecretV3RawSanitized, + modSec: Omit & { tags?: { id: string }[] }, cb?: () => void ) => Promise; - onDeleteSecret: (sec: DecryptedSecret) => void; - onDetailViewSecret: (sec: DecryptedSecret) => void; + onDeleteSecret: (sec: SecretV3RawSanitized) => void; + onDetailViewSecret: (sec: SecretV3RawSanitized) => void; isVisible?: boolean; isSelected?: boolean; onToggleSecretSelect: (id: string) => void; @@ -113,7 +113,7 @@ export const SecretItem = memo( const overrideAction = watch("overrideAction"); const hasComment = Boolean(watch("comment")); - const selectedTags = watch("tags", []); + const selectedTags = watch("tags", []) || []; const selectedTagsGroupById = selectedTags.reduce>( (prev, curr) => ({ ...prev, [curr.id]: true }), {} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx index 147e9d2b8..b3f0fb890 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx @@ -10,7 +10,7 @@ import { usePopUp } from "@app/hooks"; import { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "@app/hooks/api"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries"; -import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; +import { SecretType,SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; import { UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal"; @@ -22,7 +22,7 @@ import { SecretItem } from "./SecretItem"; import { FontAwesomeSpriteSymbols } from "./SecretListView.utils"; type Props = { - secrets?: DecryptedSecret[]; + secrets?: SecretV3RawSanitized[]; environment: string; workspaceId: string; decryptFileKey: UserWsKeyPair; @@ -34,8 +34,8 @@ type Props = { isProtectedBranch?: boolean; }; -const reorderSecretGroupByUnderscore = (secrets: DecryptedSecret[], sortDir: SortDir) => { - const groupedSecrets: Record = {}; +const reorderSecretGroupByUnderscore = (secrets: SecretV3RawSanitized[], sortDir: SortDir) => { + const groupedSecrets: Record = {}; secrets.forEach((secret) => { const lastSeperatorIndex = secret.key.lastIndexOf("_"); const namespace = @@ -53,7 +53,11 @@ const reorderSecretGroupByUnderscore = (secrets: DecryptedSecret[], sortDir: Sor .map((namespace) => ({ namespace, secrets: groupedSecrets[namespace] })); }; -const reorderSecret = (secrets: DecryptedSecret[], sortDir: SortDir, filter?: GroupBy | null) => { +const reorderSecret = ( + secrets: SecretV3RawSanitized[], + sortDir: SortDir, + filter?: GroupBy | null +) => { if (filter === GroupBy.PREFIX) { return reorderSecretGroupByUnderscore(secrets, sortDir); } @@ -70,12 +74,12 @@ const reorderSecret = (secrets: DecryptedSecret[], sortDir: SortDir, filter?: Gr ]; }; -export const filterSecrets = (secrets: DecryptedSecret[], filter: Filter) => +export const filterSecrets = (secrets: SecretV3RawSanitized[], filter: Filter) => secrets.filter(({ key, value, tags }) => { const isTagFilterActive = Boolean(Object.keys(filter.tags).length); const searchTerm = filter.searchFilter.toLowerCase(); return ( - (!isTagFilterActive || tags.some(({ id }) => filter.tags?.[id])) && + (!isTagFilterActive || tags?.some(({ id }) => filter.tags?.[id])) && (key.toLowerCase().includes(searchTerm) || value.toLowerCase().includes(searchTerm)) ); }); @@ -148,7 +152,7 @@ export const SecretListView = ({ environment, workspaceId, secretPath, - secretName: key, + secretKey: key, type, secretId }); @@ -160,12 +164,10 @@ export const SecretListView = ({ environment, workspaceId, secretPath, - secretName: key, - secretId, + secretKey: key, secretValue: value || "", type, - latestFileKey: decryptFileKey, - tags, + tagIds: tags, secretComment: comment, secretReminderRepeatDays: reminderRepeatDays, secretReminderNote: reminderNote, @@ -180,12 +182,11 @@ export const SecretListView = ({ environment, workspaceId, secretPath, - secretName: key, + secretKey: key, secretValue: value || "", secretComment: "", skipMultilineEncoding, - type, - latestFileKey: decryptFileKey + type }, {} ); @@ -193,8 +194,8 @@ export const SecretListView = ({ const handleSaveSecret = useCallback( async ( - orgSecret: DecryptedSecret, - modSecret: Omit & { tags: { id: string }[] }, + orgSecret: SecretV3RawSanitized, + modSecret: Omit & { tags?: { id: string }[] }, cb?: () => void ) => { const { key: oldKey } = orgSecret; @@ -288,7 +289,7 @@ export const SecretListView = ({ ); const handleSecretDelete = useCallback(async () => { - const { key, id: secretId } = popUp.deleteSecret?.data as DecryptedSecret; + const { key, id: secretId } = popUp.deleteSecret?.data as SecretV3RawSanitized; try { await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); // wrap this in another function and then reuse @@ -317,16 +318,16 @@ export const SecretListView = ({ text: "Failed to delete secret" }); } - }, [(popUp.deleteSecret?.data as DecryptedSecret)?.key, environment, secretPath]); + }, [(popUp.deleteSecret?.data as SecretV3RawSanitized)?.key, environment, secretPath]); // for optimization on minimise re-rendering of secret items const onCreateTag = useCallback(() => handlePopUpOpen("createTag"), []); const onDeleteSecret = useCallback( - (sec: DecryptedSecret) => handlePopUpOpen("deleteSecret", sec), + (sec: SecretV3RawSanitized) => handlePopUpOpen("deleteSecret", sec), [] ); const onDetailViewSecret = useCallback( - (sec: DecryptedSecret) => handlePopUpOpen("secretDetail", sec), + (sec: SecretV3RawSanitized) => handlePopUpOpen("secretDetail", sec), [] ); @@ -380,7 +381,7 @@ export const SecretListView = ({ )} handlePopUpToggle("deleteSecret", isOpen)} onDeleteApproved={handleSecretDelete} @@ -392,7 +393,7 @@ export const SecretListView = ({ isOpen={popUp.secretDetail.isOpen} onToggle={(isOpen) => handlePopUpToggle("secretDetail", isOpen)} decryptFileKey={decryptFileKey} - secret={popUp.secretDetail.data as DecryptedSecret} + secret={popUp.secretDetail.data as SecretV3RawSanitized} onDeleteSecret={() => handlePopUpOpen("deleteSecret", popUp.secretDetail.data)} onClose={() => handlePopUpClose("secretDetail")} onSaveSecret={handleSaveSecret} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts index 1e1e4c7a8..0a3d22c8e 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.utils.ts @@ -52,6 +52,7 @@ export const formSchema = z.object({ }) .array() .default([]) + .optional() }); export type TFormSchema = z.infer; diff --git a/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx b/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx index a571ca5d3..2a7397ec9 100644 --- a/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx +++ b/frontend/src/views/SecretMainPage/components/SnapshotView/SecretItem.tsx @@ -21,7 +21,7 @@ import { Tr } from "@app/components/v2"; import { useToggle } from "@app/hooks"; -import { DecryptedSecret } from "@app/hooks/api/types"; +import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; export enum TDiffModes { NoChange = "no change", @@ -32,8 +32,8 @@ export enum TDiffModes { type Props = { mode: TDiffModes; - preSecret?: DecryptedSecret; - postSecret: DecryptedSecret; + preSecret?: SecretV3RawSanitized; + postSecret: SecretV3RawSanitized; }; export type TDiffView = { mode: TDiffModes; @@ -120,9 +120,7 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => { Value {isModified && ( - + )} diff --git a/frontend/src/views/SecretMainPage/components/SnapshotView/SnapshotView.tsx b/frontend/src/views/SecretMainPage/components/SnapshotView/SnapshotView.tsx index ff95f321b..734159382 100644 --- a/frontend/src/views/SecretMainPage/components/SnapshotView/SnapshotView.tsx +++ b/frontend/src/views/SecretMainPage/components/SnapshotView/SnapshotView.tsx @@ -14,7 +14,7 @@ import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, ContentLoader, Input, Tag, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useGetSnapshotSecrets, usePerformSecretRollback } from "@app/hooks/api"; -import { DecryptedSecret, TSecretFolder, UserWsKeyPair } from "@app/hooks/api/types"; +import { SecretV3RawSanitized, TSecretFolder, UserWsKeyPair } from "@app/hooks/api/types"; import { renderIcon, SecretItem, TDiffModes, TDiffView } from "./SecretItem"; @@ -24,7 +24,7 @@ type Props = { workspaceId: string; secretPath?: string; decryptFileKey: UserWsKeyPair; - secrets?: DecryptedSecret[]; + secrets?: SecretV3RawSanitized[]; folders?: TSecretFolder[]; snapshotCount?: number; onGoBack: () => void; @@ -33,7 +33,7 @@ type Props = { const LOADER_TEXT = ["Fetching your snapshot", "Creating the difference view"]; -const deepCompareSecrets = (lhs: DecryptedSecret, rhs: DecryptedSecret) => +const deepCompareSecrets = (lhs: SecretV3RawSanitized, rhs: SecretV3RawSanitized) => lhs.key === rhs.key && lhs.value === rhs.value && lhs.comment === rhs.comment && @@ -52,7 +52,6 @@ export const SnapshotView = ({ snapshotCount, onClickListSnapshot }: Props) => { - const [search, setSearch] = useState(""); const { mutateAsync: performRollback, isLoading: isRollingBack } = usePerformSecretRollback(); @@ -92,11 +91,11 @@ export const SnapshotView = ({ }, [folders, rollingFolder]); const secretDiffView = useMemo(() => { - const secretGroupById = secrets.reduce>( + const secretGroupById = secrets.reduce>( (prev, curr) => ({ ...prev, [curr.id]: curr }), {} ); - const diffView: Array> = []; + const diffView: Array> = []; rollingSecrets.forEach((rollSecret) => { const { id } = rollSecret; const doesExist = Boolean(secretGroupById?.[id]); diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index 73cc88555..c63620cf1 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -178,8 +178,7 @@ export const SecretOverviewPage = () => { } = useGetProjectSecretsAllEnv({ workspaceId, envs: userAvailableEnvs.map(({ slug }) => slug), - secretPath, - decryptFileKey: latestFileKey! + secretPath }); const { folders, folderNames, isFolderPresentInEnv, getFolderByNameAndEnv } = useGetFoldersByEnv({ @@ -190,7 +189,6 @@ export const SecretOverviewPage = () => { const { isImportedSecretPresentInEnv, getImportedSecretByKey } = useGetImportedSecretsAllEnvs({ projectId: workspaceId, - decryptFileKey: latestFileKey!, path: secretPath, environments: userAvailableEnvs.map(({ slug }) => slug) }); @@ -317,11 +315,10 @@ export const SecretOverviewPage = () => { environment: env, workspaceId, secretPath, - secretName: key, + secretKey: key, secretValue: value, secretComment: "", - type: SecretType.Shared, - latestFileKey: latestFileKey! + type: SecretType.Shared }); createNotification({ type: "success", @@ -348,19 +345,16 @@ export const SecretOverviewPage = () => { env: string, key: string, value: string, - type = SecretType.Shared, - secretId?: string + type = SecretType.Shared ) => { try { await updateSecretV3({ environment: env, workspaceId, secretPath, - secretId, - secretName: key, + secretKey: key, secretValue: value, - type, - latestFileKey: latestFileKey! + type }); createNotification({ type: "success", @@ -381,7 +375,7 @@ export const SecretOverviewPage = () => { environment: env, workspaceId, secretPath, - secretName: key, + secretKey: key, secretId, type: SecretType.Shared }); @@ -861,7 +855,6 @@ export const SecretOverviewPage = () => { getSecretByKey={getSecretByKey} onTogglePopUp={(isOpen) => handlePopUpToggle("addSecretsInAllEnvs", isOpen)} onClose={() => handlePopUpClose("addSecretsInAllEnvs")} - decryptFileKey={latestFileKey!} /> ; type Props = { secretPath?: string; - decryptFileKey: UserWsKeyPair; - getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; + getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined; // modal props isOpen?: boolean; onClose: () => void; @@ -44,7 +43,6 @@ type Props = { export const CreateSecretForm = ({ secretPath = "/", - decryptFileKey, isOpen, getSecretByKey, onClose, @@ -101,10 +99,9 @@ export const CreateSecretForm = ({ environment, workspaceId, secretPath, - secretName: key, + secretKey: key, secretValue: value || "", - type: SecretType.Shared, - latestFileKey: decryptFileKey + type: SecretType.Shared }); } @@ -112,11 +109,10 @@ export const CreateSecretForm = ({ environment, workspaceId, secretPath, - secretName: key, + secretKey: key, secretValue: value || "", secretComment: "", - type: SecretType.Shared, - latestFileKey: decryptFileKey + type: SecretType.Shared }); }); diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx index 8cf1723cc..6c6dc9662 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx @@ -13,7 +13,7 @@ import { twMerge } from "tailwind-merge"; import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2"; import { useToggle } from "@app/hooks"; -import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; +import { SecretType,SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { WorkspaceEnv } from "@app/hooks/api/types"; import { SecretEditRow } from "./SecretEditRow"; @@ -26,7 +26,7 @@ type Props = { expandableColWidth: number; isSelected: boolean; onToggleSecretSelect: (key: string) => void; - getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; + getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined; onSecretCreate: (env: string, key: string, value: string) => Promise; onSecretUpdate: ( env: string, @@ -40,7 +40,7 @@ type Props = { getImportedSecretByKey: ( env: string, secretName: string - ) => { secret?: DecryptedSecret; environmentInfo?: WorkspaceEnv } | undefined; + ) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined; }; export const SecretOverviewTableRow = ({ diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx index e5488ab9f..4eda53b8a 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx @@ -17,15 +17,15 @@ import { useWorkspace } from "@app/context"; import { useToggle } from "@app/hooks"; -import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api"; -import { DecryptedSecret, SecretType } from "@app/hooks/api/types"; +import { useUpdateSecretV3 } from "@app/hooks/api"; +import { SecretType,SecretV3RawSanitized } from "@app/hooks/api/types"; import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils"; type Props = { secretKey: string; secretPath: string; environments: { name: string; slug: string }[]; - getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; + getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined; }; export const formSchema = z.object({ @@ -64,8 +64,6 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } ); const workspaceId = currentWorkspace?.id || ""; - const { data: decryptFileKey } = useGetUserWsKey(workspaceId); - const [isSecNameCopied, setIsSecNameCopied] = useToggle(false); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); @@ -109,12 +107,10 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } environment: secret?.env, workspaceId, secretPath, - secretName: secret.key, - secretId: secret.id, + secretKey: secret.key, secretValue: secret.value || "", type: SecretType.Shared, - latestFileKey: decryptFileKey!, - tags: secret.tags.map((tag) => tag.id), + tagIds: secret.tags?.map((tag) => tag.id), secretComment: secret.comment, secretReminderRepeatDays: secret.reminderRepeatDays, secretReminderNote: secret.reminderNote, diff --git a/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx b/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx index bddaa9f03..f44095bfc 100644 --- a/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx @@ -14,8 +14,8 @@ import { import { usePopUp } from "@app/hooks"; import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api"; import { - DecryptedSecret, SecretType, + SecretV3RawSanitized, TDeleteSecretBatchDTO, TSecretFolder } from "@app/hooks/api/types"; @@ -27,7 +27,7 @@ export enum EntryType { type Props = { secretPath: string; - getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; + getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined; getFolderByNameAndEnv: (name: string, env: string) => TSecretFolder | undefined; resetSelectedEntries: () => void; selectedEntries: { @@ -115,7 +115,7 @@ export const SelectionPanel = ({ return [ ...accum, { - secretName: entry.key, + secretKey: entry.key, type: SecretType.Shared } ]; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx index 3ddacf5b4..dab99faac 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx @@ -5,7 +5,7 @@ import { z } from "zod"; import { Button, FormControl, Input, Select, SelectItem, Spinner } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; -import { useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api"; +import { useGetProjectSecrets } from "@app/hooks/api"; const formSchema = z.object({ environment: z.string().trim(), @@ -38,12 +38,10 @@ export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Pr const secretPath = watch("secretPath"); const selectedSecrets = watch("secrets"); - const { data: userWsKey } = useGetUserWsKey(workspaceId); const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({ workspaceId, environment, - secretPath, - decryptFileKey: userWsKey! + secretPath }); return (