feat: validate signers have signature fields before distribution (#2411)

API users were inadvertently sending documents without signature fields,
causing confusion for recipients and breaking their signing flows.

- Add getRecipientsWithMissingFields helper in recipients.ts
- Add server-side validation in sendDocument to block distribution
- Fix v1 API to return 400 instead of 500 for validation errors
- Consolidate UI signature field checks to use isSignatureFieldType
- Add E2E tests for both v1 and v2 APIs
This commit is contained in:
Lucas Smith
2026-01-26 15:22:12 +11:00
committed by GitHub
parent b538580a1e
commit 0a3e0b8727
19 changed files with 1031 additions and 67 deletions
@@ -38,7 +38,10 @@ import { isDocumentCompleted } from '../../utils/document';
import { extractDocumentAuthMethods } from '../../utils/document-auth';
import { type EnvelopeIdOptions, mapSecondaryIdToDocumentId } from '../../utils/envelope';
import { toCheckboxCustomText, toRadioCustomText } from '../../utils/fields';
import { isRecipientEmailValidForSending } from '../../utils/recipients';
import {
getRecipientsWithMissingFields,
isRecipientEmailValidForSending,
} from '../../utils/recipients';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@@ -148,30 +151,19 @@ export const sendDocument = async ({
}
});
// Commented out server side checks for minimum 1 signature per signer now since we need to
// decide if we want to enforce this for API & templates.
// const fields = await getFieldsForDocument({
// documentId: documentId,
// userId: userId,
// });
// Validate that recipients who require fields (e.g., signers need signature fields) have them.
const recipientsWithMissingFields = getRecipientsWithMissingFields(
envelope.recipients,
envelope.fields,
);
// const fieldsWithSignerEmail = fields.map((field) => ({
// ...field,
// signerEmail:
// envelope.Recipient.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
// }));
if (recipientsWithMissingFields.length > 0) {
const missingRecipientIds = recipientsWithMissingFields.map((r) => r.id).join(', ');
// const everySignerHasSignature = document?.Recipient.every(
// (recipient) =>
// recipient.role !== RecipientRole.SIGNER ||
// fieldsWithSignerEmail.some(
// (field) => field.type === 'SIGNATURE' && field.signerEmail === recipient.email,
// ),
// );
// if (!everySignerHasSignature) {
// throw new Error('Some signers have not been assigned a signature field.');
// }
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: `The following recipients are missing required fields: ${missingRecipientIds}. Signers must have at least one signature field.`,
});
}
const allRecipientsHaveNoActionToTake = envelope.recipients.every(
(recipient) =>