Merge pull request #4574 from Infisical/secret-update-request-fixes

fix(secret-change-requests): various UPDATE change request fixes
This commit is contained in:
Scott Wilson
2025-09-22 18:00:27 -07:00
committed by GitHub
8 changed files with 93 additions and 25 deletions
@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretApprovalRequestSecretV2)) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.boolean("skipMultilineEncoding").alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretApprovalRequestSecretV2)) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.boolean("skipMultilineEncoding").defaultTo(false).alter();
});
}
}
@@ -17,7 +17,7 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
encryptedComment: zodBuffer.nullable().optional(), encryptedComment: zodBuffer.nullable().optional(),
reminderNote: z.string().nullable().optional(), reminderNote: z.string().nullable().optional(),
reminderRepeatDays: z.number().nullable().optional(), reminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(), skipMultilineEncoding: z.boolean().nullable().optional(),
metadata: z.unknown().nullable().optional(), metadata: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
@@ -320,10 +320,20 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.array(), .array(),
secretPath: z.string(), secretPath: z.string(),
commits: secretRawSchema commits: secretRawSchema
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true }) .omit({
_id: true,
environment: true,
workspace: true,
type: true,
version: true,
secretValue: true,
secretComment: true
})
.extend({ .extend({
secretValueHidden: z.boolean(), secretValueHidden: z.boolean(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
secretComment: z.string().optional(),
skipMultilineEncoding: z.boolean().nullish(),
isRotatedSecret: z.boolean().optional(), isRotatedSecret: z.boolean().optional(),
op: z.string(), op: z.string(),
tags: SanitizedTagSchema.array().optional(), tags: SanitizedTagSchema.array().optional(),
@@ -348,7 +358,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretValueHidden: z.boolean(), secretValueHidden: z.boolean(),
secretComment: z.string().optional(), secretComment: z.string().optional(),
tags: SanitizedTagSchema.array().optional(), tags: SanitizedTagSchema.array().optional(),
secretMetadata: ResourceMetadataSchema.nullish() secretMetadata: ResourceMetadataSchema.nullish(),
skipMultilineEncoding: z.boolean().nullish()
}) })
.optional() .optional()
}) })
@@ -284,7 +284,8 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("version").withSchema(TableName.SecretVersionV2).as("secVerVersion"), db.ref("version").withSchema(TableName.SecretVersionV2).as("secVerVersion"),
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"), db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"), db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment") db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment"),
db.ref("skipMultilineEncoding").withSchema(TableName.SecretVersionV2).as("secVerSkipMultilineEncoding")
) )
.select( .select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"), db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
@@ -326,14 +327,22 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
{ {
key: "secretVersion", key: "secretVersion",
label: "secretVersion" as const, label: "secretVersion" as const,
mapper: ({ secretVersion, secVerVersion, secVerKey, secVerValue, secVerComment }) => mapper: ({
secretVersion,
secVerVersion,
secVerKey,
secVerValue,
secVerComment,
secVerSkipMultilineEncoding
}) =>
secretVersion secretVersion
? { ? {
version: secVerVersion, version: secVerVersion,
id: secretVersion, id: secretVersion,
key: secVerKey, key: secVerKey,
encryptedValue: secVerValue, encryptedValue: secVerValue,
encryptedComment: secVerComment encryptedComment: secVerComment,
skipMultilineEncoding: secVerSkipMultilineEncoding
} }
: undefined, : undefined,
childrenMapper: [ childrenMapper: [
@@ -337,12 +337,17 @@ export const secretApprovalRequestServiceFactory = ({
? INFISICAL_SECRET_VALUE_HIDDEN_MASK ? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret && el.secret.isRotatedSecret : el.secret && el.secret.isRotatedSecret
? undefined ? undefined
: el.encryptedValue : el.encryptedValue !== undefined && el.encryptedValue !== null
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: "", : undefined,
secretComment: el.encryptedComment secretComment:
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() el.encryptedComment !== undefined && el.encryptedComment !== null
: "", ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
: undefined,
skipMultilineEncoding:
el.skipMultilineEncoding !== undefined && el.skipMultilineEncoding !== null
? el.skipMultilineEncoding
: undefined,
secret: el.secret secret: el.secret
? { ? {
secretKey: el.secret.key, secretKey: el.secret.key,
@@ -394,7 +399,8 @@ export const secretApprovalRequestServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "", : "",
tags: el.secretVersion.tags, tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO,
skipMultilineEncoding: el.secretVersion.skipMultilineEncoding
} }
: undefined : undefined
})); }));
@@ -733,9 +739,9 @@ export const secretApprovalRequestServiceFactory = ({
tx, tx,
inputSecrets: secretUpdationCommits.map((el) => { inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue = const encryptedValue =
!el.secret?.isRotatedSecret && typeof el.encryptedValue !== "undefined" !el.secret?.isRotatedSecret && el.encryptedValue !== null && el.encryptedValue !== undefined
? { ? {
encryptedValue: el.encryptedValue as Buffer, encryptedValue: el.encryptedValue,
references: el.encryptedValue references: el.encryptedValue
? getAllSecretReferencesV2Bridge( ? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({ secretManagerDecryptor({
@@ -749,9 +755,9 @@ export const secretApprovalRequestServiceFactory = ({
filter: { id: el.secretId as string, type: SecretType.Shared }, filter: { id: el.secretId as string, type: SecretType.Shared },
data: { data: {
reminderRepeatDays: el.reminderRepeatDays, reminderRepeatDays: el.reminderRepeatDays,
encryptedComment: el.encryptedComment, encryptedComment: el.encryptedComment !== null ? el.encryptedComment : undefined,
reminderNote: el.reminderNote, reminderNote: el.reminderNote,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding !== null ? el.skipMultilineEncoding : undefined,
key: el.key, key: el.key,
tags: el?.tags.map(({ id }) => id), tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO, secretMetadata: el.secretMetadata as ResourceMetadataDTO,
@@ -1633,11 +1639,13 @@ export const secretApprovalRequestServiceFactory = ({
key: newSecretName || secretKey, key: newSecretName || secretKey,
encryptedComment: setKnexStringValue( encryptedComment: setKnexStringValue(
secretComment, secretComment,
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob (value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob,
true // scott: we need to encrypt empty string on update to differentiate not updating comment vs clearing comment
), ),
encryptedValue: setKnexStringValue( encryptedValue: setKnexStringValue(
secretValue, secretValue,
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob (value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob,
true // scott: we need to encrypt empty string on update to differentiate not updating value vs clearing value
), ),
reminderRepeatDays, reminderRepeatDays,
reminderNote, reminderNote,
+6 -2
View File
@@ -16,8 +16,12 @@ export const stripUndefinedInWhere = <T extends object>(val: T): Exclude<T, unde
// if its undefined its skipped in knex // if its undefined its skipped in knex
// if its empty string its set as null // if its empty string its set as null
// else pass to the required one // else pass to the required one
export const setKnexStringValue = <T>(value: string | null | undefined, cb: (arg: string) => T) => { export const setKnexStringValue = <T>(
value: string | null | undefined,
cb: (arg: string) => T,
allowEmptyString?: boolean
) => {
if (typeof value === "undefined") return; if (typeof value === "undefined") return;
if (value === "" || value === null) return null; if ((value === "" && !allowEmptyString) || value === null) return null;
return cb(value); return cb(value);
}; };
@@ -113,7 +113,6 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
projectId, projectId,
secretPath, secretPath,
secretKey: secret.key, secretKey: secret.key,
secretValue: secret.value || "",
type: SecretType.Shared, type: SecretType.Shared,
tagIds: secret.tags?.map((tag) => tag.id), tagIds: secret.tags?.map((tag) => tag.id),
secretComment: secret.comment, secretComment: secret.comment,
@@ -23,6 +23,7 @@ export type Props = {
newVersion?: Omit<TSecretApprovalSecChange, "tags"> & { newVersion?: Omit<TSecretApprovalSecChange, "tags"> & {
tags?: WsTag[]; tags?: WsTag[];
secretMetadata?: { key: string; value: string }[]; secretMetadata?: { key: string; value: string }[];
skipMultilineEncoding?: boolean;
}; };
presentSecretVersionNumber: number; presentSecretVersionNumber: number;
hasMerged?: boolean; hasMerged?: boolean;
@@ -217,6 +218,14 @@ export const SecretApprovalRequestChangeItem = ({
)} )}
</div> </div>
</div> </div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Multi-line Encoding</div>
<div className="text-sm">
{secretVersion?.skipMultilineEncoding?.toString() || (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
</div>
</div>
</div> </div>
) : ( ) : (
<div className="text-md flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 text-mineshaft-300 xl:w-1/2"> <div className="text-md flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 text-mineshaft-300 xl:w-1/2">
@@ -263,7 +272,7 @@ export const SecretApprovalRequestChangeItem = ({
isReadOnly isReadOnly
valueAlwaysHidden={newVersion?.secretValueHidden} valueAlwaysHidden={newVersion?.secretValueHidden}
isVisible={isNewSecretValueVisible} isVisible={isNewSecretValueVisible}
value={newVersion?.secretValue} value={newVersion?.secretValue ?? secretVersion?.secretValue}
containerClassName={twMerge( containerClassName={twMerge(
"border border-mineshaft-600 bg-bunker-700 py-1.5 text-bunker-300 hover:border-primary-400/50", "border border-mineshaft-600 bg-bunker-700 py-1.5 text-bunker-300 hover:border-primary-400/50",
newVersion?.secretValueHidden ? "pl-8 pr-2" : "px-2" newVersion?.secretValueHidden ? "pl-8 pr-2" : "px-2"
@@ -287,7 +296,7 @@ export const SecretApprovalRequestChangeItem = ({
<div className="mb-2"> <div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Comment</div> <div className="text-sm font-medium text-mineshaft-300">Comment</div>
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]"> <div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
{newVersion?.secretComment || ( {(newVersion?.secretComment ?? secretVersion?.secretComment) || (
<span className="text-sm text-mineshaft-300">-</span> <span className="text-sm text-mineshaft-300">-</span>
)}{" "} )}{" "}
</div> </div>
@@ -315,9 +324,9 @@ export const SecretApprovalRequestChangeItem = ({
</div> </div>
<div className="mb-2"> <div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Metadata</div> <div className="text-sm font-medium text-mineshaft-300">Metadata</div>
{newVersion?.secretMetadata?.length ? ( {(newVersion?.secretMetadata ?? secretVersion?.secretMetadata)?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300"> <div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{newVersion.secretMetadata?.map((el) => ( {(newVersion?.secretMetadata ?? secretVersion?.secretMetadata)?.map((el) => (
<div key={el.key} className="flex items-center"> <div key={el.key} className="flex items-center">
<Tag <Tag
size="xs" size="xs"
@@ -353,6 +362,15 @@ export const SecretApprovalRequestChangeItem = ({
<p className="text-sm text-mineshaft-300">-</p> <p className="text-sm text-mineshaft-300">-</p>
)} )}
</div> </div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Multi-line Encoding</div>
<div className="text-sm">
{newVersion?.skipMultilineEncoding?.toString() ??
secretVersion?.skipMultilineEncoding?.toString() ?? (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
</div>
</div>
</div> </div>
) : ( ) : (
<div className="text-md flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 text-mineshaft-300 xl:w-1/2"> <div className="text-md flex w-full items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 text-mineshaft-300 xl:w-1/2">