From 30ccfbfc8e8a269c437c80e50366ea21468503bb Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Wed, 5 Mar 2025 17:20:57 -0300 Subject: [PATCH 01/11] Add actor to secret version history --- ...1152_add-actor-id-to-secret-versions-v2.ts | 52 +++++ backend/src/db/schemas/secret-versions-v2.ts | 5 +- .../secret-approval-request-service.ts | 12 +- .../secret-replication-service.ts | 8 + .../secret-snapshot-service.ts | 15 +- backend/src/server/routes/index.ts | 4 +- backend/src/server/routes/sanitizedSchemas.ts | 10 +- .../external-migration-fns.ts | 4 + .../secret-v2-bridge/secret-v2-bridge-fns.ts | 25 ++- .../secret-v2-bridge-service.ts | 70 +++++- .../secret-v2-bridge-types.ts | 8 + backend/src/services/secret/secret-service.ts | 34 ++- backend/src/services/secret/secret-types.ts | 8 + frontend/src/hooks/api/secrets/types.ts | 5 + .../SecretListView/SecretDetailSidebar.tsx | 204 +++++++++++------- 15 files changed, 376 insertions(+), 88 deletions(-) create mode 100644 backend/src/db/migrations/20250305131152_add-actor-id-to-secret-versions-v2.ts diff --git a/backend/src/db/migrations/20250305131152_add-actor-id-to-secret-versions-v2.ts b/backend/src/db/migrations/20250305131152_add-actor-id-to-secret-versions-v2.ts new file mode 100644 index 0000000000..e4340a436b --- /dev/null +++ b/backend/src/db/migrations/20250305131152_add-actor-id-to-secret-versions-v2.ts @@ -0,0 +1,52 @@ +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; + +export async function up(knex: Knex): Promise { + const hasSecretVersionV2UserActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "userActorId"); + const hasSecretVersionV2IdentityActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "identityActorId"); + const hasSecretVersionV2ActorType = await knex.schema.hasColumn(TableName.SecretVersionV2, "actorType"); + + if (!hasSecretVersionV2UserActorId) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.uuid("userActorId"); + t.foreign("userActorId").references("id").inTable(TableName.Users); + }); + } + + if (!hasSecretVersionV2IdentityActorId) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.uuid("identityActorId"); + t.foreign("identityActorId").references("id").inTable(TableName.Identity); + }); + } + if (!hasSecretVersionV2ActorType) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.string("actorType"); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasSecretVersionV2UserActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "userActorId"); + const hasSecretVersionV2IdentityActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "identityActorId"); + const hasSecretVersionV2ActorType = await knex.schema.hasColumn(TableName.SecretVersionV2, "actorType"); + + if (hasSecretVersionV2UserActorId) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.dropColumn("userActorId"); + }); + } + + if (!hasSecretVersionV2IdentityActorId) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.dropColumn("identityActorId"); + }); + } + + if (!hasSecretVersionV2ActorType) { + await knex.schema.alterTable(TableName.SecretVersionV2, (t) => { + t.dropColumn("actorType"); + }); + } +} diff --git a/backend/src/db/schemas/secret-versions-v2.ts b/backend/src/db/schemas/secret-versions-v2.ts index 160ed1c144..593a46b068 100644 --- a/backend/src/db/schemas/secret-versions-v2.ts +++ b/backend/src/db/schemas/secret-versions-v2.ts @@ -25,7 +25,10 @@ export const SecretVersionsV2Schema = z.object({ folderId: z.string().uuid(), userId: z.string().uuid().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + userActorId: z.string().uuid().nullable().optional(), + identityActorId: z.string().uuid().nullable().optional(), + actorType: z.string().nullable().optional() }); export type TSecretVersionsV2 = z.infer; diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 17eecf5088..7f8d266c9c 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -757,7 +757,11 @@ export const secretApprovalRequestServiceFactory = ({ secretDAL, secretVersionDAL, secretTagDAL, - secretVersionTagDAL + secretVersionTagDAL, + actor: { + type: actor, + actorId + } }) : []; const updatedSecrets = secretUpdationCommits.length @@ -803,7 +807,11 @@ export const secretApprovalRequestServiceFactory = ({ secretDAL, secretVersionDAL, secretTagDAL, - secretVersionTagDAL + secretVersionTagDAL, + actor: { + type: actor, + actorId + } }) : []; const deletedSecret = secretDeletionCommits.length diff --git a/backend/src/ee/services/secret-replication/secret-replication-service.ts b/backend/src/ee/services/secret-replication/secret-replication-service.ts index 3c25db98c4..c9b0a35328 100644 --- a/backend/src/ee/services/secret-replication/secret-replication-service.ts +++ b/backend/src/ee/services/secret-replication/secret-replication-service.ts @@ -710,6 +710,10 @@ export const secretReplicationServiceFactory = ({ tx, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, inputSecrets: locallyCreatedSecrets.map((doc) => { return { keyEncoding: doc.keyEncoding, @@ -741,6 +745,10 @@ export const secretReplicationServiceFactory = ({ tx, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, inputSecrets: locallyUpdatedSecrets.map((doc) => { return { filter: { diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 1c34f6b3d4..06ad0cba53 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -370,7 +370,20 @@ export const secretSnapshotServiceFactory = ({ const secrets = await secretV2BridgeDAL.insertMany( rollbackSnaps.flatMap(({ secretVersions, folderId }) => secretVersions.map( - ({ latestSecretVersion, version, updatedAt, createdAt, secretId, envId, id, tags, ...el }) => ({ + ({ + latestSecretVersion, + version, + updatedAt, + createdAt, + secretId, + envId, + id, + tags, + userActorId, + identityActorId, + actorType, + ...el + }) => ({ ...el, id: secretId, version: deletedTopLevelSecsGroupById[secretId] ? latestSecretVersion + 1 : latestSecretVersion, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index efc1cb8655..f4b1bcc357 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1039,7 +1039,9 @@ export const registerRoutes = async ( secretApprovalRequestSecretDAL, kmsService, snapshotService, - resourceMetadataDAL + resourceMetadataDAL, + userDAL, + identityDAL }); const secretApprovalRequestService = secretApprovalRequestServiceFactory({ diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 4d645ac4be..16a7396cd3 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -111,7 +111,15 @@ export const secretRawSchema = z.object({ secretReminderRepeatDays: z.number().nullable().optional(), skipMultilineEncoding: z.boolean().default(false).nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + actor: z + .object({ + actorId: z.string().nullable(), + actorType: z.string().nullable(), + name: z.string().nullable().optional() + }) + .optional() + .nullable() }); export const ProjectPermissionSchema = z.object({ diff --git a/backend/src/services/external-migration/external-migration-fns.ts b/backend/src/services/external-migration/external-migration-fns.ts index 7446787923..f4a54f0db9 100644 --- a/backend/src/services/external-migration/external-migration-fns.ts +++ b/backend/src/services/external-migration/external-migration-fns.ts @@ -772,6 +772,10 @@ export const importDataIntoInfisicalFn = async ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }); } diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index cc40b0f268..046b23deca 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -10,6 +10,7 @@ import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-sche import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types"; +import { ActorType } from "../auth/auth-type"; const INTERPOLATION_SYNTAX_REG = /\${([a-zA-Z0-9-_.]+)}/g; // akhilmhdh: JS regex with global save state in .test @@ -62,6 +63,7 @@ export const fnSecretBulkInsert = async ({ resourceMetadataDAL, secretTagDAL, secretVersionTagDAL, + actor, tx }: TFnSecretBulkInsert) => { const sanitizedInputSecrets = inputSecrets.map( @@ -90,6 +92,9 @@ export const fnSecretBulkInsert = async ({ }) ); + const userActorId = actor && actor.type === ActorType.USER ? actor.actorId : undefined; + const identityActorId = actor && actor.type !== ActorType.USER ? actor.actorId : undefined; + const newSecrets = await secretDAL.insertMany( sanitizedInputSecrets.map((el) => ({ ...el, folderId })), tx @@ -106,6 +111,9 @@ export const fnSecretBulkInsert = async ({ sanitizedInputSecrets.map((el) => ({ ...el, folderId, + userActorId, + identityActorId, + actorType: actor?.type, secretId: newSecretGroupedByKeyName[el.key][0].id })), tx @@ -157,8 +165,12 @@ export const fnSecretBulkUpdate = async ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, - resourceMetadataDAL + resourceMetadataDAL, + actor }: TFnSecretBulkUpdate) => { + const userActorId = actor && actor?.type === ActorType.USER ? actor?.actorId : undefined; + const identityActorId = actor && actor?.type !== ActorType.USER ? actor?.actorId : undefined; + const sanitizedInputSecrets = inputSecrets.map( ({ filter, @@ -216,7 +228,10 @@ export const fnSecretBulkUpdate = async ({ encryptedValue, reminderRepeatDays, folderId, - secretId + secretId, + userActorId, + identityActorId, + actorType: actor?.type }) ), tx @@ -616,6 +631,11 @@ export const reshapeBridgeSecret = ( secret: Omit & { value: string; comment: string; + actor?: { + actorType?: string; + actorId?: string; + name?: string; + }; tags?: { id: string; slug: string; @@ -636,6 +656,7 @@ export const reshapeBridgeSecret = ( _id: secret.id, id: secret.id, user: secret.userId, + actor: secret.actor, tags: secret.tags, skipMultilineEncoding: secret.skipMultilineEncoding, secretReminderRepeatDays: secret.reminderRepeatDays, 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 0ffb0ea4cd..dd0b2d9ece 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 @@ -62,6 +62,8 @@ import { } from "./secret-v2-bridge-types"; import { TSecretVersionV2DALFactory } from "./secret-version-dal"; import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal"; +import { TUserDALFactory } from "../user/user-dal"; +import { TIdentityDALFactory } from "../identity/identity-dal"; type TSecretV2BridgeServiceFactoryDep = { secretDAL: TSecretV2BridgeDALFactory; @@ -85,6 +87,8 @@ type TSecretV2BridgeServiceFactoryDep = { >; snapshotService: Pick; resourceMetadataDAL: Pick; + userDAL: Pick; + identityDAL: Pick; }; export type TSecretV2BridgeServiceFactory = ReturnType; @@ -107,7 +111,9 @@ export const secretV2BridgeServiceFactory = ({ secretApprovalRequestDAL, secretApprovalRequestSecretDAL, kmsService, - resourceMetadataDAL + resourceMetadataDAL, + userDAL, + identityDAL }: TSecretV2BridgeServiceFactoryDep) => { const $validateSecretReferences = async ( projectId: string, @@ -301,6 +307,10 @@ export const secretV2BridgeServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -483,6 +493,10 @@ export const secretV2BridgeServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -1230,6 +1244,10 @@ export const secretV2BridgeServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -1490,6 +1508,10 @@ export const secretV2BridgeServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, resourceMetadataDAL }); updatedSecrets.push(...bulkUpdatedSecrets.map((el) => ({ ...el, secretPath: folder.path }))); @@ -1522,6 +1544,10 @@ export const secretV2BridgeServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }); updatedSecrets.push(...bulkInsertedSecrets.map((el) => ({ ...el, secretPath: folder.path }))); @@ -1690,13 +1716,39 @@ export const secretV2BridgeServiceFactory = ({ projectId: folder.projectId }); const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); - return secretVersions.map((el) => - reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", { + + const userIds = Array.from( + new Set(secretVersions.map((version) => version.userActorId).filter(Boolean)) + ) as string[]; + + const users = userIds.length > 0 ? await userDAL.find({ $in: { id: userIds } }) : []; + const usersById = groupBy(users, (user) => user.id); + + const identitiesIds = Array.from( + new Set(secretVersions.map((version) => version.identityActorId).filter(Boolean)) + ) as string[]; + const identities = identitiesIds.length > 0 ? await identityDAL.find({ $in: { id: identitiesIds } }) : []; + const identitiesById = groupBy(identities, (identity) => identity.id); + + return secretVersions.map((el) => { + let entityId; + let actorName; + if (el.userActorId) { + actorName = usersById[el.userActorId]?.[0]?.username; + entityId = el.userActorId; + } else if (el.identityActorId) { + actorName = identitiesById[el.identityActorId]?.[0]?.name; + entityId = el.identityActorId; + } + const actorEntity = el.actorType ? { actorType: el.actorType, actorId: entityId, name: actorName } : undefined; + + return reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", { ...el, + actor: actorEntity, value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "", comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : "" - }) - ); + }); + }); }; // this is a backfilling API for secret references @@ -1956,6 +2008,10 @@ export const secretV2BridgeServiceFactory = ({ secretTagDAL, resourceMetadataDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, inputSecrets: locallyCreatedSecrets.map((doc) => { return { type: doc.type, @@ -1982,6 +2038,10 @@ export const secretV2BridgeServiceFactory = ({ tx, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, inputSecrets: locallyUpdatedSecrets.map((doc) => { return { filter: { diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts index ad8264e810..22956463df 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts @@ -168,6 +168,10 @@ export type TFnSecretBulkInsert = { secretVersionDAL: Pick; secretTagDAL: Pick; secretVersionTagDAL: Pick; + actor?: { + type: string; + actorId: string; + }; }; type TRequireReferenceIfValue = @@ -192,6 +196,10 @@ export type TFnSecretBulkUpdate = { secretVersionDAL: Pick; secretTagDAL: Pick; secretVersionTagDAL: Pick; + actor?: { + type: string; + actorId: string; + }; tx?: Knex; }; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 93f68e813d..49640ac2a9 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -284,6 +284,10 @@ export const secretServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -429,6 +433,10 @@ export const secretServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -822,6 +830,10 @@ export const secretServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -931,7 +943,11 @@ export const secretServiceFactory = ({ secretDAL, secretVersionDAL, secretTagDAL, - secretVersionTagDAL + secretVersionTagDAL, + actor: { + type: actor, + actorId + } }) ); @@ -2404,6 +2420,10 @@ export const secretServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -2514,6 +2534,10 @@ export const secretServiceFactory = ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, tx }) ); @@ -2848,6 +2872,10 @@ export const secretServiceFactory = ({ secretDAL, tx, secretTagDAL, + actor: { + type: actor, + actorId + }, secretVersionTagDAL, inputSecrets: locallyCreatedSecrets.map((doc) => { return { @@ -2879,6 +2907,10 @@ export const secretServiceFactory = ({ tx, secretTagDAL, secretVersionTagDAL, + actor: { + type: actor, + actorId + }, inputSecrets: locallyUpdatedSecrets.map((doc) => { return { filter: { diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 1586052763..242296c50b 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -322,6 +322,10 @@ export type TFnSecretBulkInsert = { secretVersionDAL: Pick; secretTagDAL: Pick; secretVersionTagDAL: Pick; + actor?: { + type?: string; + actorId: string; + }; }; export type TFnSecretBulkUpdate = { @@ -336,6 +340,10 @@ export type TFnSecretBulkUpdate = { secretTagDAL: Pick; secretVersionTagDAL: Pick; tx?: Knex; + actor?: { + type?: string; + actorId: string; + }; }; export type TAttachSecretTagsDTO = { diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index 92dc220b81..92e671c8bd 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -101,6 +101,11 @@ export type SecretVersions = { skipMultilineEncoding?: boolean; createdAt: string; updatedAt: string; + actor?: { + actorId?: string, + actorType: string, + name?: string + }; }; // dto 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 168cd6f43a..04f343427f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -9,11 +9,14 @@ import { faPlus, faShare, faTag, - faTrash + faTrash, + faUser, + faDesktop, + faServer } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Link } from "@tanstack/react-router"; +import { Link, useNavigate } from "@tanstack/react-router"; import { format } from "date-fns"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; @@ -49,6 +52,8 @@ import { useGetSecretVersion } from "@app/hooks/api"; import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries"; import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; +import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { useGetWorkspaceUsers } from "@app/hooks/api"; import { CreateReminderForm } from "./CreateReminderForm"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; @@ -120,7 +125,9 @@ export const SecretDetailSidebar = ({ {} ); const selectTagSlugs = selectedTags.map((i) => i.slug); - + const navigate = useNavigate(); + const { data: members = [] } = useGetWorkspaceUsers(currentWorkspace.id); + const cannotEditSecret = permission.cannot( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { @@ -201,6 +208,41 @@ export const SecretDetailSidebar = ({ const secretReminderRepeatDays = watch("reminderRepeatDays"); const secretReminderNote = watch("reminderNote"); + const getModifiedByIcon = (userType: string) => { + switch (userType) { + case ActorType.USER: + return faUser; + case ActorType.IDENTITY: + return faDesktop; + default: + return faServer; + } + } + + const getUserMembershipId = (actorId: string) => { + return members.filter((member) => member.user?.id === actorId)?.[0].id || null; + } + + const getLinkToModifyHistoryEntity = (actorId: string, actorType: string) => { + switch(actorType) { + case ActorType.USER: + return `/${ProjectType.SecretManager}/${currentWorkspace.id}/members/${getUserMembershipId(actorId)}`; + case ActorType.IDENTITY: + return `/${ProjectType.SecretManager}/${currentWorkspace.id}/identities/${actorId}`; + default: + return null; + } + } + + const onModifyHistoryClick = (actorId: string | undefined, actorType: string) => { + if (actorId && actorType !== ActorType.PLATFORM) { + const redirectLink = getLinkToModifyHistoryEntity(actorId, actorType); + if (redirectLink) { + navigate({ to: redirectLink }); + } + } + } + return ( <>
Version History
- {secretVersion?.map(({ createdAt, secretValue, version, id }) => ( + {secretVersion?.map(({ createdAt, secretValue, version, id, actor }) => (
@@ -633,36 +675,29 @@ export const SecretDetailSidebar = ({
-
-
- Value: -
-
-
- - + + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.stopPropagation(); + e.currentTarget + .closest(".group") + ?.classList.remove("show-value"); + } + }} + > + + +
+ + {secretValue?.replace(/./g, "*")} + +
- - {secretValue?.replace(/./g, "*")} - -
From 2dda7180a9b53ef611887f721b6fda4a0fbabbe4 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Wed, 5 Mar 2025 17:36:00 -0300 Subject: [PATCH 02/11] Fix linter issue --- .../components/SecretListView/SecretDetailSidebar.tsx | 1 + 1 file changed, 1 insertion(+) 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 04f343427f..161f9cbf1c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -681,6 +681,7 @@ export const SecretDetailSidebar = ({
Modified by: + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
onModifyHistoryClick(actor.actorId, actor.actorType)} className="cursor-pointer">
From 30bcf1f20411a739e0dfa4a6d9a7bfefc92c3a60 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 6 Mar 2025 09:10:13 -0300 Subject: [PATCH 03/11] Fix linter and type issues, made a small fix for secret rotation platform events --- .../secret-rotation-queue.ts | 2 + .../secret-snapshot-service.ts | 13 +++- backend/src/server/routes/sanitizedSchemas.ts | 4 +- .../secret-v2-bridge/secret-v2-bridge-fns.ts | 6 +- backend/src/services/secret/secret-fns.ts | 19 +++++- backend/src/services/secret/secret-types.ts | 4 +- frontend/src/hooks/api/secrets/types.ts | 8 +-- .../SecretListView/SecretDetailSidebar.tsx | 63 +++++++++++++------ .../SecretListView/SecretListView.tsx | 6 +- 9 files changed, 91 insertions(+), 34 deletions(-) diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index fdc493b9fd..48af65d29f 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -39,6 +39,7 @@ import { secretRotationPreSetFn } from "./secret-rotation-queue-fn"; import { TSecretRotationData, TSecretRotationDbFn, TSecretRotationEncData } from "./secret-rotation-queue-types"; +import { ActorType } from "@app/services/auth/auth-type"; export type TSecretRotationQueueFactory = ReturnType; @@ -332,6 +333,7 @@ export const secretRotationQueueFactory = ({ await secretVersionV2BridgeDAL.insertMany( updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, + actorType: ActorType.PLATFORM, secretId: id })), tx diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 06ad0cba53..40ac4493fe 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -34,6 +34,7 @@ import { TSnapshotFolderDALFactory } from "./snapshot-folder-dal"; import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal"; import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal"; import { getFullFolderPath } from "./snapshot-service-fns"; +import { ActorType } from "@app/services/auth/auth-type"; type TSecretSnapshotServiceFactoryDep = { snapshotDAL: TSnapshotDALFactory; @@ -414,8 +415,18 @@ export const secretSnapshotServiceFactory = ({ })), tx ); + const userActorId = actor === ActorType.USER ? actorId : undefined; + const identityActorId = actor !== ActorType.USER ? actorId : undefined; + const actorType = actor || ActorType.PLATFORM; + const secretVersions = await secretVersionV2BridgeDAL.insertMany( - secrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, secretId: id })), + secrets.map(({ id, updatedAt, createdAt, ...el }) => ({ + ...el, + secretId: id, + userActorId, + identityActorId, + actorType + })), tx ); await secretVersionV2TagBridgeDAL.insertMany( diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 16a7396cd3..3009993c0b 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -114,8 +114,8 @@ export const secretRawSchema = z.object({ updatedAt: z.date(), actor: z .object({ - actorId: z.string().nullable(), - actorType: z.string().nullable(), + actorId: z.string().nullable().optional(), + actorType: z.string().nullable().optional(), name: z.string().nullable().optional() }) .optional() diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index 046b23deca..007df0872c 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -94,6 +94,7 @@ export const fnSecretBulkInsert = async ({ const userActorId = actor && actor.type === ActorType.USER ? actor.actorId : undefined; const identityActorId = actor && actor.type !== ActorType.USER ? actor.actorId : undefined; + const actorType = actor?.type || ActorType.PLATFORM; const newSecrets = await secretDAL.insertMany( sanitizedInputSecrets.map((el) => ({ ...el, folderId })), @@ -113,7 +114,7 @@ export const fnSecretBulkInsert = async ({ folderId, userActorId, identityActorId, - actorType: actor?.type, + actorType, secretId: newSecretGroupedByKeyName[el.key][0].id })), tx @@ -170,6 +171,7 @@ export const fnSecretBulkUpdate = async ({ }: TFnSecretBulkUpdate) => { const userActorId = actor && actor?.type === ActorType.USER ? actor?.actorId : undefined; const identityActorId = actor && actor?.type !== ActorType.USER ? actor?.actorId : undefined; + const actorType = actor?.type || ActorType.PLATFORM; const sanitizedInputSecrets = inputSecrets.map( ({ @@ -231,7 +233,7 @@ export const fnSecretBulkUpdate = async ({ secretId, userActorId, identityActorId, - actorType: actor?.type + actorType }) ), tx diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 1775d1f444..3ecdcbd081 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -525,6 +525,7 @@ export const fnSecretBulkInsert = async ({ secretVersionDAL, secretTagDAL, secretVersionTagDAL, + actor, tx }: TFnSecretBulkInsert) => { const sanitizedInputSecrets = inputSecrets.map( @@ -579,9 +580,17 @@ export const fnSecretBulkInsert = async ({ [`${TableName.Secret}Id` as const]: newSecretGroupByBlindIndex[secretBlindIndex as string][0].id })) ); + + const userActorId = actor && actor?.type === ActorType.USER ? actor?.actorId : undefined; + const identityActorId = actor && actor?.type !== ActorType.USER ? actor?.actorId : undefined; + const actorType = actor?.type || ActorType.PLATFORM; + const secretVersions = await secretVersionDAL.insertMany( sanitizedInputSecrets.map((el) => ({ ...el, + userActorId, + identityActorId, + actorType, secretId: newSecretGroupByBlindIndex[el.secretBlindIndex as string][0].id })), tx @@ -614,7 +623,8 @@ export const fnSecretBulkUpdate = async ({ secretDAL, secretVersionDAL, secretTagDAL, - secretVersionTagDAL + secretVersionTagDAL, + actor }: TFnSecretBulkUpdate) => { const sanitizedInputSecrets = inputSecrets.map( ({ @@ -664,10 +674,17 @@ export const fnSecretBulkUpdate = async ({ }) ); + const userActorId = actor && actor?.type === ActorType.USER ? actor?.actorId : undefined; + const identityActorId = actor && actor?.type !== ActorType.USER ? actor?.actorId : undefined; + const actorType = actor?.type || ActorType.PLATFORM; + const newSecrets = await secretDAL.bulkUpdate(sanitizedInputSecrets, tx); const secretVersions = await secretVersionDAL.insertMany( newSecrets.map(({ id, createdAt, updatedAt, ...el }) => ({ ...el, + userActorId, + identityActorId, + actorType, secretId: id })), tx diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 242296c50b..4b671b02d3 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -324,7 +324,7 @@ export type TFnSecretBulkInsert = { secretVersionTagDAL: Pick; actor?: { type?: string; - actorId: string; + actorId?: string; }; }; @@ -342,7 +342,7 @@ export type TFnSecretBulkUpdate = { tx?: Knex; actor?: { type?: string; - actorId: string; + actorId?: string; }; }; diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index 92e671c8bd..9fbef1488a 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -102,10 +102,10 @@ export type SecretVersions = { createdAt: string; updatedAt: string; actor?: { - actorId?: string, - actorType: string, - name?: string - }; + actorId?: string | null; + actorType?: string | null; + name?: string | null; + } | null; }; // dto 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 161f9cbf1c..2f1a178b96 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -48,12 +48,11 @@ import { useWorkspace } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; -import { useGetSecretVersion } from "@app/hooks/api"; +import { useGetSecretVersion, useGetWorkspaceUsers } from "@app/hooks/api"; import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries"; import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { ActorType } from "@app/hooks/api/auditLogs/enums"; -import { useGetWorkspaceUsers } from "@app/hooks/api"; import { CreateReminderForm } from "./CreateReminderForm"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; @@ -127,7 +126,7 @@ export const SecretDetailSidebar = ({ const selectTagSlugs = selectedTags.map((i) => i.slug); const navigate = useNavigate(); const { data: members = [] } = useGetWorkspaceUsers(currentWorkspace.id); - + const cannotEditSecret = permission.cannot( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { @@ -199,9 +198,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); @@ -217,14 +223,23 @@ export const SecretDetailSidebar = ({ default: return faServer; } - } + }; + + const getModifiedByName = (userType: string, userName: string | undefined) => { + switch (userType) { + case ActorType.PLATFORM: + return "System-generated"; + default: + return userName; + } + }; const getUserMembershipId = (actorId: string) => { return members.filter((member) => member.user?.id === actorId)?.[0].id || null; - } + }; const getLinkToModifyHistoryEntity = (actorId: string, actorType: string) => { - switch(actorType) { + switch (actorType) { case ActorType.USER: return `/${ProjectType.SecretManager}/${currentWorkspace.id}/members/${getUserMembershipId(actorId)}`; case ActorType.IDENTITY: @@ -232,16 +247,16 @@ export const SecretDetailSidebar = ({ default: return null; } - } + }; const onModifyHistoryClick = (actorId: string | undefined, actorType: string) => { - if (actorId && actorType !== ActorType.PLATFORM) { + if (actorId && actorType !== ActorType.PLATFORM) { const redirectLink = getLinkToModifyHistoryEntity(actorId, actorType); if (redirectLink) { navigate({ to: redirectLink }); } } - } + }; return ( <> @@ -255,7 +270,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); } }} /> @@ -675,15 +690,23 @@ export const SecretDetailSidebar = ({
-
+
{actor && (
-
- Modified by: - +
+ Modified by: + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} -
onModifyHistoryClick(actor.actorId, actor.actorType)} className="cursor-pointer"> - +
+ onModifyHistoryClick(actor.actorId, actor.actorType) + } + className="cursor-pointer" + > +
@@ -697,7 +720,7 @@ export const SecretDetailSidebar = ({