mirror of
https://github.com/documenso/documenso.git
synced 2026-08-27 00:32:29 +10:00
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import type { Recipient } from '@prisma/client';
|
|
|
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
|
import { isTspEnvelope } from '../../types/signature-level';
|
|
import { effectiveOrder } from '../../utils/recipient-groups';
|
|
import { isCcRecipient } from '../../utils/recipients';
|
|
|
|
type AssertCompatibleRecipientGroupingOptions = {
|
|
signatureLevel: string;
|
|
recipients: Array<Pick<Recipient, 'role'> & { signingOrder?: number | null }>;
|
|
};
|
|
|
|
/**
|
|
* Reject recipient signing groups on AES/QES envelopes.
|
|
*
|
|
* A "group" is two or more signing recipients sharing a signing step, which
|
|
* they may then complete in any order — including at the same time. That is
|
|
* parallel signing scoped to one step, so it breaks the same per-recipient
|
|
* `/ByteRange` invariant that {@link assertCompatibleSigningOrder} exists to
|
|
* protect: each TSP signature must cover the exact bytes it was applied to,
|
|
* and concurrent incremental updates over one base state cannot all verify.
|
|
*
|
|
* Note this is not caught by the `signingOrder = PARALLEL` guard: groups are
|
|
* expressed as duplicate `Recipient.signingOrder` values on a document whose
|
|
* `documentMeta.signingOrder` is SEQUENTIAL.
|
|
*
|
|
* Recipients sharing a step are detected by {@link effectiveOrder}, so an
|
|
* absent signing order counts too — every unordered recipient lands in the
|
|
* same tail step and would sign in parallel.
|
|
*
|
|
* CC recipients are ignored: they never sign, and their signing order carries
|
|
* no meaning anywhere else.
|
|
*
|
|
* SES envelopes pass through unchanged — signing groups are an SES feature.
|
|
*
|
|
* Schema-layer guard. {@link sendDocument} re-checks at distribution time as a
|
|
* defence-in-depth backstop.
|
|
*/
|
|
export const assertCompatibleRecipientGrouping = ({
|
|
signatureLevel,
|
|
recipients,
|
|
}: AssertCompatibleRecipientGroupingOptions): void => {
|
|
if (!isTspEnvelope({ signatureLevel })) {
|
|
return;
|
|
}
|
|
|
|
const seenOrders = new Set<number>();
|
|
|
|
for (const recipient of recipients) {
|
|
if (isCcRecipient(recipient)) {
|
|
continue;
|
|
}
|
|
|
|
const order = effectiveOrder(recipient);
|
|
|
|
if (seenOrders.has(order)) {
|
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
message: `Envelopes signed at '${signatureLevel}' cannot place two recipients in the same signing step — a signing group is parallel signing within one step, which breaks the per-recipient /ByteRange invariant TSP signatures rely on. Give every signing recipient a distinct signingOrder.`,
|
|
});
|
|
}
|
|
|
|
seenOrders.add(order);
|
|
}
|
|
};
|