diff --git a/backend/src/db/migrations/20250922222724_remove-secret-approval-multiline-encoding-default.ts b/backend/src/db/migrations/20250922222724_remove-secret-approval-multiline-encoding-default.ts new file mode 100644 index 000000000..a6b9e624f --- /dev/null +++ b/backend/src/db/migrations/20250922222724_remove-secret-approval-multiline-encoding-default.ts @@ -0,0 +1,19 @@ +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; + +export async function up(knex: Knex): Promise { + 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 { + if (await knex.schema.hasTable(TableName.SecretApprovalRequestSecretV2)) { + await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => { + t.boolean("skipMultilineEncoding").defaultTo(false).alter(); + }); + } +} diff --git a/backend/src/db/schemas/secret-approval-requests-secrets-v2.ts b/backend/src/db/schemas/secret-approval-requests-secrets-v2.ts index 298985fed..d0f1a7c6d 100644 --- a/backend/src/db/schemas/secret-approval-requests-secrets-v2.ts +++ b/backend/src/db/schemas/secret-approval-requests-secrets-v2.ts @@ -17,7 +17,7 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({ encryptedComment: zodBuffer.nullable().optional(), reminderNote: z.string().nullable().optional(), reminderRepeatDays: z.number().nullable().optional(), - skipMultilineEncoding: z.boolean().default(false).nullable().optional(), + skipMultilineEncoding: z.boolean().nullable().optional(), metadata: z.unknown().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index 6a9b59f00..78d9aeddc 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -320,10 +320,20 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv .array(), secretPath: z.string(), 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({ secretValueHidden: z.boolean(), secretValue: z.string().optional(), + secretComment: z.string().optional(), + skipMultilineEncoding: z.boolean().nullish(), isRotatedSecret: z.boolean().optional(), op: z.string(), tags: SanitizedTagSchema.array().optional(), @@ -348,7 +358,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv secretValueHidden: z.boolean(), secretComment: z.string().optional(), tags: SanitizedTagSchema.array().optional(), - secretMetadata: ResourceMetadataSchema.nullish() + secretMetadata: ResourceMetadataSchema.nullish(), + skipMultilineEncoding: z.boolean().nullish() }) .optional() }) diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts index 17182cddf..d0df188df 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts @@ -284,7 +284,8 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { db.ref("version").withSchema(TableName.SecretVersionV2).as("secVerVersion"), db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"), 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( db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"), @@ -326,14 +327,22 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { { key: "secretVersion", label: "secretVersion" as const, - mapper: ({ secretVersion, secVerVersion, secVerKey, secVerValue, secVerComment }) => + mapper: ({ + secretVersion, + secVerVersion, + secVerKey, + secVerValue, + secVerComment, + secVerSkipMultilineEncoding + }) => secretVersion ? { version: secVerVersion, id: secretVersion, key: secVerKey, encryptedValue: secVerValue, - encryptedComment: secVerComment + encryptedComment: secVerComment, + skipMultilineEncoding: secVerSkipMultilineEncoding } : undefined, childrenMapper: [ diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 80fbf546f..7847a59a6 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -337,12 +337,17 @@ export const secretApprovalRequestServiceFactory = ({ ? INFISICAL_SECRET_VALUE_HIDDEN_MASK : el.secret && el.secret.isRotatedSecret ? undefined - : el.encryptedValue + : el.encryptedValue !== undefined && el.encryptedValue !== null ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() - : "", - secretComment: el.encryptedComment - ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() - : "", + : undefined, + secretComment: + 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 ? { secretKey: el.secret.key, @@ -394,7 +399,8 @@ export const secretApprovalRequestServiceFactory = ({ ? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString() : "", tags: el.secretVersion.tags, - secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO + secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO, + skipMultilineEncoding: el.secretVersion.skipMultilineEncoding } : undefined })); @@ -733,9 +739,9 @@ export const secretApprovalRequestServiceFactory = ({ tx, inputSecrets: secretUpdationCommits.map((el) => { 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 ? getAllSecretReferencesV2Bridge( secretManagerDecryptor({ @@ -749,9 +755,9 @@ export const secretApprovalRequestServiceFactory = ({ filter: { id: el.secretId as string, type: SecretType.Shared }, data: { reminderRepeatDays: el.reminderRepeatDays, - encryptedComment: el.encryptedComment, + encryptedComment: el.encryptedComment !== null ? el.encryptedComment : undefined, reminderNote: el.reminderNote, - skipMultilineEncoding: el.skipMultilineEncoding, + skipMultilineEncoding: el.skipMultilineEncoding !== null ? el.skipMultilineEncoding : undefined, key: el.key, tags: el?.tags.map(({ id }) => id), secretMetadata: el.secretMetadata as ResourceMetadataDTO, @@ -1633,11 +1639,13 @@ export const secretApprovalRequestServiceFactory = ({ key: newSecretName || secretKey, encryptedComment: setKnexStringValue( 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( 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, reminderNote, diff --git a/backend/src/lib/knex/select.ts b/backend/src/lib/knex/select.ts index feccad4e3..8cc3f4892 100644 --- a/backend/src/lib/knex/select.ts +++ b/backend/src/lib/knex/select.ts @@ -16,8 +16,12 @@ export const stripUndefinedInWhere = (val: T): Exclude(value: string | null | undefined, cb: (arg: string) => T) => { +export const setKnexStringValue = ( + value: string | null | undefined, + cb: (arg: string) => T, + allowEmptyString?: boolean +) => { if (typeof value === "undefined") return; - if (value === "" || value === null) return null; + if ((value === "" && !allowEmptyString) || value === null) return null; return cb(value); }; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx index 5e1f27f57..723d4c3e2 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx @@ -113,7 +113,6 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } projectId, secretPath, secretKey: secret.key, - secretValue: secret.value || "", type: SecretType.Shared, tagIds: secret.tags?.map((tag) => tag.id), secretComment: secret.comment, diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx index 7fd050b03..e451fdd27 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx @@ -23,6 +23,7 @@ export type Props = { newVersion?: Omit & { tags?: WsTag[]; secretMetadata?: { key: string; value: string }[]; + skipMultilineEncoding?: boolean; }; presentSecretVersionNumber: number; hasMerged?: boolean; @@ -217,6 +218,14 @@ export const SecretApprovalRequestChangeItem = ({ )} +
+
Multi-line Encoding
+
+ {secretVersion?.skipMultilineEncoding?.toString() || ( + - + )}{" "} +
+
) : (
@@ -263,7 +272,7 @@ export const SecretApprovalRequestChangeItem = ({ isReadOnly valueAlwaysHidden={newVersion?.secretValueHidden} isVisible={isNewSecretValueVisible} - value={newVersion?.secretValue} + value={newVersion?.secretValue ?? secretVersion?.secretValue} containerClassName={twMerge( "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" @@ -287,7 +296,7 @@ export const SecretApprovalRequestChangeItem = ({
Comment
- {newVersion?.secretComment || ( + {(newVersion?.secretComment ?? secretVersion?.secretComment) || ( - )}{" "}
@@ -315,9 +324,9 @@ export const SecretApprovalRequestChangeItem = ({
Metadata
- {newVersion?.secretMetadata?.length ? ( + {(newVersion?.secretMetadata ?? secretVersion?.secretMetadata)?.length ? (
- {newVersion.secretMetadata?.map((el) => ( + {(newVersion?.secretMetadata ?? secretVersion?.secretMetadata)?.map((el) => (
-

)}
+
+
Multi-line Encoding
+
+ {newVersion?.skipMultilineEncoding?.toString() ?? + secretVersion?.skipMultilineEncoding?.toString() ?? ( + - + )}{" "} +
+
) : (