From a8973f11d8bd9d5596da7db0772f24404e734e7d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 18 Dec 2025 19:34:58 +0400 Subject: [PATCH 1/4] feat(audit-logs): dynamic secret & leases audit logs --- .../routes/v1/dynamic-secret-lease-router.ts | 149 +++++++++++--- .../src/ee/routes/v1/dynamic-secret-router.ts | 138 +++++++++++-- .../ee/services/audit-log/audit-log-types.ts | 182 +++++++++++++++++- .../dynamic-secret-lease-service.ts | 49 ++++- .../dynamic-secret-lease-types.ts | 56 +++--- .../dynamic-secret/dynamic-secret-service.ts | 70 +++++-- .../dynamic-secret/dynamic-secret-types.ts | 23 ++- .../src/server/routes/v1/dashboard-router.ts | 59 +++++- .../src/hooks/api/auditLogs/constants.tsx | 14 +- frontend/src/hooks/api/auditLogs/enums.tsx | 16 +- 10 files changed, 653 insertions(+), 103 deletions(-) diff --git a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts index 62911d5239..d6475d5351 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { DynamicSecretLeasesSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; @@ -48,14 +49,35 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { data, lease, dynamicSecret } = await server.services.dynamicSecretLease.create({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - name: req.body.dynamicSecretName, - ...req.body + const { data, lease, dynamicSecret, projectId, environment, secretPath } = + await server.services.dynamicSecretLease.create({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + name: req.body.dynamicSecretName, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.CREATE_DYNAMIC_SECRET_LEASE, + metadata: { + dynamicSecretName: dynamicSecret.name, + dynamicSecretType: dynamicSecret.type, + dynamicSecretId: dynamicSecret.id, + projectId, + environment, + secretPath, + leaseId: lease.id, + leaseExternalEntityId: lease.externalEntityId, + leaseExpireAt: lease.expireAt + } + } }); + return { lease, data, dynamicSecret }; } }); @@ -92,14 +114,36 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const lease = await server.services.dynamicSecretLease.revokeLease({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - leaseId: req.params.leaseId, - ...req.body + const { lease, dynamicSecret, projectId, environment, secretPath } = + await server.services.dynamicSecretLease.revokeLease({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + leaseId: req.params.leaseId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.DELETE_DYNAMIC_SECRET_LEASE, + metadata: { + dynamicSecretName: dynamicSecret.name, + dynamicSecretType: dynamicSecret.type, + dynamicSecretId: dynamicSecret.id, + leaseId: lease.id, + leaseExternalEntityId: lease.externalEntityId, + leaseStatus: lease.status, + environment, + secretPath, + projectId, + isForced: req.body.isForced + } + } }); + return { lease }; } }); @@ -147,14 +191,35 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const lease = await server.services.dynamicSecretLease.renewLease({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - leaseId: req.params.leaseId, - ...req.body + const { lease, dynamicSecret, projectId, environment, secretPath } = + await server.services.dynamicSecretLease.renewLease({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + leaseId: req.params.leaseId, + ...req.body + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.RENEW_DYNAMIC_SECRET_LEASE, + metadata: { + dynamicSecretName: dynamicSecret.name, + dynamicSecretType: dynamicSecret.type, + dynamicSecretId: dynamicSecret.id, + leaseId: lease.id, + leaseExternalEntityId: lease.externalEntityId, + newLeaseExpireAt: lease.expireAt, + environment, + secretPath, + projectId + } + } }); + return { lease }; } }); @@ -191,15 +256,41 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const lease = await server.services.dynamicSecretLease.getLeaseDetails({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - leaseId: req.params.leaseId, - ...req.query + const { lease, dynamicSecret, projectId, environment, secretPath } = + await server.services.dynamicSecretLease.getLeaseDetails({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + leaseId: req.params.leaseId, + ...req.query + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.GET_DYNAMIC_SECRET_LEASE, + metadata: { + dynamicSecretName: dynamicSecret.name, + dynamicSecretId: dynamicSecret.id, + dynamicSecretType: dynamicSecret.type, + leaseId: lease.id, + leaseExternalEntityId: lease.externalEntityId, + leaseExpireAt: lease.expireAt, + environment, + secretPath, + projectId + } + } }); - return { lease }; + + return { + lease: { + ...lease, + dynamicSecret + } + }; } }); }; diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index 0e48206dc8..8ec57cb0ba 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { DynamicSecretLeasesSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models"; import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs"; import { removeTrailingSlash } from "@app/lib/fn"; @@ -98,6 +99,27 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => actorOrgId: req.permission.orgId, ...req.body }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: dynamicSecretCfg.projectId, + event: { + type: EventType.CREATE_DYNAMIC_SECRET, + metadata: { + dynamicSecretName: dynamicSecretCfg.name, + dynamicSecretType: dynamicSecretCfg.type, + dynamicSecretId: dynamicSecretCfg.id, + defaultTTL: dynamicSecretCfg.defaultTTL, + maxTTL: dynamicSecretCfg.maxTTL, + gatewayV2Id: dynamicSecretCfg.gatewayV2Id, + usernameTemplate: dynamicSecretCfg.usernameTemplate, + environment: dynamicSecretCfg.environment, + secretPath: dynamicSecretCfg.secretPath, + projectId: dynamicSecretCfg.projectId + } + } + }); + return { dynamicSecret: dynamicSecretCfg }; } }); @@ -171,6 +193,26 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => environmentSlug: req.body.environmentSlug, ...req.body.data }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: dynamicSecretCfg.projectId, + event: { + type: EventType.UPDATE_DYNAMIC_SECRET, + metadata: { + dynamicSecretName: dynamicSecretCfg.name, + newDynamicSecretName: req.body.data.newName, + dynamicSecretType: dynamicSecretCfg.type, + dynamicSecretId: dynamicSecretCfg.id, + newDefaultTTL: req.body.data.defaultTTL, + newMaxTTL: req.body.data.maxTTL, + newUsernameTemplate: req.body.data.usernameTemplate, + environment: dynamicSecretCfg.environment, + secretPath: dynamicSecretCfg.secretPath, + projectId: dynamicSecretCfg.projectId + } + } + }); return { dynamicSecret: dynamicSecretCfg }; } }); @@ -209,6 +251,23 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => name: req.params.name, ...req.body }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: dynamicSecretCfg.projectId, + event: { + type: EventType.DELETE_DYNAMIC_SECRET, + metadata: { + dynamicSecretName: dynamicSecretCfg.name, + dynamicSecretType: dynamicSecretCfg.type, + dynamicSecretId: dynamicSecretCfg.id, + environment: dynamicSecretCfg.environment, + secretPath: dynamicSecretCfg.secretPath, + projectId: dynamicSecretCfg.projectId + } + } + }); + return { dynamicSecret: dynamicSecretCfg }; } }); @@ -249,6 +308,22 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => ...req.query }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: dynamicSecretCfg.projectId, + event: { + type: EventType.GET_DYNAMIC_SECRET, + metadata: { + dynamicSecretName: dynamicSecretCfg.name, + dynamicSecretType: dynamicSecretCfg.type, + dynamicSecretId: dynamicSecretCfg.id, + environment: dynamicSecretCfg.environment, + secretPath: dynamicSecretCfg.secretPath, + projectId: dynamicSecretCfg.projectId + } + } + }); + return { dynamicSecret: dynamicSecretCfg }; } }); @@ -275,14 +350,29 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const dynamicSecretCfgs = await server.services.dynamicSecret.listDynamicSecretsByEnv({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - ...req.query + const { dynamicSecrets, environment, secretPath, projectId } = + await server.services.dynamicSecret.listDynamicSecretsByEnv({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.LIST_DYNAMIC_SECRETS, + metadata: { + environment, + secretPath, + projectId + } + } }); - return { dynamicSecrets: dynamicSecretCfgs }; + + return { dynamicSecrets }; } }); @@ -316,14 +406,34 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const leases = await server.services.dynamicSecretLease.listLeases({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - name: req.params.name, - ...req.query + const { leases, dynamicSecret, projectId, environment, secretPath } = + await server.services.dynamicSecretLease.listLeases({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + name: req.params.name, + ...req.query + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.LIST_DYNAMIC_SECRET_LEASES, + metadata: { + dynamicSecretName: dynamicSecret.name, + dynamicSecretType: dynamicSecret.type, + dynamicSecretId: dynamicSecret.id, + environment, + secretPath, + projectId, + + leaseCount: leases.length + } + } }); + return { leases }; } }); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 64cd7c9360..4f4f429829 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -588,7 +588,21 @@ export enum EventType { RESPOND_TO_ACME_CHALLENGE = "respond-to-acme-challenge", PASS_ACME_CHALLENGE = "pass-acme-challenge", ATTEMPT_ACME_CHALLENGE = "attempt-acme-challenge", - FAIL_ACME_CHALLENGE = "fail-acme-challenge" + FAIL_ACME_CHALLENGE = "fail-acme-challenge", + + // Dynamic Secrets + CREATE_DYNAMIC_SECRET = "create-dynamic-secret", + UPDATE_DYNAMIC_SECRET = "update-dynamic-secret", + DELETE_DYNAMIC_SECRET = "delete-dynamic-secret", + GET_DYNAMIC_SECRET = "get-dynamic-secret", + LIST_DYNAMIC_SECRETS = "list-dynamic-secrets", + + // Dynamic Secret Leases + CREATE_DYNAMIC_SECRET_LEASE = "create-dynamic-secret-lease", + DELETE_DYNAMIC_SECRET_LEASE = "delete-dynamic-secret-lease", + RENEW_DYNAMIC_SECRET_LEASE = "renew-dynamic-secret-lease", + LIST_DYNAMIC_SECRET_LEASES = "list-dynamic-secret-leases", + GET_DYNAMIC_SECRET_LEASE = "get-dynamic-secret-lease" } export const filterableSecretEvents: EventType[] = [ @@ -4487,6 +4501,160 @@ interface FailAcmeChallengeEvent { }; } +interface GetDynamicSecretLeaseEvent { + type: EventType.GET_DYNAMIC_SECRET_LEASE; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + leaseId: string; + leaseExternalEntityId: string; + leaseExpireAt: Date; + + projectId: string; + environment: string; + secretPath: string; + }; +} + +interface RenewDynamicSecretLeaseEvent { + type: EventType.RENEW_DYNAMIC_SECRET_LEASE; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + leaseId: string; + leaseExternalEntityId: string; + newLeaseExpireAt: Date; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface CreateDynamicSecretLeaseEvent { + type: EventType.CREATE_DYNAMIC_SECRET_LEASE; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + leaseId: string; + leaseExternalEntityId: string; + leaseExpireAt: Date; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface DeleteDynamicSecretLeaseEvent { + type: EventType.DELETE_DYNAMIC_SECRET_LEASE; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + leaseId: string; + leaseExternalEntityId: string; + leaseStatus?: string | null; + + environment: string; + secretPath: string; + projectId: string; + + isForced: boolean; + }; +} + +interface CreateDynamicSecretEvent { + type: EventType.CREATE_DYNAMIC_SECRET; + metadata: { + dynamicSecretName: string; + dynamicSecretType: string; + dynamicSecretId: string; + defaultTTL: string; + maxTTL?: string | null; + gatewayV2Id?: string | null; + usernameTemplate?: string | null; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface UpdateDynamicSecretEvent { + type: EventType.UPDATE_DYNAMIC_SECRET; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + newDynamicSecretName?: string; + newDefaultTTL?: string; + newMaxTTL?: string | null; + newUsernameTemplate?: string | null; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface DeleteDynamicSecretEvent { + type: EventType.DELETE_DYNAMIC_SECRET; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface GetDynamicSecretEvent { + type: EventType.GET_DYNAMIC_SECRET; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface ListDynamicSecretsEvent { + type: EventType.LIST_DYNAMIC_SECRETS; + metadata: { + environment: string; + secretPath: string; + projectId: string; + }; +} + +interface ListDynamicSecretLeasesEvent { + type: EventType.LIST_DYNAMIC_SECRET_LEASES; + metadata: { + dynamicSecretName: string; + dynamicSecretId: string; + dynamicSecretType: string; + + environment: string; + secretPath: string; + projectId: string; + + leaseCount: number; + }; +} + export type Event = | CreateSubOrganizationEvent | UpdateSubOrganizationEvent @@ -4896,4 +5064,14 @@ export type Event = | RespondToAcmeChallengeEvent | PassedAcmeChallengeEvent | AttemptAcmeChallengeEvent - | FailAcmeChallengeEvent; + | FailAcmeChallengeEvent + | CreateDynamicSecretEvent + | UpdateDynamicSecretEvent + | DeleteDynamicSecretEvent + | GetDynamicSecretEvent + | ListDynamicSecretsEvent + | ListDynamicSecretLeasesEvent + | CreateDynamicSecretLeaseEvent + | DeleteDynamicSecretLeaseEvent + | RenewDynamicSecretLeaseEvent + | GetDynamicSecretLeaseEvent; diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index ea5efd5024..13c24ce46c 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -179,7 +179,14 @@ export const dynamicSecretLeaseServiceFactory = ({ }); await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, dynamicSecretCfg.id, expireAt); - return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data }; + return { + lease: dynamicSecretLease, + dynamicSecret: dynamicSecretCfg, + data, + projectId, + environment: environmentSlug, + secretPath: path + }; }; const renewLease: TDynamicSecretLeaseServiceFactory["renewLease"] = async ({ @@ -277,7 +284,13 @@ export const dynamicSecretLeaseServiceFactory = ({ expireAt, externalEntityId: entityId }); - return updatedDynamicSecretLease; + return { + lease: updatedDynamicSecretLease, + dynamicSecret: dynamicSecretCfg, + projectId, + environment: environmentSlug, + secretPath: path + }; }; const revokeLease: TDynamicSecretLeaseServiceFactory["revokeLease"] = async ({ @@ -364,12 +377,24 @@ export const dynamicSecretLeaseServiceFactory = ({ }); // queue a job to retry the revocation at a later time await dynamicSecretQueueService.queueFailedRevocation(dynamicSecretLease.id, dynamicSecretCfg.id); - return updatedDynamicSecretLease; + return { + lease: updatedDynamicSecretLease, + dynamicSecret: dynamicSecretCfg, + projectId, + environment: environmentSlug, + secretPath: path + }; } await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id); const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id); - return deletedDynamicSecretLease; + return { + lease: deletedDynamicSecretLease, + dynamicSecret: dynamicSecretCfg, + projectId, + environment: environmentSlug, + secretPath: path + }; }; const listLeases: TDynamicSecretLeaseServiceFactory["listLeases"] = async ({ @@ -417,7 +442,13 @@ export const dynamicSecretLeaseServiceFactory = ({ ); const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id }); - return dynamicSecretLeases; + return { + leases: dynamicSecretLeases, + dynamicSecret: dynamicSecretCfg, + projectId, + environment: environmentSlug, + secretPath: path + }; }; const getLeaseDetails: TDynamicSecretLeaseServiceFactory["getLeaseDetails"] = async ({ @@ -469,7 +500,13 @@ export const dynamicSecretLeaseServiceFactory = ({ }) ); - return dynamicSecretLease; + return { + lease: dynamicSecretLease, + dynamicSecret: dynamicSecretCfg, + projectId, + environment: environmentSlug, + secretPath: path + }; }; return { diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-types.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-types.ts index c6dfe7b165..02b16679be 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-types.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-types.ts @@ -55,34 +55,36 @@ export type TDynamicSecretLeaseServiceFactory = { lease: TDynamicSecretLeases; dynamicSecret: TDynamicSecretWithMetadata; data: unknown; + projectId: string; + environment: string; + secretPath: string; + }>; + listLeases: (arg: TListDynamicSecretLeasesDTO) => Promise<{ + leases: TDynamicSecretLeases[]; + dynamicSecret: TDynamicSecretWithMetadata; + projectId: string; + environment: string; + secretPath: string; + }>; + revokeLease: (arg: TDeleteDynamicSecretLeaseDTO) => Promise<{ + lease: TDynamicSecretLeases; + dynamicSecret: TDynamicSecretWithMetadata; + projectId: string; + environment: string; + secretPath: string; + }>; + renewLease: (arg: TRenewDynamicSecretLeaseDTO) => Promise<{ + lease: TDynamicSecretLeases; + dynamicSecret: TDynamicSecretWithMetadata; + projectId: string; + environment: string; + secretPath: string; }>; - listLeases: (arg: TListDynamicSecretLeasesDTO) => Promise; - revokeLease: (arg: TDeleteDynamicSecretLeaseDTO) => Promise; - renewLease: (arg: TRenewDynamicSecretLeaseDTO) => Promise; getLeaseDetails: (arg: TDetailsDynamicSecretLeaseDTO) => Promise<{ - dynamicSecret: { - id: string; - name: string; - version: number; - type: string; - defaultTTL: string; - maxTTL: string | null | undefined; - encryptedInput: Buffer; - folderId: string; - status: string | null | undefined; - statusDetails: string | null | undefined; - createdAt: Date; - updatedAt: Date; - }; - version: number; - id: string; - createdAt: Date; - updatedAt: Date; - externalEntityId: string; - expireAt: Date; - dynamicSecretId: string; - status?: string | null | undefined; - config?: unknown; - statusDetails?: string | null | undefined; + dynamicSecret: TDynamicSecretWithMetadata; + lease: TDynamicSecretLeases; + projectId: string; + environment: string; + secretPath: string; }>; }; diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 1372076595..600e1d2a65 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -191,7 +191,13 @@ export const dynamicSecretServiceFactory = ({ return cfg; }); - return { ...dynamicSecretCfg, inputs }; + return { + ...dynamicSecretCfg, + inputs, + projectId: project.id, + environment: environmentSlug, + secretPath: path + }; }; const updateByName: TDynamicSecretServiceFactory["updateByName"] = async ({ @@ -357,7 +363,13 @@ export const dynamicSecretServiceFactory = ({ return cfg; }); - return { ...updatedDynamicCfg, inputs: updatedInput }; + return { + ...updatedDynamicCfg, + inputs: updatedInput, + projectId: project.id, + environment: environmentSlug, + secretPath: path + }; }; const deleteByName: TDynamicSecretServiceFactory["deleteByName"] = async ({ @@ -412,7 +424,12 @@ export const dynamicSecretServiceFactory = ({ await Promise.all(leases.map(({ id: leaseId }) => dynamicSecretQueueService.unsetLeaseRevocation(leaseId))); const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id); - return deletedDynamicSecretCfg; + return { + ...deletedDynamicSecretCfg, + environment: environmentSlug, + secretPath: path, + projectId: project.id + }; } // if leases exist we should flag it as deleting and then remove leases in background // then delete the main one @@ -421,11 +438,21 @@ export const dynamicSecretServiceFactory = ({ status: DynamicSecretStatus.Deleting }); await dynamicSecretQueueService.pruneDynamicSecret(updatedDynamicSecretCfg.id); - return updatedDynamicSecretCfg; + return { + ...updatedDynamicSecretCfg, + environment: environmentSlug, + secretPath: path, + projectId: project.id + }; } // if no leases just delete the config const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id); - return deletedDynamicSecretCfg; + return { + ...deletedDynamicSecretCfg, + projectId: project.id, + environment: environmentSlug, + secretPath: path + }; }; const getDetails: TDynamicSecretServiceFactory["getDetails"] = async ({ @@ -491,7 +518,13 @@ export const dynamicSecretServiceFactory = ({ projectId })) as object; - return { ...dynamicSecretCfg, inputs: providerInputs }; + return { + ...dynamicSecretCfg, + inputs: providerInputs, + projectId: project.id, + environment: environmentSlug, + secretPath: path + }; }; // get unique dynamic secret count across multiple envs @@ -622,16 +655,21 @@ export const dynamicSecretServiceFactory = ({ } ); - return dynamicSecretCfg.filter((dynamicSecret) => { - return permission.can( - ProjectPermissionDynamicSecretActions.ReadRootCredential, - subject(ProjectPermissionSub.DynamicSecrets, { - environment: environmentSlug, - secretPath: path, - metadata: dynamicSecret.metadata - }) - ); - }); + return { + dynamicSecrets: dynamicSecretCfg.filter((dynamicSecret) => { + return permission.can( + ProjectPermissionDynamicSecretActions.ReadRootCredential, + subject(ProjectPermissionSub.DynamicSecrets, { + environment: environmentSlug, + secretPath: path, + metadata: dynamicSecret.metadata + }) + ); + }), + environment: environmentSlug, + secretPath: path, + projectId + }; }; const listDynamicSecretsByFolderIds: TDynamicSecretServiceFactory["listDynamicSecretsByFolderIds"] = async ( diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts index 0e135caef6..b8dc999f1d 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts @@ -86,11 +86,24 @@ export type TGetDynamicSecretsCountDTO = Omit Promise; - updateByName: (arg: TUpdateDynamicSecretDTO) => Promise; - deleteByName: (arg: TDeleteDynamicSecretDTO) => Promise; - getDetails: (arg: TDetailsDynamicSecretDTO) => Promise; - listDynamicSecretsByEnv: (arg: TListDynamicSecretsDTO) => Promise; + create: ( + arg: TCreateDynamicSecretDTO + ) => Promise; + updateByName: ( + arg: TUpdateDynamicSecretDTO + ) => Promise; + deleteByName: ( + arg: TDeleteDynamicSecretDTO + ) => Promise; + getDetails: ( + arg: TDetailsDynamicSecretDTO + ) => Promise; + listDynamicSecretsByEnv: (arg: TListDynamicSecretsDTO) => Promise<{ + dynamicSecrets: Array; + environment: string; + secretPath: string; + projectId: string; + }>; listDynamicSecretsByEnvs: ( arg: TListDynamicSecretsMultiEnvDTO ) => Promise>; diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 7dc763730f..4287150f73 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -359,6 +359,21 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { // get the count of unique dynamic secret names to properly adjust remaining limit const uniqueDynamicSecretsCount = new Set(dynamicSecrets.map((dynamicSecret) => dynamicSecret.name)).size; + if (dynamicSecrets.length) { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.LIST_DYNAMIC_SECRETS, + metadata: { + environment: [...new Set(dynamicSecrets.map((dynamicSecret) => dynamicSecret.environment))].join(","), + secretPath, + projectId + } + } + }); + } + remainingLimit -= uniqueDynamicSecretsCount; adjustedOffset = 0; } else { @@ -738,7 +753,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { reminder: Awaited>[string] | null; })[] | undefined; - let dynamicSecrets: Awaited> | undefined; + let dynamicSecrets: + | Awaited>["dynamicSecrets"] + | undefined; let secretRotations: | (Awaited>[number] & { secrets: (NonNullable< @@ -923,7 +940,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { }); if (remainingLimit > 0 && totalDynamicSecretCount > adjustedOffset) { - dynamicSecrets = await server.services.dynamicSecret.listDynamicSecretsByEnv({ + const { dynamicSecrets: dynamicSecretCfgs } = await server.services.dynamicSecret.listDynamicSecretsByEnv({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -938,6 +955,23 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { offset: adjustedOffset }); + if (dynamicSecretCfgs.length) { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId, + event: { + type: EventType.LIST_DYNAMIC_SECRETS, + metadata: { + environment, + secretPath, + projectId + } + } + }); + } + + dynamicSecrets = dynamicSecretCfgs; + remainingLimit -= dynamicSecrets.length; adjustedOffset = 0; } else { @@ -1263,6 +1297,27 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { const sliceQuickSearch = (array: T[]) => array.slice(0, 25); + const filteredDynamicSecrets = sliceQuickSearch( + searchPath ? dynamicSecrets.filter((dynamicSecret) => dynamicSecret.path.endsWith(searchPath)) : dynamicSecrets + ); + + if (filteredDynamicSecrets?.length) { + await server.services.auditLog.createAuditLog({ + projectId, + ...req.auditLogInfo, + event: { + type: EventType.LIST_DYNAMIC_SECRETS, + metadata: { + environment: [...new Set(filteredDynamicSecrets.map((dynamicSecret) => dynamicSecret.environment))].join( + "," + ), + secretPath: [...new Set(filteredDynamicSecrets.map((dynamicSecret) => dynamicSecret.path))].join(","), + projectId + } + } + }); + } + return { secrets: sliceQuickSearch( searchPath ? secrets.filter((secret) => secret.secretPath.endsWith(searchPath)) : secrets diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index 3f1ca66982..888bb05f30 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -306,7 +306,19 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.APPROVAL_REQUEST_CANCEL]: "Cancel Approval Request", [EventType.APPROVAL_REQUEST_GRANT_LIST]: "List Approval Request Grants", [EventType.APPROVAL_REQUEST_GRANT_GET]: "Get Approval Request Grant", - [EventType.APPROVAL_REQUEST_GRANT_REVOKE]: "Revoke Approval Request Grant" + [EventType.APPROVAL_REQUEST_GRANT_REVOKE]: "Revoke Approval Request Grant", + + [EventType.CREATE_DYNAMIC_SECRET]: "Create Dynamic Secret", + [EventType.UPDATE_DYNAMIC_SECRET]: "Update Dynamic Secret", + [EventType.DELETE_DYNAMIC_SECRET]: "Delete Dynamic Secret", + [EventType.GET_DYNAMIC_SECRET]: "Get Dynamic Secret", + [EventType.LIST_DYNAMIC_SECRETS]: "List Dynamic Secrets", + + [EventType.CREATE_DYNAMIC_SECRET_LEASE]: "Create Dynamic Secret Lease", + [EventType.DELETE_DYNAMIC_SECRET_LEASE]: "Delete Dynamic Secret Lease", + [EventType.RENEW_DYNAMIC_SECRET_LEASE]: "Renew Dynamic Secret Lease", + [EventType.LIST_DYNAMIC_SECRET_LEASES]: "List Dynamic Secret Leases", + [EventType.GET_DYNAMIC_SECRET_LEASE]: "Get Dynamic Secret Lease" }; export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = { diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 72b57d1598..d83fc5e28b 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -299,5 +299,19 @@ export enum EventType { APPROVAL_REQUEST_CANCEL = "approval-request-cancel", APPROVAL_REQUEST_GRANT_LIST = "approval-request-grant-list", APPROVAL_REQUEST_GRANT_GET = "approval-request-grant-get", - APPROVAL_REQUEST_GRANT_REVOKE = "approval-request-grant-revoke" + APPROVAL_REQUEST_GRANT_REVOKE = "approval-request-grant-revoke", + + // Dynamic Secrets + CREATE_DYNAMIC_SECRET = "create-dynamic-secret", + UPDATE_DYNAMIC_SECRET = "update-dynamic-secret", + DELETE_DYNAMIC_SECRET = "delete-dynamic-secret", + GET_DYNAMIC_SECRET = "get-dynamic-secret", + LIST_DYNAMIC_SECRETS = "list-dynamic-secrets", + + // Dynamic Secret Leases + CREATE_DYNAMIC_SECRET_LEASE = "create-dynamic-secret-lease", + DELETE_DYNAMIC_SECRET_LEASE = "delete-dynamic-secret-lease", + RENEW_DYNAMIC_SECRET_LEASE = "renew-dynamic-secret-lease", + LIST_DYNAMIC_SECRET_LEASES = "list-dynamic-secret-leases", + GET_DYNAMIC_SECRET_LEASE = "get-dynamic-secret-lease" } From be46917348bbcb55253abc5b842c90fac746f3ff Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 18 Dec 2025 19:38:08 +0400 Subject: [PATCH 2/4] Update package-lock.json --- frontend/package-lock.json | 124 ++++++++++++++----------------------- 1 file changed, 47 insertions(+), 77 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bbb72bfa7c..84755ab94f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -152,8 +152,7 @@ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -198,6 +197,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -490,6 +490,7 @@ "resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.7.2.tgz", "integrity": "sha512-KjKXlcjKbUz8dKw7PY56F7qlfOFgxTU6tnlJ8YrbDyWkJMIlHa6VRWzCD8RU20zbJUC1hExhOFggZjm6tf1mUw==", "license": "MIT", + "peer": true, "dependencies": { "@ucast/mongo2js": "^1.3.0" }, @@ -548,6 +549,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1325,6 +1327,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.1.tgz", "integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.1" }, @@ -2015,6 +2018,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", @@ -4489,6 +4493,7 @@ "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/utils": "^8.13.0", "eslint-visitor-keys": "^4.2.0", @@ -5114,6 +5119,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.95.1.tgz", "integrity": "sha512-P5x4yNhcdkYsCEoYeGZP8Q9Jlxf0WXJa4G/xvbmM905seZc9FqJqvCSRvX3dWTPOXRABhl4g+8DHqfft0c/AvQ==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.95.0", "@tanstack/react-store": "^0.7.0", @@ -5325,7 +5331,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5346,7 +5351,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -5357,7 +5361,6 @@ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", @@ -5377,8 +5380,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@testing-library/user-event": { "version": "14.6.1", @@ -5386,7 +5388,6 @@ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12", "npm": ">=6" @@ -5407,8 +5408,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5461,7 +5461,6 @@ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" @@ -5535,8 +5534,7 @@ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/doctrine": { "version": "0.0.9", @@ -5672,6 +5670,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz", "integrity": "sha512-oh8AMIC4Y2ciKufU8hnKgs+ufgbA/dhPTACaZPM86AbwX9QwnFtSoPWEeRUj8fge+v6kFt78BXcDhAU1SrrAsw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5683,6 +5682,7 @@ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5730,6 +5730,7 @@ "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.0", @@ -5770,6 +5771,7 @@ "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", @@ -6041,7 +6043,6 @@ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", @@ -6059,7 +6060,6 @@ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", @@ -6087,7 +6087,6 @@ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -6098,7 +6097,6 @@ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -6112,7 +6110,6 @@ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tinyspy": "^4.0.3" }, @@ -6126,7 +6123,6 @@ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", @@ -6200,6 +6196,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6316,7 +6313,6 @@ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -6365,7 +6361,6 @@ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6446,7 +6441,6 @@ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6534,7 +6528,6 @@ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" } @@ -6545,7 +6538,6 @@ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.0.1" }, @@ -6558,8 +6550,7 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", @@ -6609,7 +6600,6 @@ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -6711,7 +6701,6 @@ "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "open": "^8.0.4" }, @@ -6948,6 +6937,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -7129,7 +7119,6 @@ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -7200,7 +7189,6 @@ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 16" } @@ -7586,8 +7574,7 @@ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cssesc": { "version": "3.0.0", @@ -7606,7 +7593,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cva": { "name": "class-variance-authority", @@ -7678,6 +7666,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7731,8 +7720,7 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -7842,7 +7830,6 @@ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7877,7 +7864,6 @@ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7999,8 +7985,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -8210,7 +8195,6 @@ "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -8294,6 +8278,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8335,7 +8320,6 @@ "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.3.4" }, @@ -8372,6 +8356,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8428,6 +8413,7 @@ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0", "object.assign": "^4.1.2", @@ -8450,6 +8436,7 @@ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -8480,6 +8467,7 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8579,6 +8567,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -8708,7 +8697,6 @@ "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -8742,6 +8730,7 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8765,7 +8754,6 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8779,7 +8767,6 @@ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -8798,7 +8785,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8960,7 +8946,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -9991,6 +9976,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" }, @@ -10084,7 +10070,6 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -10361,7 +10346,6 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "is-docker": "cli.js" }, @@ -10683,7 +10667,6 @@ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -10719,7 +10702,6 @@ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "license": "MIT", - "peer": true, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -10731,7 +10713,6 @@ "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -10881,7 +10862,6 @@ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -10916,8 +10896,7 @@ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", "dev": true, - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/language-tags": { "version": "1.0.9", @@ -10925,7 +10904,6 @@ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "language-subtag-registry": "^0.3.20" }, @@ -10958,7 +10936,6 @@ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.102.tgz", "integrity": "sha512-g70kydI0I1sZU0ChO8mBbhw0oUW/8U0GHzygpvEIx8k+jgOpqnTSb/E+70toYVqHxBhrERD21TwD5QcZJQ40ZQ==", "license": "MIT", - "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -11283,8 +11260,7 @@ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -11311,7 +11287,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -12003,7 +11978,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -12352,7 +12326,6 @@ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -12622,7 +12595,6 @@ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 14.16" } @@ -12774,6 +12746,7 @@ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12890,7 +12863,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12906,7 +12878,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -12919,8 +12890,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prismjs": { "version": "1.30.0", @@ -13121,6 +13091,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -13148,6 +13119,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13239,6 +13211,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -13289,6 +13262,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.3.tgz", "integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -13539,7 +13513,6 @@ "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", @@ -13557,7 +13530,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13574,7 +13546,6 @@ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -13589,7 +13560,6 @@ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "min-indent": "^1.0.0" }, @@ -13804,6 +13774,7 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -14314,7 +14285,6 @@ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14330,7 +14300,6 @@ "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14358,7 +14327,6 @@ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -14579,7 +14547,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.2.1", @@ -14683,7 +14652,6 @@ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14694,7 +14662,6 @@ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14989,6 +14956,7 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15374,6 +15342,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -15791,7 +15760,6 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15836,6 +15804,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -15986,6 +15955,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 310f1ee8f9d1ee9715e18cb024f06cd9898435d4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 18 Dec 2025 19:45:42 +0400 Subject: [PATCH 3/4] Update validate-pr-title.yml --- .github/workflows/validate-pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml index 1e590139c4..20ba0b3155 100644 --- a/.github/workflows/validate-pr-title.yml +++ b/.github/workflows/validate-pr-title.yml @@ -16,7 +16,7 @@ jobs: const title = context.payload.pull_request.title; // Valid PR types based on pull_request_template.md - const validTypes = ['fix', 'feature', 'improvement', 'breaking', 'docs', 'chore']; + const validTypes = ['fix', 'feature', 'improvement', 'breaking', 'docs', 'chore', 'feat']; // Regex pattern: type(optional-scope): short description // - Type must be one of the valid types From 24ecf916c006c263e49bdebd7da3404fa63d7bb9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 18 Dec 2025 23:32:18 +0400 Subject: [PATCH 4/4] requested changes --- .../src/ee/routes/v1/dynamic-secret-router.ts | 45 ++++++------- .../ee/services/audit-log/audit-log-types.ts | 5 +- .../dynamic-secret/dynamic-secret-service.ts | 51 ++++++++++++++- .../dynamic-secret/dynamic-secret-types.ts | 10 ++- backend/src/lib/fn/object.ts | 64 +++++++++++++++++++ 5 files changed, 142 insertions(+), 33 deletions(-) diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index 8ec57cb0ba..f0ce32270c 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -182,38 +182,36 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const dynamicSecretCfg = await server.services.dynamicSecret.updateByName({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - name: req.params.name, - path: req.body.path, - projectSlug: req.body.projectSlug, - environmentSlug: req.body.environmentSlug, - ...req.body.data - }); + const { dynamicSecret, updatedFields, projectId, environment, secretPath } = + await server.services.dynamicSecret.updateByName({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + name: req.params.name, + path: req.body.path, + projectSlug: req.body.projectSlug, + environmentSlug: req.body.environmentSlug, + ...req.body.data + }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - projectId: dynamicSecretCfg.projectId, + projectId, event: { type: EventType.UPDATE_DYNAMIC_SECRET, metadata: { - dynamicSecretName: dynamicSecretCfg.name, - newDynamicSecretName: req.body.data.newName, - dynamicSecretType: dynamicSecretCfg.type, - dynamicSecretId: dynamicSecretCfg.id, - newDefaultTTL: req.body.data.defaultTTL, - newMaxTTL: req.body.data.maxTTL, - newUsernameTemplate: req.body.data.usernameTemplate, - environment: dynamicSecretCfg.environment, - secretPath: dynamicSecretCfg.secretPath, - projectId: dynamicSecretCfg.projectId + dynamicSecretName: dynamicSecret.name, + dynamicSecretType: dynamicSecret.type, + dynamicSecretId: dynamicSecret.id, + environment, + secretPath, + projectId, + updatedFields } } }); - return { dynamicSecret: dynamicSecretCfg }; + return { dynamicSecret }; } }); @@ -428,7 +426,6 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => environment, secretPath, projectId, - leaseCount: leases.length } } diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 4f4f429829..8d82d6f1ce 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -4594,10 +4594,7 @@ interface UpdateDynamicSecretEvent { dynamicSecretName: string; dynamicSecretId: string; dynamicSecretType: string; - newDynamicSecretName?: string; - newDefaultTTL?: string; - newMaxTTL?: string | null; - newUsernameTemplate?: string | null; + updatedFields: string[]; environment: string; secretPath: string; diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 600e1d2a65..4da278c090 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -9,6 +9,7 @@ import { } from "@app/ee/services/permission/project-permission"; import { crypto } from "@app/lib/crypto"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { extractObjectFieldPaths } from "@app/lib/fn"; import { OrderByDirection } from "@app/lib/types"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -44,6 +45,34 @@ type TDynamicSecretServiceFactoryDep = { resourceMetadataDAL: Pick; }; +const getUpdatedFieldPaths = ( + oldData: Record | null | undefined, + newData: Record | null | undefined +): string[] => { + const updatedPaths = new Set(); + + if (!newData || typeof newData !== "object") { + return []; + } + + if (!oldData || typeof oldData !== "object") { + return []; + } + + Object.keys(newData).forEach((key) => { + const oldValue = oldData?.[key]; + const newValue = newData[key]; + + if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) { + // Extract paths from the new value + const paths = extractObjectFieldPaths(newValue, key); + paths.forEach((path) => updatedPaths.add(path)); + } + }); + + return Array.from(updatedPaths).sort(); +}; + export const dynamicSecretServiceFactory = ({ dynamicSecretDAL, dynamicSecretLeaseDAL, @@ -284,8 +313,26 @@ export const dynamicSecretServiceFactory = ({ secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; const newInput = { ...decryptedStoredInput, ...(inputs || {}) }; + const oldInput = await selectedProvider.validateProviderInputs(decryptedStoredInput, { projectId }); const updatedInput = await selectedProvider.validateProviderInputs(newInput, { projectId }); + const updatedFields = getUpdatedFieldPaths( + { + ...(oldInput as object), + maxTTL: dynamicSecretCfg.maxTTL, + defaultTTL: dynamicSecretCfg.defaultTTL, + name: dynamicSecretCfg.name, + usernameTemplate + }, + { + ...(updatedInput as object), + maxTTL, + defaultTTL, + name: newName ?? name, + usernameTemplate + } + ); + let selectedGatewayId: string | null = null; let isGatewayV1 = true; if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) { @@ -364,8 +411,8 @@ export const dynamicSecretServiceFactory = ({ }); return { - ...updatedDynamicCfg, - inputs: updatedInput, + dynamicSecret: updatedDynamicCfg, + updatedFields, projectId: project.id, environment: environmentSlug, secretPath: path diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts index b8dc999f1d..d5fa291b5a 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-types.ts @@ -89,9 +89,13 @@ export type TDynamicSecretServiceFactory = { create: ( arg: TCreateDynamicSecretDTO ) => Promise; - updateByName: ( - arg: TUpdateDynamicSecretDTO - ) => Promise; + updateByName: (arg: TUpdateDynamicSecretDTO) => Promise<{ + dynamicSecret: TDynamicSecrets; + updatedFields: string[]; + projectId: string; + environment: string; + secretPath: string; + }>; deleteByName: ( arg: TDeleteDynamicSecretDTO ) => Promise; diff --git a/backend/src/lib/fn/object.ts b/backend/src/lib/fn/object.ts index c437e484aa..c40e5c53c0 100644 --- a/backend/src/lib/fn/object.ts +++ b/backend/src/lib/fn/object.ts @@ -134,3 +134,67 @@ export const deterministicStringify = (value: unknown): string => { return JSON.stringify(value); }; + +/** + * Recursively extracts all field paths from a nested object structure. + * Returns an array of dot-notation paths (e.g., ["password", "username", "field.nestedField"]) + */ +export const extractObjectFieldPaths = (obj: unknown, prefix = ""): string[] => { + const paths: string[] = []; + + if (obj === null || obj === undefined) { + return paths; + } + + if (typeof obj !== "object") { + // return the path if it exists + if (prefix) { + paths.push(prefix); + } + return paths; + } + + if (Array.isArray(obj)) { + // for arrays, we log the array itself and optionally nested paths + if (prefix) { + paths.push(prefix); + } + // we just want to know the array field changed + obj.forEach((item, index) => { + if (typeof item === "object" && item !== null) { + const nestedPaths = extractObjectFieldPaths(item, `${prefix}[${index}]`); + paths.push(...nestedPaths); + } + }); + return paths; + } + + // for objects, extract all keys and recurse + const keys = Object.keys(obj); + if (keys.length === 0 && prefix) { + // empty object with prefix + paths.push(prefix); + } + + keys.forEach((key) => { + const currentPath = prefix ? `${prefix}.${key}` : key; + const value = (obj as Record)[key]; + + if (value === null || value === undefined) { + paths.push(currentPath); + } else if (typeof value === "object") { + // recurse into nested objects/arrays + const nestedPaths = extractObjectFieldPaths(value, currentPath); + if (nestedPaths.length === 0) { + // if nested object is empty, add the path itself + paths.push(currentPath); + } else { + paths.push(...nestedPaths); + } + } else { + paths.push(currentPath); + } + }); + + return paths; +};