diff --git a/.env.example b/.env.example index 8f76d033a..d04172438 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Database -# You use the provided remote test database, courtesy of the documenso team: postgres://documenso_test_user:GnmLG14u12sd9zHsd4vVWwP40WneFJMo@dpg-cf2hljh4reb5o45oqpq0-a.oregon-postgres.render.com/documenso_test_e2i3 -# It is however recommend, that you set up a local Postgres SQL instance -# ⚠ WARNING: The test database can be resetted or taken offline at any point +# Option 1: You can use the provided remote test database, courtesy of the documenso team: postgres://documenso_test_user:GnmLG14u12sd9zHsd4vVWwP40WneFJMo@dpg-cf2hljh4reb5o45oqpq0-a.oregon-postgres.render.com/documenso_test_e2i3 +# Option 2: Set up a local Postgres SQL instance (RECOMMENDED) +# ⚠ WARNING: The test database can be resetted or taken offline at any point. # ⚠ WARNING: Please be aware that nothing written to the test databae is private. DATABASE_URL='' @@ -18,4 +18,8 @@ NEXTAUTH_URL='http://localhost:3000' # You can also configure you own SMTP server using Nodemailer in sendMailts. (currently not possible via config) SENDGRID_API_KEY='' # Sender for signing requests and completion mails. -MAIL_FROM='' \ No newline at end of file +MAIL_FROM='' + +#FEATURE FLAGS +# Allow users to register via the /signup page. Otherwise they will be redirect to the home page. +ALLOW_SIGNUP=true \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 1ff87471d..d871b075b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "apps/website/documenso/website"] path = apps/website/documenso/website - url = http://github.com/eltimuro/website.git \ No newline at end of file + url = http://github.com/documenso/website.git \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c4165347..46768040d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,31 +1,37 @@ - # Contributing to Documenso + If you plan to contribute to Documenso, please take a moment to feel awesome ✨ People like you are what open source is about ♥. Any contributions, no matter how big or small, are highly appreciated. ## Before getting started + - Before jumping into a PR be sure to search [existing PRs](https://github.com/documenso/documenso/pulls) or [issues](https://github.com/documenso/documenso/issues) for an open or closed item that relates to your submission. - Select and issue from [here](https://github.com/documenso/documenso/issues) or create a new one - Consider the results from the discussion in the issue ## Developing -The development branch is development. All pull request should be made against this branch. If you need help getting started, [join us on Slack](https://join.slack.com/t/documenso/shared_invite/zt-1qwxxsvli-nDyojjt~wakhgBGl9JRl2w). + +The development branch is main. All pull request should be made against this branch. If you need help getting started, [join us on Slack](https://join.slack.com/t/documenso/shared_invite/zt-1qwxxsvli-nDyojjt~wakhgBGl9JRl2w). 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. 2. Create a new branch: + - Create a new branch (include the issue id and somthing readable): - ```sh - git checkout -b doc-999-my-feature-or-fix - ``` + ```sh + git checkout -b doc-999-my-feature-or-fix + ``` + 3. See the [Developer Setup](https://github.com/documenso/documenso/blob/main/README.md#developer-setup) for more setup details. - ## Building + +## Building + +> **Note** +> Please be sure that you can make a full production build before pushing code or creating PRs. You can build the project with: ```bash npm run build ``` -> **Note** -> Please be sure that you can make a full production build before pushing code or creating PRs. \ No newline at end of file diff --git a/apps/web/components/editor/field-type-selector.tsx b/apps/web/components/editor/field-type-selector.tsx index 40413a499..09d75903d 100644 --- a/apps/web/components/editor/field-type-selector.tsx +++ b/apps/web/components/editor/field-type-selector.tsx @@ -25,9 +25,6 @@ export default function FieldTypeSelector(props: any) { onChange={(e: any) => { setSelectedFieldType(e); }} - onMouseDown={(e: any) => { - if (e.button === 0) props.setAdding(true); - }} >
{fieldTypes.map((fieldType) => ( diff --git a/apps/web/components/editor/pdf-editor.tsx b/apps/web/components/editor/pdf-editor.tsx index 46410c72b..793b2cb27 100644 --- a/apps/web/components/editor/pdf-editor.tsx +++ b/apps/web/components/editor/pdf-editor.tsx @@ -17,8 +17,9 @@ export default function PDFEditor(props: any) { const [fields, setFields] = useState(props.document.Field); const [selectedRecipient, setSelectedRecipient]: any = useState(); const [selectedFieldType, setSelectedFieldType] = useState(); - const noRecipients = props?.document.Recipient.length === 0; - const [adding, setAdding] = useState(false); + const noRecipients = + props?.document.Recipient.length === 0 || + props?.document.Recipient.every((e: any) => !e.email); function onPositionChangedHandler(position: any, id: any) { if (!position) return; @@ -60,11 +61,6 @@ export default function PDFEditor(props: any) { onMouseUp={(e: any, page: number) => { e.preventDefault(); e.stopPropagation(); - console.log(adding); - if (adding) { - addField(e, page); - setAdding(false); - } }} onMouseDown={(e: any, page: number) => { if (e.button === 0) addField(e, page); @@ -80,7 +76,6 @@ export default function PDFEditor(props: any) { />
@@ -101,7 +96,7 @@ export default function PDFEditor(props: any) { ); createOrUpdateField(props?.document, signatureField).then((res) => { - setFields(fields.concat(res)); + setFields((prevState) => [...prevState, res]); }); } } diff --git a/apps/web/components/editor/pdf-signer.tsx b/apps/web/components/editor/pdf-signer.tsx index 113c165db..3c8627ca6 100644 --- a/apps/web/components/editor/pdf-signer.tsx +++ b/apps/web/components/editor/pdf-signer.tsx @@ -9,7 +9,6 @@ import { CheckBadgeIcon, InformationCircleIcon, } from "@heroicons/react/24/outline"; -import toast from "react-hot-toast"; import { FieldType } from "@prisma/client"; import { createOrUpdateField, @@ -70,7 +69,7 @@ export default function PDFSigner(props: any) { ); const signedField = { ...dialogField }; signedField.signature = signature; - setFields(fields.concat(signedField)); + setFields((prevState) => [...prevState, signedField]); setOpen(false); setDialogField(null); } @@ -174,8 +173,12 @@ export default function PDFSigner(props: any) { FieldType.FREE_SIGNATURE ); - createOrUpdateField(props.document, freeSignatureField).then((res) => { - setFields(fields.concat(res)); + createOrUpdateField( + props.document, + freeSignatureField, + recipient.token + ).then((res) => { + setFields((prevState) => [...prevState, res]); setDialogField(res); setOpen(true); }); diff --git a/apps/web/components/editor/pdf-viewer.jsx b/apps/web/components/editor/pdf-viewer.jsx index 43167ad11..1d3678b05 100644 --- a/apps/web/components/editor/pdf-viewer.jsx +++ b/apps/web/components/editor/pdf-viewer.jsx @@ -3,6 +3,7 @@ import { Document, Page } from "react-pdf/dist/esm/entry.webpack5"; import EditableField from "./editable-field"; import SignableField from "./signable-field"; import short from "short-uuid"; +import { FieldType } from "@prisma/client"; export default function PDFViewer(props) { const [numPages, setNumPages] = useState(null); @@ -71,21 +72,25 @@ export default function PDFViewer(props) { onRenderError={() => setLoading(false)} > {props?.fields - .filter((item) => item.page === index) - .map((item) => + .filter((field) => field.page === index) + .map((field) => props.readonly ? ( ) : (
-
@@ -125,7 +123,6 @@ export default function Login() {
-
-

- Are you new here?{" "} - - Create a new Account - -

+ {props.allowSignup ? ( +

+ Are you new here?{" "} + + Create a new Account + +

+ ) : ( +

+ Like Documenso{" "} + + Hosted Documenso will be availible soon™ + +

+ )} diff --git a/apps/web/components/settings.tsx b/apps/web/components/settings.tsx index 5921411b5..65f0aacab 100644 --- a/apps/web/components/settings.tsx +++ b/apps/web/components/settings.tsx @@ -115,9 +115,13 @@ export default function Setttings() { + diff --git a/apps/web/package.json b/apps/web/package.json index 28073a93a..b0a57aabc 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,7 +30,7 @@ "formidable": "^3.2.5", "install": "^0.13.0", "next": "13.0.3", - "next-auth": "^4.18.3", + "next-auth": ">=4.20.1", "next-transpile-modules": "^10.0.0", "node-forge": "^1.3.1", "node-signpdf": "^1.5.0", diff --git a/apps/web/pages/api/documents/[id].ts b/apps/web/pages/api/documents/[id].ts index 517e11757..500061921 100644 --- a/apps/web/pages/api/documents/[id].ts +++ b/apps/web/pages/api/documents/[id].ts @@ -18,10 +18,10 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { } let user = null; - + let recipient = null; if (recipientToken) { // Request from signing page without login - const recipient = await prisma.recipient.findFirst({ + recipient = await prisma.recipient.findFirst({ where: { token: recipientToken?.toString(), }, @@ -37,7 +37,14 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { if (!user) return res.status(401).end(); - const document: PrismaDocument = await getDocument(+documentId, req, res); + let document: PrismaDocument | null = null; + if (recipientToken) { + document = await prisma.document.findFirst({ + where: { id: recipient?.Document?.id }, + }); + } else { + document = await getDocument(+documentId, req, res); + } if (!document) res.status(404).end(`No document with id ${documentId} found.`); @@ -45,16 +52,18 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { const signaturesCount = await prisma.signature.count({ where: { Field: { - documentId: document.id, + documentId: document?.id, }, }, }); - let signedDocumentAsBase64 = document.document; + let signedDocumentAsBase64 = document?.document || ""; // No need to add a signature, if no one signed yet. if (signaturesCount > 0) { - signedDocumentAsBase64 = await addDigitalSignature(document.document); + signedDocumentAsBase64 = await addDigitalSignature( + document?.document || "" + ); } const buffer: Buffer = Buffer.from(signedDocumentAsBase64, "base64"); @@ -62,7 +71,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) { res.setHeader("Content-Length", buffer.length); res.setHeader( "Content-Disposition", - `attachment; filename=${document.title}` + `attachment; filename=${document?.title}` ); return res.status(200).send(buffer); 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/apps/web/pages/api/documents/[id]/sign.ts b/apps/web/pages/api/documents/[id]/sign.ts index c2b4e02d6..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.`); @@ -70,6 +73,8 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }, }); + // Don't check for inserted, because currently no "sign again" scenarios exist and + // this is probably the expected behaviour in unclean states. const nonSignatureFields = await prisma.field.findMany({ where: { documentId: document.id, diff --git a/apps/web/pages/documents/[id]/recipients.tsx b/apps/web/pages/documents/[id]/recipients.tsx index 73982450d..8e86dcac3 100644 --- a/apps/web/pages/documents/[id]/recipients.tsx +++ b/apps/web/pages/documents/[id]/recipients.tsx @@ -16,7 +16,7 @@ import { } from "@heroicons/react/24/outline"; import { getUserFromToken } from "@documenso/lib/server"; import { getDocument } from "@documenso/lib/query"; -import { Document as PrismaDocument } from "@prisma/client"; +import { Document as PrismaDocument, DocumentStatus } from "@prisma/client"; import { Breadcrumb, Button, IconButton } from "@documenso/ui"; import { Dialog, Transition } from "@headlessui/react"; import { @@ -96,14 +96,6 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
- +