From c36306d2c96ae0a806c679bfa3dc341d8a4b9b9b Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Tue, 10 Sep 2024 15:07:40 +1000 Subject: [PATCH] feat: add authOptions to the API (#1338) Add the authOptions property to the document and recipient related API endpoints. These were previously missing so the only way API users could set the authOptions was via templates and using the generateTemplate endpoint. --- packages/api/v1/implementation.ts | 37 ++++++++++++++++++- packages/api/v1/schema.ts | 28 ++++++++++++++ .../server-only/recipient/update-recipient.ts | 32 ++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 8f8987b79..c7765de1c 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -16,6 +16,7 @@ import { getDocumentById } from '@documenso/lib/server-only/document/get-documen import { resendDocument } from '@documenso/lib/server-only/document/resend-document'; import { sendDocument } from '@documenso/lib/server-only/document/send-document'; import { updateDocument } from '@documenso/lib/server-only/document/update-document'; +import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings'; import { deleteField } from '@documenso/lib/server-only/field/delete-field'; import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id'; import { updateField } from '@documenso/lib/server-only/field/update-field'; @@ -295,6 +296,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { requestMetadata: extractNextApiRequestMetadata(args.req), }); + if (body.authOptions) { + await updateDocumentSettings({ + documentId: document.id, + userId: user.id, + teamId: team?.id, + data: body.authOptions, + requestMetadata: extractNextApiRequestMetadata(args.req), + }); + } + const recipients = await setRecipientsForDocument({ userId: user.id, teamId: team?.id, @@ -465,6 +476,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { }); } + if (body.authOptions) { + await updateDocumentSettings({ + documentId: document.id, + userId: user.id, + teamId: team?.id, + data: body.authOptions, + requestMetadata: extractNextApiRequestMetadata(args.req), + }); + } + return { status: 200, body: { @@ -547,6 +568,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { }); } + if (body.authOptions) { + await updateDocumentSettings({ + documentId: document.id, + userId: user.id, + teamId: team?.id, + data: body.authOptions, + requestMetadata: extractNextApiRequestMetadata(args.req), + }); + } + return { status: 200, body: { @@ -682,7 +713,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { createRecipient: authenticatedMiddleware(async (args, user, team) => { const { id: documentId } = args.params; - const { name, email, role } = args.body; + const { name, email, role, authOptions } = args.body; const document = await getDocumentById({ id: Number(documentId), @@ -736,6 +767,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { email, name, role, + actionAuth: authOptions?.actionAuth ?? null, }, ], requestMetadata: extractNextApiRequestMetadata(args.req), @@ -767,7 +799,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { updateRecipient: authenticatedMiddleware(async (args, user, team) => { const { id: documentId, recipientId } = args.params; - const { name, email, role } = args.body; + const { name, email, role, authOptions } = args.body; const document = await getDocumentById({ id: Number(documentId), @@ -801,6 +833,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { email, name, role, + actionAuth: authOptions?.actionAuth, requestMetadata: extractNextApiRequestMetadata(args.req), }).catch(() => null); diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index 1800b53dc..f632f36a8 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -5,6 +5,11 @@ import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/const import '@documenso/lib/constants/time-zones'; import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { ZUrlSchema } from '@documenso/lib/schemas/common'; +import { + ZDocumentAccessAuthTypesSchema, + ZDocumentActionAuthTypesSchema, + ZRecipientActionAuthTypesSchema, +} from '@documenso/lib/types/document-auth'; import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { DocumentDataType, @@ -120,6 +125,12 @@ export const ZCreateDocumentMutationSchema = z.object({ redirectUrl: z.string(), }) .partial(), + authOptions: z + .object({ + globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(), + globalActionAuth: ZDocumentActionAuthTypesSchema.optional(), + }) + .optional(), formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(), }); @@ -166,6 +177,12 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({ }) .partial() .optional(), + authOptions: z + .object({ + globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(), + globalActionAuth: ZDocumentActionAuthTypesSchema.optional(), + }) + .optional(), formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(), }); @@ -223,6 +240,12 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({ }) .partial() .optional(), + authOptions: z + .object({ + globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(), + globalActionAuth: ZDocumentActionAuthTypesSchema.optional(), + }) + .optional(), formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(), }); @@ -254,6 +277,11 @@ export const ZCreateRecipientMutationSchema = z.object({ name: z.string().min(1), email: z.string().email().min(1), role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER), + authOptions: z + .object({ + actionAuth: ZRecipientActionAuthTypesSchema.optional(), + }) + .optional(), }); /** diff --git a/packages/lib/server-only/recipient/update-recipient.ts b/packages/lib/server-only/recipient/update-recipient.ts index 6d7d6c7e8..2b5ad43c8 100644 --- a/packages/lib/server-only/recipient/update-recipient.ts +++ b/packages/lib/server-only/recipient/update-recipient.ts @@ -1,9 +1,16 @@ +import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { prisma } from '@documenso/prisma'; import type { RecipientRole, Team } from '@documenso/prisma/client'; +import { AppError, AppErrorCode } from '../../errors/app-error'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; +import { + type TRecipientActionAuthTypes, + ZRecipientAuthOptionsSchema, +} from '../../types/document-auth'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { createDocumentAuditLogData, diffRecipientChanges } from '../../utils/document-audit-logs'; +import { createRecipientAuthOptions } from '../../utils/document-auth'; export type UpdateRecipientOptions = { documentId: number; @@ -11,6 +18,7 @@ export type UpdateRecipientOptions = { email?: string; name?: string; role?: RecipientRole; + actionAuth?: TRecipientActionAuthTypes | null; userId: number; teamId?: number; requestMetadata?: RequestMetadata; @@ -22,6 +30,7 @@ export const updateRecipient = async ({ email, name, role, + actionAuth, userId, teamId, requestMetadata, @@ -48,6 +57,9 @@ export const updateRecipient = async ({ }), }, }, + include: { + Document: true, + }, }); let team: Team | null = null; @@ -75,6 +87,22 @@ export const updateRecipient = async ({ throw new Error('Recipient not found'); } + if (actionAuth) { + const isDocumentEnterprise = await isUserEnterprise({ + userId, + teamId, + }); + + if (!isDocumentEnterprise) { + throw new AppError( + AppErrorCode.UNAUTHORIZED, + 'You do not have permission to set the action auth', + ); + } + } + + const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions); + const updatedRecipient = await prisma.$transaction(async (tx) => { const persisted = await prisma.recipient.update({ where: { @@ -84,6 +112,10 @@ export const updateRecipient = async ({ email: email?.toLowerCase() ?? recipient.email, name: name ?? recipient.name, role: role ?? recipient.role, + authOptions: createRecipientAuthOptions({ + accessAuth: recipientAuthOptions.accessAuth, + actionAuth: actionAuth ?? null, + }), }, });