feat: per-recipient envelope expiration (#2519)

This commit is contained in:
Lucas Smith
2026-02-20 11:36:20 +11:00
committed by GitHub
parent f3ec8ddc57
commit 006b1d0a57
70 changed files with 2705 additions and 93 deletions
@@ -571,6 +571,14 @@ export const formatDocumentAuditLogAction = (
you: msg`You deleted an envelope item with title ${data.envelopeItemTitle}`,
user: msg`${user} deleted an envelope item with title ${data.envelopeItemTitle}`,
}))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_EXPIRED }, ({ data }) => ({
anonymous: msg({
message: `Recipient signing window expired`,
context: `Audit log format`,
}),
you: msg`Signing window expired for ${data.recipientName || data.recipientEmail}`,
user: msg`Signing window expired for ${data.recipientName || data.recipientEmail}`,
}))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELEGATED_OWNER_CREATED }, ({ data }) => {
const message = msg({
message: `The document ownership was delegated to ${data.delegatedOwnerName || data.delegatedOwnerEmail} on behalf of ${data.teamName}`,
+4
View File
@@ -62,6 +62,10 @@ export const extractDerivedDocumentMeta = (
emailReplyTo: meta.emailReplyTo ?? settings.emailReplyTo,
emailSettings:
meta.emailSettings || settings.emailDocumentSettings || DEFAULT_DOCUMENT_EMAIL_SETTINGS,
// Envelope expiration.
envelopeExpirationPeriod:
meta.envelopeExpirationPeriod ?? settings.envelopeExpirationPeriod ?? null,
} satisfies Omit<DocumentMeta, 'id'>;
};
+4
View File
@@ -8,6 +8,7 @@ import {
import type { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../constants/date-formats';
import { DEFAULT_ENVELOPE_EXPIRATION_PERIOD } from '../constants/envelope-expiration';
import {
LOWEST_ORGANISATION_ROLE,
ORGANISATION_MEMBER_ROLE_HIERARCHY,
@@ -138,6 +139,9 @@ export const generateDefaultOrganisationSettings = (): Omit<
emailDocumentSettings: DEFAULT_DOCUMENT_EMAIL_SETTINGS,
defaultRecipients: null,
envelopeExpirationPeriod: DEFAULT_ENVELOPE_EXPIRATION_PERIOD,
aiFeaturesEnabled: false,
};
};
+21
View File
@@ -5,6 +5,7 @@ import { z } from 'zod';
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import { AppError, AppErrorCode } from '../errors/app-error';
import { extractLegacyIds } from '../universal/id';
/**
@@ -94,3 +95,23 @@ export const mapRecipientToLegacyRecipient = (
export const isRecipientEmailValidForSending = (recipient: Pick<Recipient, 'email'>) => {
return z.string().email().safeParse(recipient.email).success;
};
/**
* Whether the recipient's signing window has expired.
*/
export const isRecipientExpired = (recipient: { expiresAt: Date | null }) => {
return Boolean(recipient.expiresAt && new Date(recipient.expiresAt) <= new Date());
};
/**
* Asserts that the recipient's signing window has not expired.
*
* Throws an AppError with RECIPIENT_EXPIRED if the expiration date has passed.
*/
export const assertRecipientNotExpired = (recipient: { expiresAt: Date | null }) => {
if (isRecipientExpired(recipient)) {
throw new AppError(AppErrorCode.RECIPIENT_EXPIRED, {
message: 'Recipient signing window has expired',
});
}
};
+3
View File
@@ -205,6 +205,9 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
// emailReplyToName: null,
defaultRecipients: null,
envelopeExpirationPeriod: null,
aiFeaturesEnabled: null,
};
};