diff --git a/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts b/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts index c2cac02aa..9e567cefe 100644 --- a/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts +++ b/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts @@ -221,20 +221,25 @@ export async function down(knex: Knex): Promise { }, []); if (updatedServiceTokens.length > 0) { - await knex(TableName.ServiceToken) - .whereIn( - "id", - updatedServiceTokens.map((t) => t.id) - ) - .update({ - // @ts-expect-error -- raw query - permissions: knex.raw( - `CASE id - ${updatedServiceTokens.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")} - END`, - updatedServiceTokens.map((t) => t.permissions) + for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) { + const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE); + + // eslint-disable-next-line no-await-in-loop + await knex(TableName.ServiceToken) + .whereIn( + "id", + chunk.map((t) => t.id) ) - }); + .update({ + // @ts-expect-error -- raw query + permissions: knex.raw( + `CASE id + ${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")} + END`, + chunk.map((t) => t.permissions) + ) + }); + } } const updatedRoles = projectRoles.reduce((acc, projectRole) => { diff --git a/backend/src/ee/routes/v1/secret-version-router.ts b/backend/src/ee/routes/v1/secret-version-router.ts index 11443ebfe..a09a05c91 100644 --- a/backend/src/ee/routes/v1/secret-version-router.ts +++ b/backend/src/ee/routes/v1/secret-version-router.ts @@ -22,7 +22,11 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) => }), response: { 200: z.object({ - secretVersions: secretRawSchema.array() + secretVersions: secretRawSchema + .extend({ + secretValueHidden: z.boolean() + }) + .array() }) } }, @@ -37,6 +41,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) => offset: req.query.offset, secretId: req.params.secretId }); + return { secretVersions }; } }); diff --git a/backend/src/ee/routes/v1/snapshot-router.ts b/backend/src/ee/routes/v1/snapshot-router.ts index 64471393b..283b9b31e 100644 --- a/backend/src/ee/routes/v1/snapshot-router.ts +++ b/backend/src/ee/routes/v1/snapshot-router.ts @@ -1,10 +1,10 @@ import { z } from "zod"; -import { SecretSnapshotsSchema, SecretTagsSchema } from "@app/db/schemas"; +import { SecretSnapshotsSchema } from "@app/db/schemas"; import { PROJECTS } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { secretRawSchema } from "@app/server/routes/sanitizedSchemas"; +import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerSnapshotRouter = async (server: FastifyZodProvider) => { @@ -33,11 +33,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { .extend({ secretValueHidden: z.boolean(), secretId: z.string(), - tags: SecretTagsSchema.pick({ - id: true, - slug: true, - color: true - }).array() + tags: SanitizedTagSchema.array() }) .array(), folderVersion: z.object({ id: z.string(), name: z.string() }).array(), diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 9347d69e7..3f69c481a 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -442,7 +442,7 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [ z.object({ subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."), inverted: z.boolean().optional().describe("Whether rule allows or forbids."), - action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe( + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( "Describe what action an entity can take." ), conditions: SecretConditionV1Schema.describe( diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 533520e61..9679107ee 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -417,6 +417,8 @@ export const secretV2BridgeServiceFactory = ({ if ((inputSecret.tagIds || []).length !== newTags.length) throw new NotFoundError({ message: `Tag not found. Found ${newTags.map((el) => el.slug).join(",")}` }); + const tagsToCheck = inputSecret.tagIds ? newTags : secret.tags; + // now check with new ids ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretActions.Edit, @@ -424,7 +426,9 @@ export const secretV2BridgeServiceFactory = ({ environment, secretPath, secretName: inputSecret.secretName, - secretTags: newTags?.map((el) => el.slug) + ...(tagsToCheck.length && { + secretTags: tagsToCheck.map((el) => el.slug) + }) }) ); @@ -441,7 +445,9 @@ export const secretV2BridgeServiceFactory = ({ environment, secretPath, secretName: inputSecret.newSecretName, - secretTags: newTags?.map((el) => el.slug) + ...(tagsToCheck.length && { + secretTags: tagsToCheck.map((el) => el.slug) + }) }) ); } @@ -519,14 +525,15 @@ export const secretV2BridgeServiceFactory = ({ }); } - const tagsToCheck = newTags?.length ? newTags : secret.tags; const secretValueHidden = !permission.can( ProjectPermissionSecretActions.ReadValue, subject(ProjectPermissionSub.Secrets, { environment, secretPath, secretName: inputSecret.secretName, - secretTags: tagsToCheck.length ? tagsToCheck.map((el) => el.slug) : undefined + ...(tagsToCheck.length && { + secretTags: tagsToCheck.map((el) => el.slug) + }) }) ); @@ -1073,26 +1080,27 @@ export const secretV2BridgeServiceFactory = ({ expandSecretReferences, decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { - return ( - permission.can( - ProjectPermissionSecretActions.ReadValue, - subject(ProjectPermissionSub.Secrets, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }) - ) && - permission.can( - ProjectPermissionSecretActions.DescribeSecret, - subject(ProjectPermissionSub.Secrets, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }) - ) + const canDescribe = permission.can( + ProjectPermissionSecretActions.DescribeSecret, + subject(ProjectPermissionSub.Secrets, { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + }) ); + + const canReadValue = permission.can( + ProjectPermissionSecretActions.ReadValue, + subject(ProjectPermissionSub.Secrets, { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + }) + ); + + return viewSecretValue ? canDescribe && canReadValue : canDescribe; } }); @@ -1220,26 +1228,26 @@ export const secretV2BridgeServiceFactory = ({ decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined, hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { - return ( - permission.can( - ProjectPermissionSecretActions.ReadValue, - subject(ProjectPermissionSub.Secrets, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }) - ) && - permission.can( - ProjectPermissionSecretActions.DescribeSecret, - subject(ProjectPermissionSub.Secrets, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }) - ) + const canDescribe = permission.can( + ProjectPermissionSecretActions.DescribeSecret, + subject(ProjectPermissionSub.Secrets, { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + }) ); + const canReadValue = permission.can( + ProjectPermissionSecretActions.ReadValue, + subject(ProjectPermissionSub.Secrets, { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + }) + ); + + return viewSecretValue ? canDescribe && canReadValue : canDescribe; } }); @@ -1461,7 +1469,7 @@ export const secretV2BridgeServiceFactory = ({ environment, secretPath, secretName: el.key, - secretTags: el.tags.map((i) => i.slug) + secretTags: el.tags?.map((i) => i.slug) }) ); @@ -1970,9 +1978,24 @@ export const secretV2BridgeServiceFactory = ({ projectId: folder.projectId }); - const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); - return secretVersions.map((el) => - reshapeBridgeSecret( + const secretVersions = await secretVersionDAL.findBySecretId(secretId, { + offset, + limit, + sort: [["createdAt", "desc"]] + }); + return secretVersions.map((el) => { + const secretValueHidden = permission.cannot( + ProjectPermissionSecretActions.ReadValue, + subject(ProjectPermissionSub.Secrets, { + environment: folder.environment.envSlug, + secretPath: "/", + secretName: el.key, + ...(el.tags?.length && { + secretTags: el.tags.map((tag) => tag.slug) + }) + }) + ); + return reshapeBridgeSecret( folder.projectId, folder.environment.envSlug, "/", @@ -1981,9 +2004,9 @@ export const secretV2BridgeServiceFactory = ({ value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "", comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : "" }, - false - ) - ); + secretValueHidden + ); + }); }; // this is a backfilling API for secret references diff --git a/backend/src/services/secret-v2-bridge/secret-version-dal.ts b/backend/src/services/secret-v2-bridge/secret-version-dal.ts index 7772b8518..f974ab71e 100644 --- a/backend/src/services/secret-v2-bridge/secret-version-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-version-dal.ts @@ -1,9 +1,9 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas"; +import { SecretVersionsV2Schema, TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas"; import { BadRequestError, DatabaseError } from "@app/lib/errors"; -import { ormify, selectAllTableCols } from "@app/lib/knex"; +import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex"; import { logger } from "@app/lib/logger"; import { QueueName } from "@app/queue"; @@ -12,6 +12,58 @@ export type TSecretVersionV2DALFactory = ReturnType { const secretVersionV2Orm = ormify(db, TableName.SecretVersionV2); + const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt = {}) => { + try { + const query = (tx || db.replicaNode())(TableName.SecretVersionV2) + .where(`${TableName.SecretVersionV2}.secretId`, secretId) + .leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`) + .leftJoin( + TableName.SecretV2JnTag, + `${TableName.SecretV2}.id`, + `${TableName.SecretV2JnTag}.${TableName.SecretV2}Id` + ) + .leftJoin( + TableName.SecretTag, + `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, + `${TableName.SecretTag}.id` + ) + .select(selectAllTableCols(TableName.SecretVersionV2)) + .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) + .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); + + if (limit) void query.limit(limit); + if (offset) void query.offset(offset); + if (sort) { + void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls }))); + } + + const docs = await query; + + const data = sqlNestRelationships({ + data: docs, + key: "id", + parentMapper: (el) => ({ _id: el.id, ...SecretVersionsV2Schema.parse(el) }), + childrenMapper: [ + { + key: "tagId", + label: "tags" as const, + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ + id, + color, + slug, + name: slug + }) + } + ] + }); + + return data; + } catch (error) { + throw new DatabaseError({ error, name: `${TableName.SecretVersionV2}: FindBySecretId` }); + } + }; + // This will fetch all latest secret versions from a folder const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => { try { @@ -124,6 +176,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { pruneExcessVersions, findLatestVersionMany, bulkUpdate, - findLatestVersionByFolderId + findLatestVersionByFolderId, + findBySecretId }; }; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 5c0806041..cd8971ded 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -667,15 +667,7 @@ export const secretServiceFactory = ({ environment, secretPath: groupedPaths[secret.folderId][0].path })), - imports: importedSecrets.map((el) => { - return { - ...el, - secrets: el.secrets.map((secret) => ({ - ...secret, - secretValueHidden: false - })) - }; - }) + imports: importedSecrets }; } @@ -2399,19 +2391,42 @@ export const secretServiceFactory = ({ actionProjectType: ActionProjectType.SecretManager }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); - const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); - return secretVersions.map((el) => - decryptSecretRaw( + const secretVersions = await secretVersionDAL.findBySecretId(secretId, { + offset, + limit, + sort: [["createdAt", "desc"]] + }); + return secretVersions.map((el) => { + const secretKey = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretKeyCiphertext, + iv: secret.secretKeyIV, + tag: secret.secretKeyTag, + key: botKey + }); + + const secretValueHidden = permission.cannot( + ProjectPermissionSecretActions.ReadValue, + subject(ProjectPermissionSub.Secrets, { + environment: folder.environment.envSlug, + secretPath: "/", + secretName: secretKey, + ...(el.tags?.length && { + secretTags: el.tags.map((tag) => tag.slug) + }) + }) + ); + + return decryptSecretRaw( { - secretValueHidden: false, + secretValueHidden, ...el, workspace: folder.projectId, environment: folder.environment.envSlug, secretPath: "/" }, botKey - ) - ); + ); + }); }; const attachTags = async ({ diff --git a/backend/src/services/secret/secret-version-dal.ts b/backend/src/services/secret/secret-version-dal.ts index 8e77858a5..8e4544c19 100644 --- a/backend/src/services/secret/secret-version-dal.ts +++ b/backend/src/services/secret/secret-version-dal.ts @@ -1,9 +1,9 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas"; +import { SecretVersionsSchema, TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas"; import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; -import { ormify, selectAllTableCols } from "@app/lib/knex"; +import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex"; import { logger } from "@app/lib/logger"; import { QueueName } from "@app/queue"; @@ -12,6 +12,50 @@ export type TSecretVersionDALFactory = ReturnType { const secretVersionOrm = ormify(db, TableName.SecretVersion); + const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt = {}) => { + try { + const query = (tx || db.replicaNode())(TableName.SecretVersion) + .where(`${TableName.SecretVersion}.secretId`, secretId) + .leftJoin(TableName.Secret, `${TableName.SecretVersion}.secretId`, `${TableName.Secret}.id`) + .leftJoin(TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id`) + .leftJoin(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`) + .select(selectAllTableCols(TableName.SecretVersion)) + .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) + .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) + .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); + + if (limit) void query.limit(limit); + if (offset) void query.offset(offset); + if (sort) { + void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls }))); + } + + const docs = await query; + + const data = sqlNestRelationships({ + data: docs, + key: "id", + parentMapper: (el) => ({ _id: el.id, ...SecretVersionsSchema.parse(el) }), + childrenMapper: [ + { + key: "tagId", + label: "tags" as const, + mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({ + id, + color, + slug, + name: slug + }) + } + ] + }); + + return data; + } catch (error) { + throw new DatabaseError({ error, name: `${TableName.SecretVersion}: FindBySecretId` }); + } + }; + // This will fetch all latest secret versions from a folder const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => { try { @@ -149,6 +193,7 @@ export const secretVersionDALFactory = (db: TDbClient) => { findLatestVersionMany, bulkUpdate, findLatestVersionByFolderId, + findBySecretId, bulkUpdateNoVersionIncrement }; }; diff --git a/frontend/src/components/v2/Blur/Blur.tsx b/frontend/src/components/v2/Blur/Blur.tsx new file mode 100644 index 000000000..bd1ded40a --- /dev/null +++ b/frontend/src/components/v2/Blur/Blur.tsx @@ -0,0 +1,22 @@ +import { twMerge } from "tailwind-merge"; + +import { Tooltip } from "../Tooltip/Tooltip"; + +interface IProps { + className?: string; + tooltipText?: string; +} + +export const Blur = ({ className, tooltipText }: IProps) => { + return ( + +
+ ******** +
+
+ ); +}; diff --git a/frontend/src/components/v2/Blur/index.tsx b/frontend/src/components/v2/Blur/index.tsx new file mode 100644 index 000000000..50fb7ccc6 --- /dev/null +++ b/frontend/src/components/v2/Blur/index.tsx @@ -0,0 +1 @@ +export { Blur } from "./Blur"; diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx index 263768777..d273bd5ea 100644 --- a/frontend/src/hooks/api/secretSnapshots/queries.tsx +++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx @@ -75,7 +75,7 @@ export const useGetSnapshotSecrets = ({ snapshotId }: TSnapshotDataProps) => id: secretVersion.secretId, env: data.environment.slug, key: secretVersion.secretKey, - secretValueHidden: false, + secretValueHidden: secretVersion.secretValueHidden, value: secretVersion.secretValue || "", tags: secretVersion.tags, comment: secretVersion.secretComment, diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index e3d83a203..9a419a037 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -98,6 +98,7 @@ export type SecretVersions = { envId: string; secretKey: string; secretValue?: string; + secretValueHidden: boolean; secretComment?: string; tags: WsTag[]; __v: number; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx index 44e6648ac..c0a3ce2ef 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx @@ -25,6 +25,7 @@ import { ModalTrigger, Tooltip } from "@app/components/v2"; +import { Blur } from "@app/components/v2/Blur"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useToggle } from "@app/hooks"; @@ -143,11 +144,7 @@ export const SecretEditRow = ({
{secretValueHidden ? ( - -
- ******** -
-
+ ) : (
-
NO ACCESS
+ diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx index b2a784612..8b6650247 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -16,6 +16,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { Link } from "@tanstack/react-router"; import { format } from "date-fns"; +import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; @@ -207,9 +208,16 @@ export const SecretDetailSidebar = ({ await onSaveSecret(secret, { ...secret, ...data }, () => reset()); }; - const handleReminderSubmit = async (reminderRepeatDays: number | null | undefined, reminderNote: string | null | undefined) => { - await onSaveSecret(secret, { ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true }, () => { }); - } + const handleReminderSubmit = async ( + reminderRepeatDays: number | null | undefined, + reminderNote: string | null | undefined + ) => { + await onSaveSecret( + secret, + { ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true }, + () => {} + ); + }; const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false); @@ -228,7 +236,7 @@ export const SecretDetailSidebar = ({ if (data) { setValue("reminderRepeatDays", data.days, { shouldDirty: false }); setValue("reminderNote", data.note, { shouldDirty: false }); - handleReminderSubmit(data.days, data.note) + handleReminderSubmit(data.days, data.note); } }} /> @@ -278,6 +286,7 @@ export const SecretDetailSidebar = ({ render={({ field }) => (
@@ -651,51 +660,34 @@ export const SecretDetailSidebar = ({
Version History
- {secretVersion?.map(({ createdAt, secretValue, version, id }) => ( -
-
-
-
-
- v{version} + {secretVersion?.map( + ({ createdAt, secretValue, version, id, secretValueHidden }) => ( +
+
+
+
+
+ v{version} +
+
{format(new Date(createdAt), "Pp")}
-
{format(new Date(createdAt), "Pp")}
-
-
-
-
-
-
-
- Value: +
+
+
-
-
- - + -
- - {secretValue?.replace(/./g, "*")} - +
+ + {secretValueHidden ? "******" : secretValue?.replace(/./g, "*")} + - + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.currentTarget + .closest(".group") + ?.classList.add("show-value"); + } + }} + > + + + +
+
+ + setValue("value", secretValue)} + > + + + +
-
- - setValue("value", secretValue)} - > - - - -
-
- ))} + ) + )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx index 7d2c6fd49..0f4711a8e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx @@ -46,6 +46,7 @@ import { } from "@app/components/secrets/SecretReferenceDetails"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; +import { Blur } from "@app/components/v2/Blur"; import { FontAwesomeSpriteName, formSchema, @@ -283,15 +284,7 @@ export const SecretItem = memo( )} /> ) : secretValueHidden ? ( - -
- ******** -
-
+ ) : ( { className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent" />
-
- ******** -
+
))} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SecretItem.tsx index d6680bcef..076cf3b6d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SecretItem.tsx @@ -20,6 +20,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Blur } from "@app/components/v2/Blur"; import { useToggle } from "@app/hooks"; import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; @@ -120,11 +121,25 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => { Value {isModified && ( - + {preSecret?.secretValueHidden ? ( + + ) : ( + + )} )} - + {postSecret?.secretValueHidden ? ( + + ) : ( + + )} {Boolean(preSecret?.idOverride || postSecret?.idOverride) && (