Files
documenso/packages/trpc/server/embedding-router/apply-multi-sign-signature.ts
Lucas Smith ce66da0055 feat: multisign embedding (#1823)
Adds the ability to use a multisign embedding for cases where multiple
documents need to be signed in a convenient manner.
2025-06-05 12:58:52 +10:00

103 lines
3.5 KiB
TypeScript

import { FieldType, ReadStatus, SigningStatus } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { procedure } from '../trpc';
import {
ZApplyMultiSignSignatureRequestSchema,
ZApplyMultiSignSignatureResponseSchema,
} from './apply-multi-sign-signature.types';
export const applyMultiSignSignatureRoute = procedure
.input(ZApplyMultiSignSignatureRequestSchema)
.output(ZApplyMultiSignSignatureResponseSchema)
.mutation(async ({ input, ctx: { metadata } }) => {
try {
const { tokens, signature, isBase64 } = input;
// Get all documents and recipients for the tokens
const envelopes = await Promise.all(
tokens.map(async (token) => {
const document = await getDocumentByToken({ token });
const recipient = await getRecipientByToken({ token });
return { document, recipient };
}),
);
// Check if all documents have been viewed
const hasUnviewedDocuments = envelopes.some(
(envelope) => envelope.recipient.readStatus !== ReadStatus.OPENED,
);
if (hasUnviewedDocuments) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'All documents must be viewed before signing',
});
}
// If we require action auth we should abort here for now
for (const envelope of envelopes) {
const derivedRecipientActionAuth = extractDocumentAuthMethods({
documentAuth: envelope.document.authOptions,
recipientAuth: envelope.recipient.authOptions,
});
if (
derivedRecipientActionAuth.recipientAccessAuthRequired ||
derivedRecipientActionAuth.recipientActionAuthRequired
) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Documents that require additional authentication cannot be multi signed at the moment',
});
}
}
// Sign all signature fields for each document
await Promise.all(
envelopes.map(async (envelope) => {
if (envelope.recipient.signingStatus === SigningStatus.REJECTED) {
return;
}
const signatureFields = await prisma.field.findMany({
where: {
documentId: envelope.document.id,
recipientId: envelope.recipient.id,
type: FieldType.SIGNATURE,
inserted: false,
},
});
await Promise.all(
signatureFields.map(async (field) =>
signFieldWithToken({
token: envelope.recipient.token,
fieldId: field.id,
value: signature,
isBase64,
requestMetadata: metadata.requestMetadata,
}),
),
);
}),
);
return { success: true };
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
message: 'Failed to apply multi-sign signature',
});
}
});