mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
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:
@@ -1,3 +1,10 @@
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE, RECIPIENT_DIFF_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentStatus,
|
||||
@@ -8,27 +15,17 @@ import {
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import {
|
||||
DOCUMENT_AUDIT_LOG_TYPE,
|
||||
RECIPIENT_DIFF_TYPE,
|
||||
} from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import type { TRecipientAccessAuth } from '../../types/document-auth';
|
||||
import { DocumentAuth } from '../../types/document-auth';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../types/webhook-payload';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { mapSecondaryIdToDocumentId, unsafeBuildEnvelopeIdQuery } from '../../utils/envelope';
|
||||
import { assertRecipientNotExpired } from '../../utils/recipients';
|
||||
import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
import { isRecipientAuthorized } from './is-recipient-authorized';
|
||||
@@ -94,6 +91,8 @@ export const completeDocumentWithToken = async ({
|
||||
|
||||
const [recipient] = envelope.recipients;
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
@@ -106,55 +105,15 @@ export const completeDocumentWithToken = async ({
|
||||
}
|
||||
|
||||
if (envelope.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) {
|
||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token: recipient.token });
|
||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({
|
||||
token: recipient.token,
|
||||
});
|
||||
|
||||
if (!isRecipientsTurn) {
|
||||
throw new Error(
|
||||
`Recipient ${recipient.id} attempted to complete the document before it was their turn`,
|
||||
);
|
||||
throw new Error(`Recipient ${recipient.id} attempted to complete the document before it was their turn`);
|
||||
}
|
||||
}
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
recipientId: recipient.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
||||
}
|
||||
|
||||
let recipientName = recipient.name;
|
||||
let recipientEmail = recipient.email;
|
||||
|
||||
// Only trim the name if it's been derived.
|
||||
if (!recipientName) {
|
||||
recipientName = (
|
||||
recipientOverride?.name ||
|
||||
fields.find((field) => field.type === FieldType.NAME)?.customText ||
|
||||
''
|
||||
).trim();
|
||||
}
|
||||
|
||||
// Only trim the email if it's been derived.
|
||||
if (!recipient.email) {
|
||||
recipientEmail = (
|
||||
recipientOverride?.email ||
|
||||
fields.find((field) => field.type === FieldType.EMAIL)?.customText ||
|
||||
''
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
if (!recipientEmail) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Recipient email is required',
|
||||
});
|
||||
}
|
||||
|
||||
const { derivedRecipientAccessAuth, derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
||||
documentAuth: envelope.authOptions,
|
||||
recipientAuth: recipient.authOptions,
|
||||
@@ -229,6 +188,110 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
}
|
||||
|
||||
let fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
recipientId: recipient.id,
|
||||
},
|
||||
});
|
||||
|
||||
// This should be scoped to the current recipient.
|
||||
const uninsertedDateFields = fields.filter((field) => field.type === FieldType.DATE && !field.inserted);
|
||||
|
||||
let recipientName = recipient.name;
|
||||
let recipientEmail = recipient.email;
|
||||
|
||||
// Only trim the name if it's been derived.
|
||||
if (!recipientName) {
|
||||
recipientName = (
|
||||
recipientOverride?.name ||
|
||||
fields.find((field) => field.type === FieldType.NAME)?.customText ||
|
||||
''
|
||||
).trim();
|
||||
}
|
||||
|
||||
// Only trim the email if it's been derived.
|
||||
if (!recipient.email) {
|
||||
recipientEmail = (
|
||||
recipientOverride?.email ||
|
||||
fields.find((field) => field.type === FieldType.EMAIL)?.customText ||
|
||||
''
|
||||
)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
if (!recipientEmail) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Recipient email is required',
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-insert all un-inserted date fields for V2 envelopes at completion time.
|
||||
if (envelope.internalVersion === 2 && uninsertedDateFields.length > 0) {
|
||||
const formattedDate = DateTime.now()
|
||||
.setZone(envelope.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE)
|
||||
.toFormat(envelope.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT);
|
||||
|
||||
const newDateFieldValues = {
|
||||
customText: formattedDate,
|
||||
inserted: true,
|
||||
};
|
||||
|
||||
await prisma.field.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: uninsertedDateFields.map((field) => field.id),
|
||||
},
|
||||
},
|
||||
data: {
|
||||
...newDateFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
// Create audit log entries for each auto-inserted date field.
|
||||
await prisma.documentAuditLog.createMany({
|
||||
data: uninsertedDateFields.map((field) =>
|
||||
createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
email: recipientEmail,
|
||||
name: recipientName,
|
||||
},
|
||||
requestMetadata,
|
||||
data: {
|
||||
recipientEmail: recipientEmail,
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipientName,
|
||||
recipientRole: recipient.role,
|
||||
fieldId: field.secondaryId,
|
||||
field: {
|
||||
type: FieldType.DATE,
|
||||
data: formattedDate,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
// Update the local fields array so the subsequent validation check passes.
|
||||
fields = fields.map((field) => {
|
||||
if (field.type === FieldType.DATE && !field.inserted) {
|
||||
return {
|
||||
...field,
|
||||
...newDateFieldValues,
|
||||
};
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
}
|
||||
|
||||
if (fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
where: {
|
||||
@@ -299,6 +362,18 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const envelopeWithRelations = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: envelope.id },
|
||||
include: { documentMeta: true, recipients: true },
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_RECIPIENT_COMPLETED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(envelopeWithRelations)),
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.recipient.signed.email',
|
||||
payload: {
|
||||
@@ -372,6 +447,7 @@ export const completeDocumentWithToken = async ({
|
||||
where: { id: nextRecipient.id },
|
||||
data: {
|
||||
sendStatus: SendStatus.SENT,
|
||||
sentAt: new Date(),
|
||||
...(nextSigner && envelope.documentMeta?.allowDictateNextSigner
|
||||
? {
|
||||
name: nextSigner.name,
|
||||
@@ -380,16 +456,16 @@ export const completeDocumentWithToken = async ({
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.signing.requested.email',
|
||||
payload: {
|
||||
userId: envelope.userId,
|
||||
documentId: legacyDocumentId,
|
||||
recipientId: nextRecipient.id,
|
||||
requestMetadata,
|
||||
},
|
||||
});
|
||||
await jobs.triggerJob({
|
||||
name: 'send.signing.requested.email',
|
||||
payload: {
|
||||
userId: envelope.userId,
|
||||
documentId: legacyDocumentId,
|
||||
recipientId: nextRecipient.id,
|
||||
requestMetadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user