mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-06 22:23:53 -05:00
requested changes
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
};
|
||||
|
||||
const getUpdatedFieldPaths = (
|
||||
oldData: Record<string, unknown> | null | undefined,
|
||||
newData: Record<string, unknown> | null | undefined
|
||||
): string[] => {
|
||||
const updatedPaths = new Set<string>();
|
||||
|
||||
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
|
||||
|
||||
@@ -89,9 +89,13 @@ export type TDynamicSecretServiceFactory = {
|
||||
create: (
|
||||
arg: TCreateDynamicSecretDTO
|
||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
||||
updateByName: (
|
||||
arg: TUpdateDynamicSecretDTO
|
||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
||||
updateByName: (arg: TUpdateDynamicSecretDTO) => Promise<{
|
||||
dynamicSecret: TDynamicSecrets;
|
||||
updatedFields: string[];
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}>;
|
||||
deleteByName: (
|
||||
arg: TDeleteDynamicSecretDTO
|
||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
||||
|
||||
@@ -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<string, unknown>)[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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user