feat: configure audit log embedding per document

This commit is contained in:
ephraimduncan
2026-05-21 03:42:41 +00:00
parent c0ea4c60e4
commit 89c804a635
28 changed files with 353 additions and 40 deletions
@@ -177,7 +177,7 @@ export const run = async ({ payload, io }: { payload: TSealDocumentJobDefinition
const usePlaywrightPdf = NEXT_PRIVATE_USE_PLAYWRIGHT_PDF();
const needsCertificate = settings.includeSigningCertificate;
const needsAuditLog = settings.includeAuditLog;
const needsAuditLog = envelope.documentMeta.includeAuditLog;
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
@@ -2,7 +2,13 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-log
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData, diffDocumentMetaChanges } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { type DocumentDistributionMethod, type DocumentSigningOrder, EnvelopeType } from '@prisma/client';
import {
type DocumentDistributionMethod,
type DocumentSigningOrder,
EnvelopeType,
RecipientRole,
SendStatus,
} from '@prisma/client';
import type { SupportedLanguageCodes } from '../../constants/i18n';
import { AppError, AppErrorCode } from '../../errors/app-error';
@@ -25,6 +31,7 @@ export type CreateDocumentMetaOptions = {
signingOrder?: DocumentSigningOrder;
allowDictateNextSigner?: boolean;
distributionMethod?: DocumentDistributionMethod;
includeAuditLog?: boolean;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
@@ -47,6 +54,7 @@ export const updateDocumentMeta = async ({
emailReplyTo,
emailSettings,
distributionMethod,
includeAuditLog,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
@@ -91,6 +99,28 @@ export const updateDocumentMeta = async ({
}
}
if (
envelope.type === EnvelopeType.DOCUMENT &&
includeAuditLog !== undefined &&
includeAuditLog !== envelope.documentMeta.includeAuditLog
) {
const sentRecipientCount = await prisma.recipient.count({
where: {
envelopeId: envelope.id,
role: {
not: RecipientRole.CC,
},
sendStatus: SendStatus.SENT,
},
});
if (sentRecipientCount > 0) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Audit log embedding can only be changed before the document is sent',
});
}
}
return await prisma.$transaction(async (tx) => {
const upsertedDocumentMeta = await tx.documentMeta.update({
where: {
@@ -108,6 +138,7 @@ export const updateDocumentMeta = async ({
emailReplyTo,
emailSettings,
distributionMethod,
includeAuditLog,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
@@ -40,6 +40,7 @@ export const ZEnvelopeForSigningResponse = z.object({
documentMeta: DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
timezone: true,
dateFormat: true,
redirectUrl: true,
@@ -4,7 +4,14 @@ import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/do
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { DocumentMeta, DocumentVisibility, Prisma, TemplateType } from '@prisma/client';
import { DocumentStatus, EnvelopeType, FolderType, WebhookTriggerEvents } from '@prisma/client';
import {
DocumentStatus,
EnvelopeType,
FolderType,
RecipientRole,
SendStatus,
WebhookTriggerEvents,
} from '@prisma/client';
import { isDeepEqual } from 'remeda';
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
@@ -137,6 +144,28 @@ export const updateEnvelope = async ({
}
}
if (
envelope.type === EnvelopeType.DOCUMENT &&
meta.includeAuditLog !== undefined &&
meta.includeAuditLog !== envelope.documentMeta.includeAuditLog
) {
const sentRecipientCount = await prisma.recipient.count({
where: {
envelopeId: envelope.id,
role: {
not: RecipientRole.CC,
},
sendStatus: SendStatus.SENT,
},
});
if (sentRecipientCount > 0) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Audit log embedding can only be changed before the document is sent',
});
}
}
let folderUpdateQuery: Prisma.FolderUpdateOneWithoutEnvelopesNestedInput | undefined;
// Validate folder ID.
@@ -182,11 +211,6 @@ export const updateEnvelope = async ({
const isGlobalActionSame =
documentGlobalActionAuth === undefined || isDeepEqual(documentGlobalActionAuth, newGlobalActionAuth);
const isDocumentVisibilitySame = data.visibility === undefined || data.visibility === envelope.visibility;
const isFolderSame = data.folderId === undefined || data.folderId === envelope.folderId;
const isTemplateTypeSame = data.templateType === undefined || data.templateType === envelope.templateType;
const isPublicDescriptionSame =
data.publicDescription === undefined || data.publicDescription === envelope.publicDescription;
const isPublicTitleSame = data.publicTitle === undefined || data.publicTitle === envelope.publicTitle;
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
@@ -266,36 +290,6 @@ export const updateEnvelope = async ({
);
}
// Todo: Decide if we want to log moving the document around.
// if (!isFolderSame) {
// auditLogs.push(
// createDocumentAuditLogData({
// type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FOLDER_UPDATED,
// envelopeId: envelope.id,
// metadata: requestMetadata,
// data: {
// from: envelope.folderId,
// to: data.folderId || '',
// },
// }),
// );
// }
// Todo: Determine if changes are made
// Commented out since we didn't detect the changes to sequence.
// const isMetaSame = isDeepEqual(envelope.documentMeta, meta);
// Early return if nothing is required.
// if (
// auditLogs.length === 0 &&
// data.useLegacyFieldInsertion === undefined &&
// isFolderSame &&
// isTemplateTypeSame &&
// isPublicDescriptionSame &&
// isPublicTitleSame
// ) {
// return envelope;
// }
const updatedEnvelope = await prisma.$transaction(async (tx) => {
const result = await tx.envelope.update({
where: {
@@ -105,6 +105,7 @@ export type CreateDocumentFromTemplateOptions = {
language?: SupportedLanguageCodes;
distributionMethod?: DocumentDistributionMethod;
allowDictateNextSigner?: boolean;
includeAuditLog?: boolean;
emailSettings?: TDocumentEmailSettings;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
@@ -513,6 +514,7 @@ export const createDocumentFromTemplate = async ({
dateFormat: override?.dateFormat || template.documentMeta?.dateFormat,
redirectUrl: override?.redirectUrl || template.documentMeta?.redirectUrl,
distributionMethod: override?.distributionMethod || template.documentMeta?.distributionMethod,
includeAuditLog: override?.includeAuditLog ?? template.documentMeta?.includeAuditLog,
emailSettings: override?.emailSettings || template.documentMeta?.emailSettings,
signingOrder: override?.signingOrder || template.documentMeta?.signingOrder,
language: override?.language || template.documentMeta?.language || settings.documentLanguage,
@@ -666,7 +668,7 @@ export const createDocumentFromTemplate = async ({
const date = new Date(selector.value);
if (isNaN(date.getTime())) {
if (Number.isNaN(date.getTime())) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: `Invalid date value for field ${field.id}: ${selector.value}`,
});
@@ -47,6 +47,7 @@ export const generateSampleWebhookPayload = (event: WebhookTriggerEvents, webhoo
drawSignatureEnabled: true,
language: 'en',
distributionMethod: DocumentDistributionMethod.EMAIL,
includeAuditLog: false,
emailSettings: null,
},
recipients: [
+6
View File
@@ -19,6 +19,7 @@ import { ZDocumentEmailSettingsSchema } from './document-email';
export const ZDocumentMetaSchema = DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
id: true,
subject: true,
message: true,
@@ -93,6 +94,10 @@ export const ZDocumentMetaDistributionMethodSchema = z
.nativeEnum(DocumentDistributionMethod)
.describe('The distribution method to use when sending the document to the recipients.');
export const ZDocumentMetaIncludeAuditLogSchema = z
.boolean()
.describe('Whether to include the audit logs in the sealed document PDF.');
export const ZDocumentMetaTypedSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using a typed signature.');
@@ -116,6 +121,7 @@ export const ZDocumentMetaCreateSchema = z.object({
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
includeAuditLog: ZDocumentMetaIncludeAuditLogSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
+1
View File
@@ -53,6 +53,7 @@ export const ZDocumentSchema = LegacyDocumentSchema.pick({
documentMeta: DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
id: true,
subject: true,
message: true,
+1
View File
@@ -270,6 +270,7 @@ export const ZEditorEnvelopeSchema = EnvelopeSchema.pick({
documentMeta: DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
id: true,
subject: true,
message: true,
+1
View File
@@ -40,6 +40,7 @@ export const ZEnvelopeSchema = EnvelopeSchema.pick({
documentMeta: DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
id: true,
subject: true,
message: true,
+2
View File
@@ -56,6 +56,7 @@ export const ZTemplateSchema = TemplateSchema.pick({
drawSignatureEnabled: true,
allowDictateNextSigner: true,
distributionMethod: true,
includeAuditLog: true,
redirectUrl: true,
language: true,
emailSettings: true,
@@ -149,6 +150,7 @@ export const ZTemplateManySchema = TemplateSchema.pick({
templateMeta: DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
includeAuditLog: true,
}).nullable(),
directLink: LegacyTemplateDirectLinkSchema.pick({
token: true,
+1
View File
@@ -56,6 +56,7 @@ export const ZWebhookDocumentMetaSchema = z.object({
drawSignatureEnabled: z.boolean(),
language: z.string(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
includeAuditLog: z.boolean(),
emailSettings: z.any().nullable(),
});
+1
View File
@@ -44,6 +44,7 @@ export const extractDerivedDocumentMeta = (
signingOrder: meta.signingOrder || DocumentSigningOrder.PARALLEL,
allowDictateNextSigner: meta.allowDictateNextSigner ?? false,
distributionMethod: meta.distributionMethod || DocumentDistributionMethod.EMAIL, // Todo: Make this a setting.
includeAuditLog: meta.includeAuditLog ?? settings.includeAuditLog,
// Signature settings.
typedSignatureEnabled: meta.typedSignatureEnabled ?? settings.typedSignatureEnabled,