mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
fix: update reauth constraints and tests
This commit is contained in:
@ -9,12 +9,12 @@ import { DateTime } from 'luxon';
|
|||||||
import { signOut } from 'next-auth/react';
|
import { signOut } from 'next-auth/react';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
|
||||||
import {
|
import {
|
||||||
DocumentAuth,
|
DocumentAuth,
|
||||||
type TRecipientActionAuth,
|
type TRecipientActionAuth,
|
||||||
type TRecipientActionAuthTypes,
|
type TRecipientActionAuthTypes,
|
||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
|
import type { FieldType } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -33,7 +33,7 @@ export type DocumentActionAuthDialogProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
documentAuthType: TRecipientActionAuthTypes;
|
documentAuthType: TRecipientActionAuthTypes;
|
||||||
description?: string;
|
description?: string;
|
||||||
actionTarget?: 'FIELD' | 'DOCUMENT';
|
actionTarget: FieldType | 'DOCUMENT';
|
||||||
isSubmitting?: boolean;
|
isSubmitting?: boolean;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (value: boolean) => void;
|
onOpenChange: (value: boolean) => void;
|
||||||
@ -53,7 +53,6 @@ export const DocumentActionAuthDialog = ({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
documentAuthType,
|
documentAuthType,
|
||||||
actionTarget = 'FIELD',
|
|
||||||
// onReauthFormSubmit,
|
// onReauthFormSubmit,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
open,
|
open,
|
||||||
@ -135,19 +134,14 @@ export const DocumentActionAuthDialog = ({
|
|||||||
// setFormErrorCode(null);
|
// setFormErrorCode(null);
|
||||||
// }, [open, form]);
|
// }, [open, form]);
|
||||||
|
|
||||||
const defaultRecipientActionVerb = RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleOnOpenChange}>
|
<Dialog open={open} onOpenChange={handleOnOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>{title || 'Sign field'}</DialogTitle>
|
||||||
{title || `${defaultRecipientActionVerb} ${actionTarget.toLowerCase()}`}
|
|
||||||
</DialogTitle>
|
|
||||||
|
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{description ||
|
{description || `Reauthentication is required to sign the field`}
|
||||||
`Reauthentication is required to ${defaultRecipientActionVerb.toLowerCase()} the ${actionTarget.toLowerCase()}`}
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -156,8 +150,7 @@ export const DocumentActionAuthDialog = ({
|
|||||||
<fieldset disabled={isSigningOut} className="space-y-4">
|
<fieldset disabled={isSigningOut} className="space-y-4">
|
||||||
<Alert>
|
<Alert>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
To {defaultRecipientActionVerb.toLowerCase()} this {actionTarget.toLowerCase()},
|
To sign this field, you need to be logged in as <strong>{recipient.email}</strong>
|
||||||
you need to be logged in as <strong>{recipient.email}</strong>
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import type {
|
|||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
import type { Document, Recipient, User } from '@documenso/prisma/client';
|
import { type Document, FieldType, type Recipient, type User } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { DocumentActionAuthDialogProps } from './document-action-auth-dialog';
|
import type { DocumentActionAuthDialogProps } from './document-action-auth-dialog';
|
||||||
import { DocumentActionAuthDialog } from './document-action-auth-dialog';
|
import { DocumentActionAuthDialog } from './document-action-auth-dialog';
|
||||||
@ -106,7 +106,7 @@ export const DocumentAuthProvider = ({
|
|||||||
|
|
||||||
const executeActionAuthProcedure = async (options: ExecuteActionAuthProcedureOptions) => {
|
const executeActionAuthProcedure = async (options: ExecuteActionAuthProcedureOptions) => {
|
||||||
// Directly run callback if no auth required.
|
// Directly run callback if no auth required.
|
||||||
if (!derivedRecipientActionAuth) {
|
if (!derivedRecipientActionAuth || options.actionTarget !== FieldType.SIGNATURE) {
|
||||||
await options.onReauthFormSubmit();
|
await options.onReauthFormSubmit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import { Input } from '@documenso/ui/primitives/input';
|
|||||||
import { Label } from '@documenso/ui/primitives/label';
|
import { Label } from '@documenso/ui/primitives/label';
|
||||||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
|
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
|
||||||
|
|
||||||
import { useRequiredDocumentAuthContext } from './document-auth-provider';
|
|
||||||
import { useRequiredSigningContext } from './provider';
|
import { useRequiredSigningContext } from './provider';
|
||||||
import { SignDialog } from './sign-dialog';
|
import { SignDialog } from './sign-dialog';
|
||||||
|
|
||||||
@ -37,7 +36,6 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
|||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
|
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
|
||||||
|
|
||||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||||
|
|
||||||
@ -67,10 +65,13 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeActionAuthProcedure({
|
await completeDocument();
|
||||||
onReauthFormSubmit: completeDocument,
|
|
||||||
actionTarget: 'DOCUMENT',
|
// Reauth is currently not required for completing the document.
|
||||||
});
|
// await executeActionAuthProcedure({
|
||||||
|
// onReauthFormSubmit: completeDocument,
|
||||||
|
// actionTarget: 'DOCUMENT',
|
||||||
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeDocument = async (authOptions?: TRecipientActionAuth) => {
|
const completeDocument = async (authOptions?: TRecipientActionAuth) => {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Loader } from 'lucide-react';
|
|||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import { type Recipient } from '@documenso/prisma/client';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -69,6 +69,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
|
|||||||
|
|
||||||
void executeActionAuthProcedure({
|
void executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localFullName),
|
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localFullName),
|
||||||
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,6 @@ import {
|
|||||||
|
|
||||||
import { truncateTitle } from '~/helpers/truncate-title';
|
import { truncateTitle } from '~/helpers/truncate-title';
|
||||||
|
|
||||||
import { useRequiredDocumentAuthContext } from './document-auth-provider';
|
|
||||||
|
|
||||||
export type SignDialogProps = {
|
export type SignDialogProps = {
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
document: Document;
|
document: Document;
|
||||||
@ -31,27 +29,26 @@ export const SignDialog = ({
|
|||||||
onSignatureComplete,
|
onSignatureComplete,
|
||||||
role,
|
role,
|
||||||
}: SignDialogProps) => {
|
}: SignDialogProps) => {
|
||||||
const { executeActionAuthProcedure, isAuthRedirectRequired } = useRequiredDocumentAuthContext();
|
|
||||||
|
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const truncatedTitle = truncateTitle(document.title);
|
const truncatedTitle = truncateTitle(document.title);
|
||||||
const isComplete = fields.every((field) => field.inserted);
|
const isComplete = fields.every((field) => field.inserted);
|
||||||
|
|
||||||
const handleOpenChange = async (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
if (isSubmitting || !isComplete) {
|
if (isSubmitting || !isComplete) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAuthRedirectRequired) {
|
// Reauth is currently not required for signing the document.
|
||||||
await executeActionAuthProcedure({
|
// if (isAuthRedirectRequired) {
|
||||||
actionTarget: 'DOCUMENT',
|
// await executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: () => {
|
// actionTarget: 'DOCUMENT',
|
||||||
// Do nothing since the user should be redirected.
|
// onReauthFormSubmit: () => {
|
||||||
},
|
// // Do nothing since the user should be redirected.
|
||||||
});
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
setShowDialog(open);
|
setShowDialog(open);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Loader } from 'lucide-react';
|
|||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import { type Recipient } from '@documenso/prisma/client';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -89,6 +89,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
|
|||||||
|
|
||||||
void executeActionAuthProcedure({
|
void executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localSignature),
|
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localSignature),
|
||||||
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { type TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import { type TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
import { FieldRootContainer } from '@documenso/ui/components/field/field';
|
import { FieldRootContainer } from '@documenso/ui/components/field/field';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||||
@ -55,11 +56,24 @@ export const SigningFieldContainer = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bypass reauth for non signature fields.
|
||||||
|
if (field.type !== FieldType.SIGNATURE) {
|
||||||
|
const presignResult = await onPreSign?.();
|
||||||
|
|
||||||
|
if (presignResult === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await onSign();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isAuthRedirectRequired) {
|
if (isAuthRedirectRequired) {
|
||||||
await executeActionAuthProcedure({
|
await executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: () => {
|
onReauthFormSubmit: () => {
|
||||||
// Do nothing since the user should be redirected.
|
// Do nothing since the user should be redirected.
|
||||||
},
|
},
|
||||||
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -76,6 +90,7 @@ export const SigningFieldContainer = ({
|
|||||||
|
|
||||||
await executeActionAuthProcedure({
|
await executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: onSign,
|
onReauthFormSubmit: onSign,
|
||||||
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Loader } from 'lucide-react';
|
|||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import { type Recipient } from '@documenso/prisma/client';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -61,6 +61,7 @@ export const TextField = ({ field, recipient }: TextFieldProps) => {
|
|||||||
|
|
||||||
void executeActionAuthProcedure({
|
void executeActionAuthProcedure({
|
||||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
||||||
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -124,7 +124,8 @@ test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ pa
|
|||||||
await unseedUser(recipientWithAccount.id);
|
await unseedUser(recipientWithAccount.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[DOCUMENT_AUTH]: should deny signing document when required for global auth', async ({
|
// Currently document auth for signing/approving/viewing is not required.
|
||||||
|
test.skip('[DOCUMENT_AUTH]: should deny signing document when required for global auth', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const user = await seedUser();
|
const user = await seedUser();
|
||||||
@ -184,6 +185,10 @@ test('[DOCUMENT_AUTH]: should deny signing fields when required for global auth'
|
|||||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||||
|
|
||||||
for (const field of Field) {
|
for (const field of Field) {
|
||||||
|
if (field.type !== FieldType.SIGNATURE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
await expect(page.getByRole('paragraph')).toContainText(
|
await expect(page.getByRole('paragraph')).toContainText(
|
||||||
'Reauthentication is required to sign the field',
|
'Reauthentication is required to sign the field',
|
||||||
@ -249,6 +254,10 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
|
|||||||
|
|
||||||
if (isAuthRequired) {
|
if (isAuthRequired) {
|
||||||
for (const field of Field) {
|
for (const field of Field) {
|
||||||
|
if (field.type !== FieldType.SIGNATURE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
await expect(page.getByRole('paragraph')).toContainText(
|
await expect(page.getByRole('paragraph')).toContainText(
|
||||||
'Reauthentication is required to sign the field',
|
'Reauthentication is required to sign the field',
|
||||||
@ -356,6 +365,10 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
|
|||||||
|
|
||||||
if (isAuthRequired) {
|
if (isAuthRequired) {
|
||||||
for (const field of Field) {
|
for (const field of Field) {
|
||||||
|
if (field.type !== FieldType.SIGNATURE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
await expect(page.getByRole('paragraph')).toContainText(
|
await expect(page.getByRole('paragraph')).toContainText(
|
||||||
'Reauthentication is required to sign the field',
|
'Reauthentication is required to sign the field',
|
||||||
|
|||||||
@ -4,17 +4,21 @@ import path from 'node:path';
|
|||||||
import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { getRecipientByEmail } from '@documenso/lib/server-only/recipient/get-recipient-by-email';
|
import { getRecipientByEmail } from '@documenso/lib/server-only/recipient/get-recipient-by-email';
|
||||||
import { DocumentStatus } from '@documenso/prisma/client';
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
import { TEST_USER } from '@documenso/prisma/seed/pr-718-add-stepper-component';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
|
import { apiSignin } from './fixtures/authentication';
|
||||||
|
|
||||||
test(`[PR-718]: should be able to create a document`, async ({ page }) => {
|
test(`[PR-718]: should be able to create a document`, async ({ page }) => {
|
||||||
await page.goto('/signin');
|
await page.goto('/signin');
|
||||||
|
|
||||||
const documentTitle = `example-${Date.now()}.pdf`;
|
const documentTitle = `example-${Date.now()}.pdf`;
|
||||||
|
|
||||||
// Sign in
|
const user = await seedUser();
|
||||||
await page.getByLabel('Email').fill(TEST_USER.email);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password);
|
await apiSignin({
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
page,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
|
||||||
// Upload document
|
// Upload document
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
@ -82,10 +86,12 @@ test('should be able to create a document with multiple recipients', async ({ pa
|
|||||||
|
|
||||||
const documentTitle = `example-${Date.now()}.pdf`;
|
const documentTitle = `example-${Date.now()}.pdf`;
|
||||||
|
|
||||||
// Sign in
|
const user = await seedUser();
|
||||||
await page.getByLabel('Email').fill(TEST_USER.email);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password);
|
await apiSignin({
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
page,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
|
||||||
// Upload document
|
// Upload document
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
@ -103,7 +109,7 @@ test('should be able to create a document with multiple recipients', async ({ pa
|
|||||||
await page.waitForURL(/\/documents\/\d+/);
|
await page.waitForURL(/\/documents\/\d+/);
|
||||||
|
|
||||||
// Set title
|
// Set title
|
||||||
await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Title').fill(documentTitle);
|
await page.getByLabel('Title').fill(documentTitle);
|
||||||
|
|
||||||
@ -112,13 +118,12 @@ test('should be able to create a document with multiple recipients', async ({ pa
|
|||||||
// Add signers
|
// Add signers
|
||||||
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Email*').fill('user1@example.com');
|
// Add 2 signers.
|
||||||
await page.getByLabel('Name').fill('User 1');
|
await page.getByPlaceholder('Email').fill('user1@example.com');
|
||||||
|
await page.getByPlaceholder('Name').fill('User 1');
|
||||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||||
|
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('user2@example.com');
|
||||||
await page.getByLabel('Email*').nth(1).fill('user2@example.com');
|
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('User 2');
|
||||||
await page.getByLabel('Name').nth(1).fill('User 2');
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
@ -177,10 +182,12 @@ test('should be able to create, send and sign a document', async ({ page }) => {
|
|||||||
|
|
||||||
const documentTitle = `example-${Date.now()}.pdf`;
|
const documentTitle = `example-${Date.now()}.pdf`;
|
||||||
|
|
||||||
// Sign in
|
const user = await seedUser();
|
||||||
await page.getByLabel('Email').fill(TEST_USER.email);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password);
|
await apiSignin({
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
page,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
|
||||||
// Upload document
|
// Upload document
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
@ -198,7 +205,7 @@ test('should be able to create, send and sign a document', async ({ page }) => {
|
|||||||
await page.waitForURL(/\/documents\/\d+/);
|
await page.waitForURL(/\/documents\/\d+/);
|
||||||
|
|
||||||
// Set title
|
// Set title
|
||||||
await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Title').fill(documentTitle);
|
await page.getByLabel('Title').fill(documentTitle);
|
||||||
|
|
||||||
@ -207,8 +214,8 @@ test('should be able to create, send and sign a document', async ({ page }) => {
|
|||||||
// Add signers
|
// Add signers
|
||||||
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Email*').fill('user1@example.com');
|
await page.getByPlaceholder('Email').fill('user1@example.com');
|
||||||
await page.getByLabel('Name').fill('User 1');
|
await page.getByPlaceholder('Name').fill('User 1');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
@ -225,8 +232,9 @@ test('should be able to create, send and sign a document', async ({ page }) => {
|
|||||||
// Assert document was created
|
// Assert document was created
|
||||||
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
|
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
|
||||||
await page.getByRole('link', { name: documentTitle }).click();
|
await page.getByRole('link', { name: documentTitle }).click();
|
||||||
|
await page.waitForURL(/\/documents\/\d+/);
|
||||||
|
|
||||||
const url = await page.url().split('/');
|
const url = page.url().split('/');
|
||||||
const documentId = url[url.length - 1];
|
const documentId = url[url.length - 1];
|
||||||
|
|
||||||
const { token } = await getRecipientByEmail({
|
const { token } = await getRecipientByEmail({
|
||||||
@ -260,10 +268,12 @@ test('should be able to create, send with redirect url, sign a document and redi
|
|||||||
|
|
||||||
const documentTitle = `example-${Date.now()}.pdf`;
|
const documentTitle = `example-${Date.now()}.pdf`;
|
||||||
|
|
||||||
// Sign in
|
const user = await seedUser();
|
||||||
await page.getByLabel('Email').fill(TEST_USER.email);
|
|
||||||
await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password);
|
await apiSignin({
|
||||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
page,
|
||||||
|
email: user.email,
|
||||||
|
});
|
||||||
|
|
||||||
// Upload document
|
// Upload document
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
@ -280,18 +290,19 @@ test('should be able to create, send with redirect url, sign a document and redi
|
|||||||
// Wait to be redirected to the edit page
|
// Wait to be redirected to the edit page
|
||||||
await page.waitForURL(/\/documents\/\d+/);
|
await page.waitForURL(/\/documents\/\d+/);
|
||||||
|
|
||||||
// Set title
|
// Set title & advanced redirect
|
||||||
await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Title').fill(documentTitle);
|
await page.getByLabel('Title').fill(documentTitle);
|
||||||
|
await page.getByRole('button', { name: 'Advanced Options' }).click();
|
||||||
|
await page.getByLabel('Redirect URL').fill('https://documenso.com');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
// Add signers
|
// Add signers
|
||||||
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Email*').fill('user1@example.com');
|
await page.getByPlaceholder('Email').fill('user1@example.com');
|
||||||
await page.getByLabel('Name').fill('User 1');
|
await page.getByPlaceholder('Name').fill('User 1');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
@ -299,11 +310,6 @@ test('should be able to create, send with redirect url, sign a document and redi
|
|||||||
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Continue' }).click();
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
|
||||||
// Add subject and send
|
|
||||||
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
|
|
||||||
await page.getByRole('button', { name: 'Advanced Options' }).click();
|
|
||||||
await page.getByLabel('Redirect URL').fill('https://documenso.com');
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Send' }).click();
|
await page.getByRole('button', { name: 'Send' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
@ -311,8 +317,9 @@ test('should be able to create, send with redirect url, sign a document and redi
|
|||||||
// Assert document was created
|
// Assert document was created
|
||||||
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
|
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
|
||||||
await page.getByRole('link', { name: documentTitle }).click();
|
await page.getByRole('link', { name: documentTitle }).click();
|
||||||
|
await page.waitForURL(/\/documents\/\d+/);
|
||||||
|
|
||||||
const url = await page.url().split('/');
|
const url = page.url().split('/');
|
||||||
const documentId = url[url.length - 1];
|
const documentId = url[url.length - 1];
|
||||||
|
|
||||||
const { token } = await getRecipientByEmail({
|
const { token } = await getRecipientByEmail({
|
||||||
|
|||||||
@ -7,11 +7,8 @@ import { prisma } from '@documenso/prisma';
|
|||||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
|
||||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
import type { TRecipientActionAuth } from '../../types/document-auth';
|
||||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
import { isRecipientAuthorized } from './is-recipient-authorized';
|
|
||||||
import { sealDocument } from './seal-document';
|
import { sealDocument } from './seal-document';
|
||||||
import { sendPendingEmail } from './send-pending-email';
|
import { sendPendingEmail } from './send-pending-email';
|
||||||
|
|
||||||
@ -46,8 +43,6 @@ const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptio
|
|||||||
export const completeDocumentWithToken = async ({
|
export const completeDocumentWithToken = async ({
|
||||||
token,
|
token,
|
||||||
documentId,
|
documentId,
|
||||||
userId,
|
|
||||||
authOptions,
|
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: CompleteDocumentWithTokenOptions) => {
|
}: CompleteDocumentWithTokenOptions) => {
|
||||||
'use server';
|
'use server';
|
||||||
@ -79,22 +74,24 @@ export const completeDocumentWithToken = async ({
|
|||||||
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
// Document reauth for completing documents is currently not required.
|
||||||
documentAuth: document.authOptions,
|
|
||||||
recipientAuth: recipient.authOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isValid = await isRecipientAuthorized({
|
// const { derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
||||||
type: 'ACTION',
|
// documentAuth: document.authOptions,
|
||||||
document: document,
|
// recipientAuth: recipient.authOptions,
|
||||||
recipient: recipient,
|
// });
|
||||||
userId,
|
|
||||||
authOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isValid) {
|
// const isValid = await isRecipientAuthorized({
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
// type: 'ACTION',
|
||||||
}
|
// document: document,
|
||||||
|
// recipient: recipient,
|
||||||
|
// userId,
|
||||||
|
// authOptions,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!isValid) {
|
||||||
|
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
||||||
|
// }
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.$transaction(async (tx) => {
|
||||||
await tx.recipient.update({
|
await tx.recipient.update({
|
||||||
@ -121,7 +118,7 @@ export const completeDocumentWithToken = async ({
|
|||||||
recipientName: recipient.name,
|
recipientName: recipient.name,
|
||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
recipientRole: recipient.role,
|
recipientRole: recipient.role,
|
||||||
actionAuth: derivedRecipientActionAuth || undefined,
|
// actionAuth: derivedRecipientActionAuth || undefined,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -79,18 +79,28 @@ export const signFieldWithToken = async ({
|
|||||||
throw new Error(`Field ${fieldId} has no recipientId`);
|
throw new Error(`Field ${fieldId} has no recipientId`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
let { derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
||||||
documentAuth: document.authOptions,
|
documentAuth: document.authOptions,
|
||||||
recipientAuth: recipient.authOptions,
|
recipientAuth: recipient.authOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isValid = await isRecipientAuthorized({
|
// Override all non-signature fields to not require any auth.
|
||||||
type: 'ACTION',
|
if (field.type !== FieldType.SIGNATURE) {
|
||||||
document: document,
|
derivedRecipientActionAuth = null;
|
||||||
recipient: recipient,
|
}
|
||||||
userId,
|
|
||||||
authOptions,
|
let isValid = true;
|
||||||
});
|
|
||||||
|
// Only require auth on signature fields for now.
|
||||||
|
if (field.type === FieldType.SIGNATURE) {
|
||||||
|
isValid = await isRecipientAuthorized({
|
||||||
|
type: 'ACTION',
|
||||||
|
document: document,
|
||||||
|
recipient: recipient,
|
||||||
|
userId,
|
||||||
|
authOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
|
||||||
|
|
||||||
import { prisma } from '..';
|
|
||||||
|
|
||||||
//
|
|
||||||
// https://github.com/documenso/documenso/pull/713
|
|
||||||
//
|
|
||||||
|
|
||||||
const PULL_REQUEST_NUMBER = 718;
|
|
||||||
|
|
||||||
const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`;
|
|
||||||
|
|
||||||
export const TEST_USER = {
|
|
||||||
name: 'User 1',
|
|
||||||
email: `user1@${EMAIL_DOMAIN}`,
|
|
||||||
password: 'Password123',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const seedDatabase = async () => {
|
|
||||||
await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
name: TEST_USER.name,
|
|
||||||
email: TEST_USER.email,
|
|
||||||
password: hashSync(TEST_USER.password),
|
|
||||||
emailVerified: new Date(),
|
|
||||||
url: TEST_USER.email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user