Files
zeekay be1d26a502 Let a recipient without an account sign
The socket the app's pages call over (/zap) opened only for a session, so
a recipient signing by their link, who has no account, could not reach
the routes that sign a field or complete a document: every field they
clicked failed with "An error occurred while signing the field". tRPC had
served those as public procedures; the port to ZAP kept their bodies and
lost the door.

A connection from the app's own pages with no session now opens as
anonymous and reaches only the routes tRPC served publicly, each
authorised by the token it carries; every other route answers it 401
before its handler runs. The operation API still refuses a request
without a token.
2026-10-20 12:41:58 -07:00

436 lines
12 KiB
TypeScript

// esign ZAP handlers — recipient router (fully ported, 17/17 procedures).
//
// Verbatim tRPC procedure bodies rewired to (ctx, raw). Input is validated by
// the SAME Zod schemas the tRPC procedures used; the server-only recipient
// functions are reused unchanged. The two token-based signing flows
// (recipient.completeDocumentWithToken / rejectDocumentWithToken) were
// unauthenticated `procedure`s in tRPC and are ported verbatim — they read
// ctx.user?.id and ctx.metadata.requestMetadata exactly as before.
//
// Route keys (dotted, matching the tRPC nested router shape):
// recipient.suggestions.find
// recipient.getDocumentRecipient / createDocumentRecipient /
// createDocumentRecipients / updateDocumentRecipient /
// updateDocumentRecipients / deleteDocumentRecipient / setDocumentRecipients
// recipient.getTemplateRecipient / createTemplateRecipient /
// createTemplateRecipients / updateTemplateRecipient /
// updateTemplateRecipients / deleteTemplateRecipient / setTemplateRecipients
// recipient.completeDocumentWithToken / rejectDocumentWithToken
import { EnvelopeType } from '@prisma/client';
import { completeDocumentWithToken } from '@hanzo/esign-lib/server-only/document/complete-document-with-token';
import { rejectDocumentWithToken } from '@hanzo/esign-lib/server-only/document/reject-document-with-token';
import { createEnvelopeRecipients } from '@hanzo/esign-lib/server-only/recipient/create-envelope-recipients';
import { deleteEnvelopeRecipient } from '@hanzo/esign-lib/server-only/recipient/delete-envelope-recipient';
import { getRecipientById } from '@hanzo/esign-lib/server-only/recipient/get-recipient-by-id';
import { getRecipientSuggestions } from '@hanzo/esign-lib/server-only/recipient/get-recipient-suggestions';
import { setDocumentRecipients } from '@hanzo/esign-lib/server-only/recipient/set-document-recipients';
import { setTemplateRecipients } from '@hanzo/esign-lib/server-only/recipient/set-template-recipients';
import { updateEnvelopeRecipients } from '@hanzo/esign-lib/server-only/recipient/update-envelope-recipients';
import { ZGetRecipientSuggestionsRequestSchema } from '../../../server/recipient-router/find-recipient-suggestions.types';
import {
ZCompleteDocumentWithTokenMutationSchema,
ZCreateDocumentRecipientRequestSchema,
ZCreateDocumentRecipientsRequestSchema,
ZCreateTemplateRecipientRequestSchema,
ZCreateTemplateRecipientsRequestSchema,
ZDeleteDocumentRecipientRequestSchema,
ZDeleteTemplateRecipientRequestSchema,
ZGetRecipientRequestSchema,
ZRejectDocumentWithTokenMutationSchema,
ZSetDocumentRecipientsRequestSchema,
ZSetTemplateRecipientsRequestSchema,
ZUpdateDocumentRecipientRequestSchema,
ZUpdateDocumentRecipientsRequestSchema,
ZUpdateTemplateRecipientRequestSchema,
ZUpdateTemplateRecipientsRequestSchema,
} from '../../../server/recipient-router/schema';
import { ZGenericSuccessResponse } from '../../../server/schema';
import type { ZapConnectionContext, ZapContext } from '../context';
import type { ZapRouteMap } from '../dispatch';
export const recipientRoutes: ZapRouteMap = {
'recipient.suggestions.find': async (ctx: ZapContext, raw) => {
const { teamId, user } = ctx;
const { query } = ZGetRecipientSuggestionsRequestSchema.parse(raw);
ctx.logger.info({
input: {
query,
},
});
const suggestions = await getRecipientSuggestions({
userId: user.id,
teamId,
query,
});
return {
results: suggestions,
};
},
'recipient.getDocumentRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { recipientId } = ZGetRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
recipientId,
},
});
return await getRecipientById({
userId: ctx.user.id,
teamId,
recipientId,
type: EnvelopeType.DOCUMENT,
});
},
'recipient.createDocumentRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { documentId, recipient } = ZCreateDocumentRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
const createdRecipients = await createEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
recipients: [recipient],
requestMetadata: ctx.metadata,
});
return createdRecipients.recipients[0];
},
'recipient.createDocumentRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { documentId, recipients } = ZCreateDocumentRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
return await createEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
recipients,
requestMetadata: ctx.metadata,
});
},
'recipient.updateDocumentRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { documentId, recipient } = ZUpdateDocumentRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
const updatedRecipients = await updateEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
recipients: [recipient],
requestMetadata: ctx.metadata,
});
return updatedRecipients.recipients[0];
},
'recipient.updateDocumentRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { documentId, recipients } = ZUpdateDocumentRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
return await updateEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
recipients,
requestMetadata: ctx.metadata,
});
},
'recipient.deleteDocumentRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { recipientId } = ZDeleteDocumentRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
recipientId,
},
});
await deleteEnvelopeRecipient({
userId: ctx.user.id,
teamId,
recipientId,
requestMetadata: ctx.metadata,
});
return ZGenericSuccessResponse;
},
'recipient.setDocumentRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { documentId, recipients } = ZSetDocumentRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
return await setDocumentRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
recipients: recipients.map((recipient) => ({
id: recipient.id,
email: recipient.email,
name: recipient.name,
role: recipient.role,
signingOrder: recipient.signingOrder,
actionAuth: recipient.actionAuth,
})),
requestMetadata: ctx.metadata,
});
},
'recipient.getTemplateRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { recipientId } = ZGetRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
recipientId,
},
});
return await getRecipientById({
userId: ctx.user.id,
teamId,
recipientId,
type: EnvelopeType.TEMPLATE,
});
},
'recipient.createTemplateRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { templateId, recipient } = ZCreateTemplateRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
templateId,
},
});
const createdRecipients = await createEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
id: templateId,
type: 'templateId',
},
recipients: [recipient],
requestMetadata: ctx.metadata,
});
return createdRecipients.recipients[0];
},
'recipient.createTemplateRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { templateId, recipients } = ZCreateTemplateRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
templateId,
},
});
return await createEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
id: templateId,
type: 'templateId',
},
recipients,
requestMetadata: ctx.metadata,
});
},
'recipient.updateTemplateRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { templateId, recipient } = ZUpdateTemplateRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
templateId,
},
});
const updatedRecipients = await updateEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'templateId',
id: templateId,
},
recipients: [recipient],
requestMetadata: ctx.metadata,
});
return updatedRecipients.recipients[0];
},
'recipient.updateTemplateRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { templateId, recipients } = ZUpdateTemplateRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
templateId,
},
});
return await updateEnvelopeRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'templateId',
id: templateId,
},
recipients,
requestMetadata: ctx.metadata,
});
},
'recipient.deleteTemplateRecipient': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { recipientId } = ZDeleteTemplateRecipientRequestSchema.parse(raw);
ctx.logger.info({
input: {
recipientId,
},
});
await deleteEnvelopeRecipient({
recipientId,
userId: ctx.user.id,
teamId,
requestMetadata: ctx.metadata,
});
return ZGenericSuccessResponse;
},
'recipient.setTemplateRecipients': async (ctx: ZapContext, raw) => {
const { teamId } = ctx;
const { templateId, recipients } = ZSetTemplateRecipientsRequestSchema.parse(raw);
ctx.logger.info({
input: {
templateId,
},
});
return await setTemplateRecipients({
userId: ctx.user.id,
teamId,
id: {
type: 'templateId',
id: templateId,
},
recipients: recipients.map((recipient) => ({
id: recipient.id,
email: recipient.email,
name: recipient.name,
role: recipient.role,
signingOrder: recipient.signingOrder,
actionAuth: recipient.actionAuth,
})),
});
},
'recipient.completeDocumentWithToken': async (ctx: ZapConnectionContext, raw) => {
const { token, documentId, accessAuthOptions, nextSigner, recipientOverride } =
ZCompleteDocumentWithTokenMutationSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
await completeDocumentWithToken({
token,
id: {
type: 'documentId',
id: documentId,
},
accessAuthOptions,
nextSigner,
recipientOverride,
userId: ctx.user?.id,
requestMetadata: ctx.metadata.requestMetadata,
});
},
'recipient.rejectDocumentWithToken': async (ctx: ZapConnectionContext, raw) => {
const { token, documentId, reason } = ZRejectDocumentWithTokenMutationSchema.parse(raw);
ctx.logger.info({
input: {
documentId,
},
});
return await rejectDocumentWithToken({
token,
id: {
type: 'documentId',
id: documentId,
},
reason,
requestMetadata: ctx.metadata.requestMetadata,
});
},
};