fix: update merge/get secret change request to reflect granular update changes, update change request UI to properly display state, and remove secret value setting on overview rename mutation

This commit is contained in:
Scott Wilson
2025-09-22 16:41:28 -07:00
parent a1dc9ec78f
commit ed4414c456
7 changed files with 92 additions and 24 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();
});
}
}
@@ -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">Mutli-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">Mutli-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">