mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-07 22:53:55 -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]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const dynamicSecretCfg = await server.services.dynamicSecret.updateByName({
|
const { dynamicSecret, updatedFields, projectId, environment, secretPath } =
|
||||||
actor: req.permission.type,
|
await server.services.dynamicSecret.updateByName({
|
||||||
actorId: req.permission.id,
|
actor: req.permission.type,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
name: req.params.name,
|
actorOrgId: req.permission.orgId,
|
||||||
path: req.body.path,
|
name: req.params.name,
|
||||||
projectSlug: req.body.projectSlug,
|
path: req.body.path,
|
||||||
environmentSlug: req.body.environmentSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
...req.body.data
|
environmentSlug: req.body.environmentSlug,
|
||||||
});
|
...req.body.data
|
||||||
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: dynamicSecretCfg.projectId,
|
projectId,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.UPDATE_DYNAMIC_SECRET,
|
type: EventType.UPDATE_DYNAMIC_SECRET,
|
||||||
metadata: {
|
metadata: {
|
||||||
dynamicSecretName: dynamicSecretCfg.name,
|
dynamicSecretName: dynamicSecret.name,
|
||||||
newDynamicSecretName: req.body.data.newName,
|
dynamicSecretType: dynamicSecret.type,
|
||||||
dynamicSecretType: dynamicSecretCfg.type,
|
dynamicSecretId: dynamicSecret.id,
|
||||||
dynamicSecretId: dynamicSecretCfg.id,
|
environment,
|
||||||
newDefaultTTL: req.body.data.defaultTTL,
|
secretPath,
|
||||||
newMaxTTL: req.body.data.maxTTL,
|
projectId,
|
||||||
newUsernameTemplate: req.body.data.usernameTemplate,
|
updatedFields
|
||||||
environment: dynamicSecretCfg.environment,
|
|
||||||
secretPath: dynamicSecretCfg.secretPath,
|
|
||||||
projectId: dynamicSecretCfg.projectId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { dynamicSecret: dynamicSecretCfg };
|
return { dynamicSecret };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,7 +426,6 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
projectId,
|
projectId,
|
||||||
|
|
||||||
leaseCount: leases.length
|
leaseCount: leases.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4594,10 +4594,7 @@ interface UpdateDynamicSecretEvent {
|
|||||||
dynamicSecretName: string;
|
dynamicSecretName: string;
|
||||||
dynamicSecretId: string;
|
dynamicSecretId: string;
|
||||||
dynamicSecretType: string;
|
dynamicSecretType: string;
|
||||||
newDynamicSecretName?: string;
|
updatedFields: string[];
|
||||||
newDefaultTTL?: string;
|
|
||||||
newMaxTTL?: string | null;
|
|
||||||
newUsernameTemplate?: string | null;
|
|
||||||
|
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { extractObjectFieldPaths } from "@app/lib/fn";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
@@ -44,6 +45,34 @@ type TDynamicSecretServiceFactoryDep = {
|
|||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
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 = ({
|
export const dynamicSecretServiceFactory = ({
|
||||||
dynamicSecretDAL,
|
dynamicSecretDAL,
|
||||||
dynamicSecretLeaseDAL,
|
dynamicSecretLeaseDAL,
|
||||||
@@ -284,8 +313,26 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
||||||
) as object;
|
) as object;
|
||||||
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
||||||
|
const oldInput = await selectedProvider.validateProviderInputs(decryptedStoredInput, { projectId });
|
||||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput, { 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 selectedGatewayId: string | null = null;
|
||||||
let isGatewayV1 = true;
|
let isGatewayV1 = true;
|
||||||
if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) {
|
if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) {
|
||||||
@@ -364,8 +411,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedDynamicCfg,
|
dynamicSecret: updatedDynamicCfg,
|
||||||
inputs: updatedInput,
|
updatedFields,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
environment: environmentSlug,
|
environment: environmentSlug,
|
||||||
secretPath: path
|
secretPath: path
|
||||||
|
|||||||
@@ -89,9 +89,13 @@ export type TDynamicSecretServiceFactory = {
|
|||||||
create: (
|
create: (
|
||||||
arg: TCreateDynamicSecretDTO
|
arg: TCreateDynamicSecretDTO
|
||||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
||||||
updateByName: (
|
updateByName: (arg: TUpdateDynamicSecretDTO) => Promise<{
|
||||||
arg: TUpdateDynamicSecretDTO
|
dynamicSecret: TDynamicSecrets;
|
||||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
updatedFields: string[];
|
||||||
|
projectId: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
}>;
|
||||||
deleteByName: (
|
deleteByName: (
|
||||||
arg: TDeleteDynamicSecretDTO
|
arg: TDeleteDynamicSecretDTO
|
||||||
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
) => Promise<TDynamicSecrets & { projectId: string; environment: string; secretPath: string }>;
|
||||||
|
|||||||
@@ -134,3 +134,67 @@ export const deterministicStringify = (value: unknown): string => {
|
|||||||
|
|
||||||
return JSON.stringify(value);
|
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