From 7d6bd00a229598f2890f3bea667da6888207b16f Mon Sep 17 00:00:00 2001 From: Timur Ercan Date: Mon, 20 Mar 2023 15:11:20 +0100 Subject: [PATCH 1/3] allow adding field via recipient token for signing --- apps/web/components/editor/pdf-signer.tsx | 6 +++- .../pages/api/documents/[id]/fields/index.ts | 34 +++++++++++++------ packages/lib/api/createOrUpdateField.ts | 5 +-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/apps/web/components/editor/pdf-signer.tsx b/apps/web/components/editor/pdf-signer.tsx index a7988e7e9..da0f0180d 100644 --- a/apps/web/components/editor/pdf-signer.tsx +++ b/apps/web/components/editor/pdf-signer.tsx @@ -173,7 +173,11 @@ export default function PDFSigner(props: any) { FieldType.FREE_SIGNATURE ); - createOrUpdateField(props.document, freeSignatureField).then((res) => { + createOrUpdateField( + props.document, + freeSignatureField, + recipient.token + ).then((res) => { setFields(fields.concat(res)); setDialogField(res); setOpen(true); diff --git a/apps/web/pages/api/documents/[id]/fields/index.ts b/apps/web/pages/api/documents/[id]/fields/index.ts index 9aa2366d1..861b4c672 100644 --- a/apps/web/pages/api/documents/[id]/fields/index.ts +++ b/apps/web/pages/api/documents/[id]/fields/index.ts @@ -36,8 +36,10 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { } async function postHandler(req: NextApiRequest, res: NextApiResponse) { - const user = await getUserFromToken(req, res); - const { id: documentId } = req.query; + const { token: recipientToken } = req.query; + let user = null; + if (!recipientToken) user = await getUserFromToken(req, res); + if (!user && !recipientToken) return res.status(401).end(); const body: { id: number; type: FieldType; @@ -48,18 +50,30 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { customText: string; } = req.body; - if (!user) return; - + const { id: documentId } = req.query; if (!documentId) { - res.status(400).send("Missing parameter documentId."); - return; + return res.status(400).send("Missing parameter documentId."); } - const document: PrismaDocument = await getDocument(+documentId, req, res); + if (recipientToken) { + const recipient = await prisma.recipient.findFirst({ + where: { token: recipientToken?.toString() }, + }); - // todo entity ownerships checks - if (document.userId !== user.id) { - return res.status(401).send("User does not have access to this document."); + if (!recipient || recipient?.documentId !== +documentId) + return res + .status(401) + .send("Recipient does not have access to this document."); + } + + if (user) { + const document: PrismaDocument = await getDocument(+documentId, req, res); + // todo entity ownerships checks + if (document.userId !== user.id) { + return res + .status(401) + .send("User does not have access to this document."); + } } const field = await prisma.field.upsert({ diff --git a/packages/lib/api/createOrUpdateField.ts b/packages/lib/api/createOrUpdateField.ts index 08ad993f5..62cccb3ce 100644 --- a/packages/lib/api/createOrUpdateField.ts +++ b/packages/lib/api/createOrUpdateField.ts @@ -2,11 +2,12 @@ import toast from "react-hot-toast"; export const createOrUpdateField = async ( document: any, - field: any + field: any, + recipientToken: string = "" ): Promise => { try { const created = await toast.promise( - fetch("/api/documents/" + document.id + "/fields", { + fetch("/api/documents/" + document.id + "/fields?token=" + recipientToken, { method: "POST", headers: { "Content-Type": "application/json", From d37dd000afea2f48c8f63483753269077ce191fa Mon Sep 17 00:00:00 2001 From: Timur Ercan Date: Mon, 20 Mar 2023 15:12:13 +0100 Subject: [PATCH 2/3] get document without relying on logged in user --- apps/web/pages/api/documents/[id]/sign.ts | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/web/pages/api/documents/[id]/sign.ts b/apps/web/pages/api/documents/[id]/sign.ts index f4316cf2a..e4d22487d 100644 --- a/apps/web/pages/api/documents/[id]/sign.ts +++ b/apps/web/pages/api/documents/[id]/sign.ts @@ -1,8 +1,4 @@ -import { - defaultHandler, - defaultResponder, - getUserFromToken, -} from "@documenso/lib/server"; +import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; import { NextApiRequest, NextApiResponse } from "next"; import { SigningStatus, DocumentStatus } from "@prisma/client"; @@ -12,7 +8,6 @@ import { insertImageInPDF, insertTextInPDF } from "@documenso/pdf"; import { sendSigningDoneMail } from "@documenso/lib/mail"; async function postHandler(req: NextApiRequest, res: NextApiResponse) { - const existingUser = await getUserFromToken(req, res); const { token: recipientToken } = req.query; const { signatures: signaturesFromBody }: { signatures: any[] } = req.body; @@ -29,11 +24,19 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { return res.status(401).send("Recipient not found."); } - const document: PrismaDocument = await getDocument( - recipient.documentId, - req, - res - ); + const document: PrismaDocument = await prisma.document.findFirstOrThrow({ + where: { + id: recipient.documentId, + }, + include: { + Recipient: { + orderBy: { + id: "asc", + }, + }, + Field: { include: { Recipient: true, Signature: true } }, + }, + }); if (!document) res.status(404).end(`No document found.`); From 91b206e3d7a111a1ab397924888b44438a0d8702 Mon Sep 17 00:00:00 2001 From: Timur Ercan Date: Mon, 20 Mar 2023 15:12:51 +0100 Subject: [PATCH 3/3] add token to download link to allow download without user --- apps/web/pages/documents/[id]/signed.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/web/pages/documents/[id]/signed.tsx b/apps/web/pages/documents/[id]/signed.tsx index 31b7bd118..8075fe226 100644 --- a/apps/web/pages/documents/[id]/signed.tsx +++ b/apps/web/pages/documents/[id]/signed.tsx @@ -6,7 +6,7 @@ import { Button, IconButton } from "@documenso/ui"; import Link from "next/link"; import { useRouter } from "next/router"; -const SignPage: NextPageWithLayout = (props: any) => { +const Signed: NextPageWithLayout = (props: any) => { const router = useRouter(); const allRecipientsSigned = props.document.Recipient?.every( (r: any) => r.signingStatus === "SIGNED" @@ -47,7 +47,12 @@ const SignPage: NextPageWithLayout = (props: any) => { onClick={(event: any) => { event.preventDefault(); event.stopPropagation(); - router.push("/api/documents/" + props.document.id); + router.push( + "/api/documents/" + + props.document.id + + "?token=" + + props.recipient.token + ); }} > Download "{props.document.title}" @@ -103,8 +108,9 @@ export async function getServerSideProps(context: any) { props: { document: JSON.parse(JSON.stringify(recipient.Document)), fields: JSON.parse(JSON.stringify(fields)), + recipient: JSON.parse(JSON.stringify(recipient)), }, }; } -export default SignPage; +export default Signed;