chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/external-2fa-codes. Resolve formatting
conflicts caused by biome rollout; preserve both feature streams:
PR's external 2FA token + signing-session 2FA proof additions plus
main's RateLimit/RecipientExpired/signingReminders/date-auto-insert.

In complete-document-with-token.ts, drop the duplicate early
field-fetching block introduced when main moved that logic later
with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH
check using derivedRecipientActionAuth.
This commit is contained in:
ephraimduncan
2026-05-12 11:46:11 +00:00
parent 9194884fbe
commit 138d663c25
1959 changed files with 93488 additions and 47038 deletions
@@ -1,6 +1,3 @@
import { PDF } from '@libpdf/core';
import { EnvelopeType } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { TFieldAndMeta } from '@documenso/lib/types/field-meta';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
@@ -8,6 +5,8 @@ import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.serv
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { PDF } from '@libpdf/core';
import { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { EnvelopeIdOptions } from '../../utils/envelope';
@@ -157,8 +156,7 @@ export const createEnvelopeFields = async ({
// Check whether the recipient associated with the field can have new fields created.
if (!canRecipientFieldsBeModified(recipient, envelope.fields)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Recipient type cannot have fields, or they have already interacted with the document.',
message: 'Recipient type cannot have fields, or they have already interacted with the document.',
});
}
@@ -265,9 +263,7 @@ export const createEnvelopeFields = async ({
if (envelope.type === EnvelopeType.DOCUMENT) {
await tx.documentAuditLog.createMany({
data: newlyCreatedFields.map((createdField) => {
const recipient = validatedFields.find(
(field) => field.recipientId === createdField.recipientId,
);
const recipient = validatedFields.find((field) => field.recipientId === createdField.recipientId);
return createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
@@ -308,7 +304,7 @@ export const createEnvelopeFields = async ({
continue;
}
const newDocumentData = await putPdfFileServerSide({
const { documentData: newDocumentData } = await putPdfFileServerSide({
name: 'document.pdf',
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(Buffer.from(modifiedPdfBytes)),
@@ -1,9 +1,8 @@
import { EnvelopeType } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { canRecipientFieldsBeModified } from '../../utils/recipients';
@@ -16,12 +15,7 @@ export interface DeleteDocumentFieldOptions {
requestMetadata: ApiRequestMetadata;
}
export const deleteDocumentField = async ({
userId,
teamId,
fieldId,
requestMetadata,
}: DeleteDocumentFieldOptions) => {
export const deleteDocumentField = async ({ userId, teamId, fieldId, requestMetadata }: DeleteDocumentFieldOptions) => {
// Unauthenticated check, we do the real check later.
const field = await prisma.field.findFirst({
where: {
@@ -1,6 +1,5 @@
import { EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { buildTeamWhereQuery } from '../../utils/teams';
@@ -12,11 +11,7 @@ export interface DeleteTemplateFieldOptions {
fieldId: number;
}
export const deleteTemplateField = async ({
userId,
teamId,
fieldId,
}: DeleteTemplateFieldOptions): Promise<void> => {
export const deleteTemplateField = async ({ userId, teamId, fieldId }: DeleteTemplateFieldOptions): Promise<void> => {
const field = await prisma.field.findFirst({
where: {
id: fieldId,
@@ -1,14 +1,11 @@
import { SigningStatus } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { SigningStatus } from '@prisma/client';
export type GetCompletedFieldsForDocumentOptions = {
documentId: number;
};
export const getCompletedFieldsForDocument = async ({
documentId,
}: GetCompletedFieldsForDocumentOptions) => {
export const getCompletedFieldsForDocument = async ({ documentId }: GetCompletedFieldsForDocumentOptions) => {
return await prisma.field.findMany({
where: {
documentId,
@@ -1,6 +1,5 @@
import { EnvelopeType, SigningStatus } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { EnvelopeType, SigningStatus } from '@prisma/client';
export type GetCompletedFieldsForTokenOptions = {
token: string;
@@ -1,6 +1,5 @@
import type { EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import type { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { mapFieldToLegacyField } from '../../utils/fields';
@@ -14,12 +13,7 @@ export type GetFieldByIdOptions = {
envelopeType?: EnvelopeType;
};
export const getFieldById = async ({
userId,
teamId,
fieldId,
envelopeType,
}: GetFieldByIdOptions) => {
export const getFieldById = async ({ userId, teamId, fieldId, envelopeType }: GetFieldByIdOptions) => {
const field = await prisma.field.findFirst({
where: {
id: fieldId,
@@ -1,6 +1,5 @@
import { EnvelopeType, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { EnvelopeType, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
export type GetFieldsForTokenOptions = {
token: string;
@@ -35,6 +34,7 @@ export const getFieldsForToken = async ({ token }: GetFieldsForTokenOptions) =>
signingOrder: {
gte: recipient.signingOrder ?? 0,
},
envelopeId: recipient.envelopeId,
},
envelope: {
id: recipient.envelopeId,
@@ -1,9 +1,9 @@
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { assertRecipientNotExpired } from '@documenso/lib/utils/recipients';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
export type RemovedSignedFieldWithTokenOptions = {
token: string;
@@ -37,6 +37,7 @@ export const removeSignedFieldWithToken = async ({
signingStatus: {
not: SigningStatus.SIGNED,
},
envelopeId: recipient.envelopeId,
}),
},
},
@@ -56,10 +57,9 @@ export const removeSignedFieldWithToken = async ({
throw new Error(`Document ${envelope.id} must be pending`);
}
if (
recipient?.signingStatus === SigningStatus.SIGNED ||
field.recipient.signingStatus === SigningStatus.SIGNED
) {
assertRecipientNotExpired(recipient);
if (recipient?.signingStatus === SigningStatus.SIGNED || field.recipient.signingStatus === SigningStatus.SIGNED) {
throw new Error(`Recipient ${recipient.id} has already signed`);
}
@@ -1,6 +1,3 @@
import { EnvelopeType, type Field, FieldType } from '@prisma/client';
import { isDeepEqual } from 'remeda';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
@@ -8,6 +5,7 @@ import { validateRadioField } from '@documenso/lib/advanced-fields-validation/va
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import {
FIELD_META_DEFAULT_VALUES,
type TFieldMetaSchema as FieldMeta,
ZCheckboxFieldMeta,
ZDropdownFieldMeta,
@@ -17,11 +15,10 @@ import {
ZTextFieldMeta,
} from '@documenso/lib/types/field-meta';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import {
createDocumentAuditLogData,
diffFieldChanges,
} from '@documenso/lib/utils/document-audit-logs';
import { createDocumentAuditLogData, diffFieldChanges } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { EnvelopeType, type Field, FieldType } from '@prisma/client';
import { isDeepEqual } from 'remeda';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { EnvelopeIdOptions } from '../../utils/envelope';
@@ -92,9 +89,7 @@ export const setFieldsForDocument = async ({
const recipient = envelope.recipients.find((recipient) => recipient.id === field.recipientId);
// Check whether the field is being attached to an allowed envelope item.
const foundEnvelopeItem = envelope.envelopeItems.find(
(envelopeItem) => envelopeItem.id === field.envelopeItemId,
);
const foundEnvelopeItem = envelope.envelopeItems.find((envelopeItem) => envelopeItem.id === field.envelopeItemId);
if (!foundEnvelopeItem) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -110,22 +105,16 @@ export const setFieldsForDocument = async ({
}
// Check whether the existing field can be modified.
if (
existing &&
hasFieldBeenChanged(existing, field) &&
!canRecipientFieldsBeModified(recipient, existingFields)
) {
if (existing && hasFieldBeenChanged(existing, field) && !canRecipientFieldsBeModified(recipient, existingFields)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Cannot modify a field where the recipient has already interacted with the document',
message: 'Cannot modify a field where the recipient has already interacted with the document',
});
}
// Prevent creating new fields when recipient has interacted with the document.
if (!existing && !canRecipientFieldsBeModified(recipient, existingFields)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Cannot modify a field where the recipient has already interacted with the document',
message: 'Cannot modify a field where the recipient has already interacted with the document',
});
}
@@ -143,7 +132,7 @@ export const setFieldsForDocument = async ({
const parsedFieldMeta = field.fieldMeta
? ZFieldMetaSchema.parse(field.fieldMeta)
: undefined;
: FIELD_META_DEFAULT_VALUES[field.type];
if (field.type === FieldType.TEXT && field.fieldMeta) {
const textFieldParsedMeta = ZTextFieldMeta.parse(field.fieldMeta);
@@ -157,11 +146,7 @@ export const setFieldsForDocument = async ({
if (field.type === FieldType.NUMBER && field.fieldMeta) {
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
const errors = validateNumberField(
String(numberFieldParsedMeta.value || ''),
numberFieldParsedMeta,
false,
);
const errors = validateNumberField(String(numberFieldParsedMeta.value || ''), numberFieldParsedMeta, false);
if (errors.length > 0) {
throw new Error(errors.join(', '));
@@ -180,18 +165,14 @@ export const setFieldsForDocument = async ({
throw new Error(errors.join(', '));
}
} else {
throw new Error(
'To proceed further, please set at least one value for the Checkbox field',
);
throw new Error('To proceed further, please set at least one value for the Checkbox field');
}
}
if (field.type === FieldType.RADIO) {
if (field.fieldMeta) {
const radioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find(
(option) => option.checked,
)?.value;
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find((option) => option.checked)?.value;
const errors = validateRadioField(checkedRadioFieldValue, radioFieldParsedMeta);
@@ -199,9 +180,7 @@ export const setFieldsForDocument = async ({
throw new Error(errors.join('. '));
}
} else {
throw new Error(
'To proceed further, please set at least one value for the Radio field',
);
throw new Error('To proceed further, please set at least one value for the Radio field');
}
}
@@ -214,9 +193,7 @@ export const setFieldsForDocument = async ({
throw new Error(errors.join('. '));
}
} else {
throw new Error(
'To proceed further, please set at least one value for the Dropdown field',
);
throw new Error('To proceed further, please set at least one value for the Dropdown field');
}
}
@@ -1,11 +1,10 @@
import { EnvelopeType, FieldType } from '@prisma/client';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import {
FIELD_META_DEFAULT_VALUES,
type TFieldMetaSchema as FieldMeta,
ZCheckboxFieldMeta,
ZDropdownFieldMeta,
@@ -15,6 +14,7 @@ import {
ZTextFieldMeta,
} from '@documenso/lib/types/field-meta';
import { prisma } from '@documenso/prisma';
import { EnvelopeType, FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { EnvelopeIdOptions } from '../../utils/envelope';
@@ -40,12 +40,7 @@ export type SetFieldsForTemplateOptions = {
}[];
};
export const setFieldsForTemplate = async ({
userId,
teamId,
id,
fields,
}: SetFieldsForTemplateOptions) => {
export const setFieldsForTemplate = async ({ userId, teamId, id, fields }: SetFieldsForTemplateOptions) => {
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id,
type: EnvelopeType.TEMPLATE,
@@ -88,9 +83,7 @@ export const setFieldsForTemplate = async ({
const recipient = envelope.recipients.find((recipient) => recipient.id === field.recipientId);
// Check whether the field is being attached to an allowed envelope item.
const foundEnvelopeItem = envelope.envelopeItems.find(
(envelopeItem) => envelopeItem.id === field.envelopeItemId,
);
const foundEnvelopeItem = envelope.envelopeItems.find((envelopeItem) => envelopeItem.id === field.envelopeItemId);
if (!foundEnvelopeItem) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -116,7 +109,9 @@ export const setFieldsForTemplate = async ({
// Disabling as wrapping promises here causes type issues
// eslint-disable-next-line @typescript-eslint/promise-function-async
linkedFields.map(async (field) => {
const parsedFieldMeta = field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined;
const parsedFieldMeta = field.fieldMeta
? ZFieldMetaSchema.parse(field.fieldMeta)
: FIELD_META_DEFAULT_VALUES[field.type];
if (field.type === FieldType.TEXT && field.fieldMeta) {
const textFieldParsedMeta = ZTextFieldMeta.parse(field.fieldMeta);
@@ -128,10 +123,7 @@ export const setFieldsForTemplate = async ({
if (field.type === FieldType.NUMBER && field.fieldMeta) {
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
const errors = validateNumberField(
String(numberFieldParsedMeta.value || ''),
numberFieldParsedMeta,
);
const errors = validateNumberField(String(numberFieldParsedMeta.value || ''), numberFieldParsedMeta);
if (errors.length > 0) {
throw new Error(errors.join(', '));
}
@@ -156,9 +148,7 @@ export const setFieldsForTemplate = async ({
throw new Error('Radio field is missing required metadata');
}
const radioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find(
(option) => option.checked,
)?.value;
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find((option) => option.checked)?.value;
const errors = validateRadioField(checkedRadioFieldValue, radioFieldParsedMeta);
if (errors.length > 0) {
throw new Error(errors.join('. '));
@@ -1,8 +1,3 @@
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
import { DateTime } from 'luxon';
import { isDeepEqual } from 'remeda';
import { match } from 'ts-pattern';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
@@ -10,6 +5,10 @@ import { validateRadioField } from '@documenso/lib/advanced-fields-validation/va
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
import { DateTime } from 'luxon';
import { isDeepEqual } from 'remeda';
import { match } from 'ts-pattern';
import { AUTO_SIGNABLE_FIELD_TYPES } from '../../constants/autosign';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
@@ -25,6 +24,7 @@ import {
} from '../../types/field-meta';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { assertRecipientNotExpired } from '../../utils/recipients';
import { validateFieldAuth } from '../document/validate-field-auth';
export type SignFieldWithTokenOptions = {
@@ -77,6 +77,7 @@ export const signFieldWithToken = async ({
signingOrder: {
gte: recipient.signingOrder ?? 0,
},
envelopeId: recipient.envelopeId,
}),
},
},
@@ -108,10 +109,9 @@ export const signFieldWithToken = async ({
throw new Error(`Document ${envelope.id} must be pending for signing`);
}
if (
recipient.signingStatus === SigningStatus.SIGNED ||
field.recipient.signingStatus === SigningStatus.SIGNED
) {
assertRecipientNotExpired(recipient);
if (recipient.signingStatus === SigningStatus.SIGNED || field.recipient.signingStatus === SigningStatus.SIGNED) {
throw new Error(`Recipient ${recipient.id} has already signed`);
}
@@ -188,8 +188,7 @@ export const signFieldWithToken = async ({
},
});
const isSignatureField =
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
const isSignatureField = field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
let customText = !isSignatureField ? value : undefined;
@@ -292,27 +291,14 @@ export const signFieldWithToken = async ({
type,
data: signatureImageAsBase64 || typedSignature || '',
}))
.with(
FieldType.DATE,
FieldType.EMAIL,
FieldType.NAME,
FieldType.TEXT,
FieldType.INITIALS,
(type) => ({
type,
data: updatedField.customText,
}),
)
.with(
FieldType.NUMBER,
FieldType.RADIO,
FieldType.CHECKBOX,
FieldType.DROPDOWN,
(type) => ({
type,
data: updatedField.customText,
}),
)
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.TEXT, FieldType.INITIALS, (type) => ({
type,
data: updatedField.customText,
}))
.with(FieldType.NUMBER, FieldType.RADIO, FieldType.CHECKBOX, FieldType.DROPDOWN, (type) => ({
type,
data: updatedField.customText,
}))
.exhaustive(),
fieldSecurity: derivedRecipientActionAuth
? {
@@ -1,16 +1,12 @@
import { EnvelopeType, type FieldType } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import {
createDocumentAuditLogData,
diffFieldChanges,
} from '@documenso/lib/utils/document-audit-logs';
import { createDocumentAuditLogData, diffFieldChanges } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import { EnvelopeType, type FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { type EnvelopeIdOptions } from '../../utils/envelope';
import type { EnvelopeIdOptions } from '../../utils/envelope';
import { mapFieldToLegacyField } from '../../utils/fields';
import { canRecipientFieldsBeModified } from '../../utils/recipients';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
@@ -79,9 +75,7 @@ export const updateEnvelopeFields = async ({
});
}
const recipient = envelope.recipients.find(
(recipient) => recipient.id === originalField.recipientId,
);
const recipient = envelope.recipients.find((recipient) => recipient.id === originalField.recipientId);
// Each field MUST have a recipient associated with it.
if (!recipient) {
@@ -93,8 +87,7 @@ export const updateEnvelopeFields = async ({
// Check whether the recipient associated with the field can be modified.
if (!canRecipientFieldsBeModified(recipient, envelope.fields)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Cannot modify a field where the recipient has already interacted with the document',
message: 'Cannot modify a field where the recipient has already interacted with the document',
});
}
@@ -102,20 +95,13 @@ export const updateEnvelopeFields = async ({
const fieldMetaType = field.fieldMeta?.type || originalField.fieldMeta?.type;
// Not going to mess with V1 envelopes.
if (
envelope.internalVersion === 2 &&
fieldMetaType &&
fieldMetaType.toLowerCase() !== fieldType.toLowerCase()
) {
if (envelope.internalVersion === 2 && fieldMetaType && fieldMetaType.toLowerCase() !== fieldType.toLowerCase()) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Field meta type does not match the field type',
});
}
if (
field.envelopeItemId &&
!envelope.envelopeItems.some((item) => item.id === field.envelopeItemId)
) {
if (field.envelopeItemId && !envelope.envelopeItems.some((item) => item.id === field.envelopeItemId)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Envelope item not found',
});