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 & { 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(); 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); } };