From dd162205faa6990fbbb228ea0d1e2acf89b7e599 Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:17:58 +0000 Subject: [PATCH] fix: prevent accidental signatures (#1515) ![CleanShot 2024-12-06 at 03 30 39](https://github.com/user-attachments/assets/d47dc820-f19d-43b7-a60d-914fc9ab24b8) ![CleanShot 2024-12-06 at 03 32 34](https://github.com/user-attachments/assets/0db98735-8c91-469b-873c-adb19d0fff7b) --- .devcontainer/devcontainer.json | 10 +- .../documents/[id]/document-page-view.tsx | 2 +- .../d/[token]/sign-direct-template.tsx | 12 ++- .../src/app/(signing)/sign/[token]/form.tsx | 30 +++++- .../app/(signing)/sign/[token]/provider.tsx | 5 + .../sign/[token]/signature-field.tsx | 37 ++++--- .../app/embed/direct/[[...url]]/client.tsx | 29 +++++- .../src/app/embed/sign/[[...url]]/client.tsx | 31 +++++- .../e2e/document-auth/action-auth.spec.ts | 16 +-- .../document-flow/stepper-component.spec.ts | 13 ++- .../include-document-certificate.spec.ts | 12 +-- packages/app-tests/e2e/user/auth-flow.spec.ts | 5 +- .../app-tests/e2e/user/update-name.spec.ts | 10 +- packages/lib/translations/de/common.po | 22 ++--- packages/lib/translations/de/web.po | 97 ++++++++++--------- packages/lib/translations/en/common.po | 22 ++--- packages/lib/translations/en/web.po | 97 ++++++++++--------- packages/lib/translations/es/common.po | 22 ++--- packages/lib/translations/es/web.po | 97 ++++++++++--------- packages/lib/translations/fr/common.po | 22 ++--- packages/lib/translations/fr/web.po | 97 ++++++++++--------- .../document-flow/add-signature.tsx | 13 ++- .../signature-pad/signature-pad.tsx | 34 ++++++- 23 files changed, 443 insertions(+), 292 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3471f4f88..790c1ab0b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,13 +10,7 @@ "ghcr.io/devcontainers/features/node:1": {} }, "onCreateCommand": "./.devcontainer/on-create.sh", - "forwardPorts": [ - 3000, - 54320, - 9000, - 2500, - 1100 - ], + "forwardPorts": [3000, 54320, 9000, 2500, 1100], "customizations": { "vscode": { "extensions": [ @@ -35,4 +29,4 @@ ] } } -} \ No newline at end of file +} diff --git a/apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx b/apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx index f05d148f7..ddc49b1cf 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx @@ -221,7 +221,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps) -

+

{match(document.status) .with(DocumentStatus.COMPLETED, () => ( This document has been signed by all recipients diff --git a/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx b/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx index bf9b671c4..f39863f43 100644 --- a/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx +++ b/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx @@ -1,7 +1,6 @@ import { useMemo, useState } from 'react'; import { Trans } from '@lingui/macro'; -import { useLingui } from '@lingui/react'; import { DateTime } from 'luxon'; import { match } from 'ts-pattern'; @@ -72,9 +71,8 @@ export const SignDirectTemplateForm = ({ template, onSubmit, }: SignDirectTemplateFormProps) => { - const { _ } = useLingui(); - - const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext(); + const { fullName, signature, signatureValid, setFullName, setSignature } = + useRequiredSigningContext(); const [localFields, setLocalFields] = useState(directRecipientFields); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); @@ -135,6 +133,8 @@ export const SignDirectTemplateForm = ({ ); }; + const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE); + const uninsertedFields = useMemo(() => { return sortFieldsByPosition(localFields.filter((field) => !field.inserted)); }, [localFields]); @@ -147,6 +147,10 @@ export const SignDirectTemplateForm = ({ const handleSubmit = async () => { setValidateUninsertedFields(true); + if (hasSignatureField && !signatureValid) { + return; + } + const isFieldsValid = validateFieldsInserted(localFields); if (!isFieldsValid) { diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx index 8085234db..17a33dff3 100644 --- a/apps/web/src/app/(signing)/sign/[token]/form.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx @@ -12,7 +12,7 @@ import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token'; import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; -import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client'; +import { type Field, FieldType, type Recipient, RecipientRole } from '@documenso/prisma/client'; import { trpc } from '@documenso/trpc/react'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { cn } from '@documenso/ui/lib/utils'; @@ -44,7 +44,8 @@ export const SigningForm = ({ const analytics = useAnalytics(); const { data: session } = useSession(); - const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext(); + const { fullName, signature, setFullName, setSignature, signatureValid, setSignatureValid } = + useRequiredSigningContext(); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); @@ -56,6 +57,8 @@ export const SigningForm = ({ // Keep the loading state going if successful since the redirect may take some time. const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful; + const hasSignatureField = fields.some((field) => field.type === FieldType.SIGNATURE); + const uninsertedFields = useMemo(() => { return sortFieldsByPosition(fields.filter((field) => !field.inserted)); }, [fields]); @@ -68,6 +71,10 @@ export const SigningForm = ({ const onFormSubmit = async () => { setValidateUninsertedFields(true); + if (hasSignatureField && !signatureValid) { + return; + } + const isFieldsValid = validateFieldsInserted(fields); if (!isFieldsValid) { @@ -142,7 +149,7 @@ export const SigningForm = ({

@@ -307,7 +322,7 @@ export const SignatureField = ({