mirror of
https://github.com/documenso/documenso.git
synced 2026-07-12 14:05:20 +10:00
chore: remove unnecessary constant extraction and defensive optional chaining
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
EnvelopeRenderProvider,
|
||||
useCurrentEnvelopeRender,
|
||||
} from '@documenso/lib/client-only/providers/envelope-render-provider';
|
||||
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import PDFViewerKonvaLazy from '@documenso/ui/components/pdf-viewer/pdf-viewer-konva-lazy';
|
||||
@@ -52,9 +53,12 @@ export const DocumentCertificateQRView = ({
|
||||
completedDate,
|
||||
token,
|
||||
}: DocumentCertificateQRViewProps) => {
|
||||
const { data: documentViaUser } = trpc.document.get.useQuery({
|
||||
documentId,
|
||||
});
|
||||
const { sessionData } = useOptionalSession();
|
||||
|
||||
const { data: documentViaUser } = trpc.document.get.useQuery(
|
||||
{ documentId },
|
||||
{ enabled: !!sessionData?.user },
|
||||
);
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(() => !!documentViaUser);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { CheckCircle2, Clock8, DownloadIcon, EyeIcon, Loader2 } from 'lucide-react';
|
||||
@@ -106,7 +105,6 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
}
|
||||
|
||||
export default function CompletedSigningPage({ loaderData }: Route.ComponentProps) {
|
||||
const { _ } = useLingui();
|
||||
const revalidator = useRevalidator();
|
||||
|
||||
const { sessionData } = useOptionalSession();
|
||||
@@ -159,8 +157,12 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
||||
});
|
||||
|
||||
setQrRetryCount(nextRetryCount);
|
||||
await revalidator.revalidate();
|
||||
setIsRetryingQrLink(false);
|
||||
|
||||
try {
|
||||
await revalidator.revalidate();
|
||||
} finally {
|
||||
setIsRetryingQrLink(false);
|
||||
}
|
||||
}, [isDocumentAccessValid, qrRetryCount, revalidator]);
|
||||
|
||||
const isFullyCompleted = isDocumentAccessValid && signingStatus === 'COMPLETED';
|
||||
@@ -296,7 +298,7 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
||||
<div className="mt-8 flex w-full max-w-xs flex-col items-stretch gap-4 md:w-auto md:max-w-none md:flex-row md:items-center">
|
||||
{isFullyCompleted && hasQrToken && (
|
||||
<Button asChild variant="secondary" className="w-full">
|
||||
<Link to={`/share/${document.qrToken}`}>
|
||||
<Link to={`/share/${document.qrToken}`} target="_blank" rel="noopener noreferrer">
|
||||
<EyeIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>View completed PDF</Trans>
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Link, isRouteErrorResponse, redirect, useLoaderData, useRouteError } from 'react-router';
|
||||
import { Link, isRouteErrorResponse, redirect, useLoaderData } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -64,27 +65,23 @@ export function meta({ params: { slug }, loaderData }: Route.MetaArgs) {
|
||||
|
||||
type TQrShareErrorPayload = {
|
||||
code: string;
|
||||
message: string;
|
||||
correlationId: string;
|
||||
};
|
||||
|
||||
const createQrShareErrorResponse = ({
|
||||
status,
|
||||
code,
|
||||
message,
|
||||
correlationId,
|
||||
headers,
|
||||
}: {
|
||||
status: number;
|
||||
code: string;
|
||||
message: string;
|
||||
correlationId: string;
|
||||
headers?: HeadersInit;
|
||||
}) => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code,
|
||||
message,
|
||||
correlationId,
|
||||
} satisfies TQrShareErrorPayload),
|
||||
{
|
||||
@@ -106,11 +103,7 @@ const parseQrShareErrorPayload = (value: unknown): TQrShareErrorPayload | null =
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
|
||||
if (
|
||||
typeof parsed.code === 'string' &&
|
||||
typeof parsed.message === 'string' &&
|
||||
typeof parsed.correlationId === 'string'
|
||||
) {
|
||||
if (typeof parsed.code === 'string' && typeof parsed.correlationId === 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -148,7 +141,6 @@ export const loader = async ({ request, params: { slug } }: Route.LoaderArgs) =>
|
||||
throw createQrShareErrorResponse({
|
||||
status: 429,
|
||||
code: 'QR_VIEW_RATE_LIMITED',
|
||||
message: 'Too many requests. Please try again shortly.',
|
||||
correlationId,
|
||||
headers: {
|
||||
'Retry-After': retryAfter,
|
||||
@@ -180,27 +172,24 @@ export const loader = async ({ request, params: { slug } }: Route.LoaderArgs) =>
|
||||
} catch (error) {
|
||||
const appError = AppError.parseError(error);
|
||||
|
||||
let status = 500;
|
||||
let code = 'QR_VIEW_INTERNAL_ERROR';
|
||||
let message = 'An unexpected error occurred while opening this document.';
|
||||
|
||||
if (appError.code === AppErrorCode.NOT_FOUND) {
|
||||
status = 404;
|
||||
code = 'QR_VIEW_NOT_FOUND';
|
||||
message = 'The shared document could not be found.';
|
||||
} else if (appError.code === AppErrorCode.INVALID_REQUEST || appError.statusCode === 409) {
|
||||
status = 409;
|
||||
code = 'QR_VIEW_NOT_COMPLETED';
|
||||
message = 'This document is not fully completed yet.';
|
||||
} else if (appError.code === AppErrorCode.UNAUTHORIZED && appError.statusCode === 403) {
|
||||
status = 403;
|
||||
code = 'QR_VIEW_DISABLED';
|
||||
message = 'Public completed-document access is currently disabled.';
|
||||
} else if (appError.code === AppErrorCode.UNAUTHORIZED) {
|
||||
status = 401;
|
||||
code = 'QR_VIEW_UNAUTHORIZED';
|
||||
message = 'You are not authorized to view this document.';
|
||||
}
|
||||
const { status, code } = match(appError)
|
||||
.when(
|
||||
(e) => e.code === AppErrorCode.NOT_FOUND,
|
||||
() => ({ status: 404, code: 'QR_VIEW_NOT_FOUND' }),
|
||||
)
|
||||
.when(
|
||||
(e) => e.code === AppErrorCode.INVALID_REQUEST,
|
||||
() => ({ status: 409, code: 'QR_VIEW_NOT_COMPLETED' }),
|
||||
)
|
||||
.when(
|
||||
(e) => e.code === AppErrorCode.UNAUTHORIZED && e.statusCode === 403,
|
||||
() => ({ status: 403, code: 'QR_VIEW_DISABLED' }),
|
||||
)
|
||||
.when(
|
||||
(e) => e.code === AppErrorCode.UNAUTHORIZED,
|
||||
() => ({ status: 401, code: 'QR_VIEW_UNAUTHORIZED' }),
|
||||
)
|
||||
.otherwise(() => ({ status: 500, code: 'QR_VIEW_INTERNAL_ERROR' }));
|
||||
|
||||
logger.warn({
|
||||
msg: 'QR share access denied',
|
||||
@@ -216,7 +205,6 @@ export const loader = async ({ request, params: { slug } }: Route.LoaderArgs) =>
|
||||
throw createQrShareErrorResponse({
|
||||
status,
|
||||
code,
|
||||
message,
|
||||
correlationId,
|
||||
});
|
||||
}
|
||||
@@ -250,11 +238,23 @@ export default function SharePage() {
|
||||
);
|
||||
}
|
||||
|
||||
return <div></div>;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const error = useRouteError();
|
||||
const qrShareErrorMessage = (code: string | undefined) =>
|
||||
match(code)
|
||||
.with('QR_VIEW_NOT_FOUND', () => <Trans>The shared document could not be found.</Trans>)
|
||||
.with('QR_VIEW_NOT_COMPLETED', () => <Trans>This document is not fully completed yet.</Trans>)
|
||||
.with('QR_VIEW_DISABLED', () => (
|
||||
<Trans>Public completed-document access is currently disabled.</Trans>
|
||||
))
|
||||
.with('QR_VIEW_UNAUTHORIZED', () => (
|
||||
<Trans>You are not authorized to view this document.</Trans>
|
||||
))
|
||||
.with('QR_VIEW_RATE_LIMITED', () => <Trans>Too many requests. Please try again shortly.</Trans>)
|
||||
.otherwise(() => <Trans>Something went wrong while opening this shared view.</Trans>);
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
const payload = isRouteErrorResponse(error) ? parseQrShareErrorPayload(error.data) : null;
|
||||
|
||||
return (
|
||||
@@ -266,11 +266,7 @@ export function ErrorBoundary() {
|
||||
<h2 className="text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||
<Trans>Unable to Open Document</Trans>
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{payload?.message ?? (
|
||||
<Trans>Something went wrong while opening this shared view.</Trans>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{qrShareErrorMessage(payload?.code)}</p>
|
||||
|
||||
{payload?.correlationId && (
|
||||
<p className="mt-4 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { DocumentStatus } from '@prisma/client';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { Hono } from 'hono';
|
||||
import type { Context } from 'hono';
|
||||
import { DocumentStatus, type Prisma } from '@prisma/client';
|
||||
import { type Context, Hono } from 'hono';
|
||||
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
@@ -58,7 +56,7 @@ const envelopeItemTokenInclude = {
|
||||
} as const;
|
||||
|
||||
const maybeApplyQrRateLimit = async (c: Context<HonoEnv>, token: string) => {
|
||||
let ip = 'unknown';
|
||||
let ip: string;
|
||||
|
||||
try {
|
||||
ip = getIpAddress(c.req.raw);
|
||||
@@ -74,6 +72,65 @@ const maybeApplyQrRateLimit = async (c: Context<HonoEnv>, token: string) => {
|
||||
return rateLimitResponse(c, result);
|
||||
};
|
||||
|
||||
const getEnvelopeItemByToken = async (
|
||||
c: Context<HonoEnv>,
|
||||
token: string,
|
||||
envelopeItemId: string,
|
||||
) => {
|
||||
const isQrToken = token.startsWith('qr_');
|
||||
|
||||
if (isQrToken) {
|
||||
const limited = await maybeApplyQrRateLimit(c, token);
|
||||
|
||||
if (limited) {
|
||||
return { limited } as const;
|
||||
}
|
||||
}
|
||||
|
||||
const envelopeWhereQuery: Prisma.EnvelopeItemWhereUniqueInput = isQrToken
|
||||
? {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
qrToken: token,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
}
|
||||
: {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const envelopeItem = await prisma.envelopeItem.findUnique({
|
||||
where: envelopeWhereQuery,
|
||||
include: envelopeItemTokenInclude,
|
||||
});
|
||||
|
||||
if (!envelopeItem) {
|
||||
return { error: c.json({ error: 'Envelope item not found' }, 404) } as const;
|
||||
}
|
||||
|
||||
if (isQrToken && !isPublicDocumentAccessEnabled(envelopeItem.envelope.team)) {
|
||||
return {
|
||||
error: c.json(
|
||||
{ error: 'Public completed-document access is disabled for this document' },
|
||||
403,
|
||||
),
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (!envelopeItem.documentData) {
|
||||
return { error: c.json({ error: 'Document data not found' }, 404) } as const;
|
||||
}
|
||||
|
||||
return { envelopeItem, isQrToken } as const;
|
||||
};
|
||||
|
||||
export const filesRoute = new Hono<HonoEnv>()
|
||||
/**
|
||||
* Uploads a document file to the appropriate storage location and creates
|
||||
@@ -270,61 +327,21 @@ export const filesRoute = new Hono<HonoEnv>()
|
||||
sValidator('param', ZGetEnvelopeItemFileTokenRequestParamsSchema),
|
||||
async (c) => {
|
||||
const { token, envelopeItemId } = c.req.valid('param');
|
||||
const isQrToken = token.startsWith('qr_');
|
||||
|
||||
if (isQrToken) {
|
||||
const limited = await maybeApplyQrRateLimit(c, token);
|
||||
const result = await getEnvelopeItemByToken(c, token, envelopeItemId);
|
||||
|
||||
if (limited) {
|
||||
return limited;
|
||||
}
|
||||
if ('limited' in result) {
|
||||
return result.limited;
|
||||
}
|
||||
|
||||
let envelopeWhereQuery: Prisma.EnvelopeItemWhereUniqueInput = {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (isQrToken) {
|
||||
envelopeWhereQuery = {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
qrToken: token,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const envelopeItem = await prisma.envelopeItem.findUnique({
|
||||
where: envelopeWhereQuery,
|
||||
include: envelopeItemTokenInclude,
|
||||
});
|
||||
|
||||
if (!envelopeItem) {
|
||||
return c.json({ error: 'Envelope item not found' }, 404);
|
||||
}
|
||||
|
||||
if (isQrToken && !isPublicDocumentAccessEnabled(envelopeItem.envelope.team)) {
|
||||
return c.json(
|
||||
{ error: 'Public completed-document access is disabled for this document' },
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
if (!envelopeItem.documentData) {
|
||||
return c.json({ error: 'Document data not found' }, 404);
|
||||
if ('error' in result) {
|
||||
return result.error;
|
||||
}
|
||||
|
||||
return await handleEnvelopeItemFileRequest({
|
||||
title: envelopeItem.title,
|
||||
status: envelopeItem.envelope.status,
|
||||
documentData: envelopeItem.documentData,
|
||||
title: result.envelopeItem.title,
|
||||
status: result.envelopeItem.envelope.status,
|
||||
documentData: result.envelopeItem.documentData!,
|
||||
version: 'signed',
|
||||
isDownload: false,
|
||||
context: c,
|
||||
@@ -336,62 +353,23 @@ export const filesRoute = new Hono<HonoEnv>()
|
||||
sValidator('param', ZGetEnvelopeItemFileTokenDownloadRequestParamsSchema),
|
||||
async (c) => {
|
||||
const { token, envelopeItemId, version } = c.req.valid('param');
|
||||
const isQrToken = token.startsWith('qr_');
|
||||
const effectiveVersion = isQrToken ? 'signed' : version;
|
||||
|
||||
if (isQrToken) {
|
||||
const limited = await maybeApplyQrRateLimit(c, token);
|
||||
const result = await getEnvelopeItemByToken(c, token, envelopeItemId);
|
||||
|
||||
if (limited) {
|
||||
return limited;
|
||||
}
|
||||
if ('limited' in result) {
|
||||
return result.limited;
|
||||
}
|
||||
|
||||
let envelopeWhereQuery: Prisma.EnvelopeItemWhereUniqueInput = {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (isQrToken) {
|
||||
envelopeWhereQuery = {
|
||||
id: envelopeItemId,
|
||||
envelope: {
|
||||
qrToken: token,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
};
|
||||
if ('error' in result) {
|
||||
return result.error;
|
||||
}
|
||||
|
||||
const envelopeItem = await prisma.envelopeItem.findUnique({
|
||||
where: envelopeWhereQuery,
|
||||
include: envelopeItemTokenInclude,
|
||||
});
|
||||
|
||||
if (!envelopeItem) {
|
||||
return c.json({ error: 'Envelope item not found' }, 404);
|
||||
}
|
||||
|
||||
if (isQrToken && !isPublicDocumentAccessEnabled(envelopeItem.envelope.team)) {
|
||||
return c.json(
|
||||
{ error: 'Public completed-document access is disabled for this document' },
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
if (!envelopeItem.documentData) {
|
||||
return c.json({ error: 'Document data not found' }, 404);
|
||||
}
|
||||
const effectiveVersion = result.isQrToken ? 'signed' : version;
|
||||
|
||||
return await handleEnvelopeItemFileRequest({
|
||||
title: envelopeItem.title,
|
||||
status: envelopeItem.envelope.status,
|
||||
documentData: envelopeItem.documentData,
|
||||
title: result.envelopeItem.title,
|
||||
status: result.envelopeItem.envelope.status,
|
||||
documentData: result.envelopeItem.documentData!,
|
||||
version: effectiveVersion,
|
||||
isDownload: true,
|
||||
context: c,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { signSignaturePad } from '../fixtures/signature';
|
||||
|
||||
test.describe.configure({ mode: 'parallel', timeout: 60000 });
|
||||
|
||||
const completeSigningForRecipient = async ({
|
||||
page,
|
||||
token,
|
||||
@@ -113,7 +115,7 @@ test('[DOCUMENT_AUTH]: recipient cannot access final view action before full com
|
||||
test('[DOCUMENT_AUTH]: invalid QR share token is denied', async ({ page }) => {
|
||||
await page.goto('/share/qr_invalid_token');
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Unable to open this document' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Unable to Open Document' })).toBeVisible();
|
||||
await expect(page.getByText('Support code:')).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -186,7 +188,7 @@ test('[DOCUMENT_AUTH]: disabling public completed-document access revokes QR sha
|
||||
});
|
||||
|
||||
await page.goto(`/share/${completedEnvelope.qrToken}`);
|
||||
await expect(page.getByRole('heading', { name: 'Unable to open this document' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Unable to Open Document' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Public completed-document access is currently disabled.'),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -23,7 +23,6 @@ export const getDocumentByAccessToken = async ({ token }: GetDocumentByAccessTok
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
qrToken: token,
|
||||
},
|
||||
// Do not provide extra information that is not needed.
|
||||
select: {
|
||||
id: true,
|
||||
secondaryId: true,
|
||||
@@ -96,10 +95,13 @@ export const getDocumentByAccessToken = async ({ token }: GetDocumentByAccessTok
|
||||
});
|
||||
}
|
||||
|
||||
const firstDocumentData = result.envelopeItems[0].documentData;
|
||||
const firstEnvelopeItem = result.envelopeItems[0];
|
||||
|
||||
if (!firstDocumentData) {
|
||||
throw new Error('Missing document data');
|
||||
if (!firstEnvelopeItem?.documentData) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Missing document data for QR token',
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -109,6 +111,6 @@ export const getDocumentByAccessToken = async ({ token }: GetDocumentByAccessTok
|
||||
completedAt: result.completedAt,
|
||||
envelopeItems: result.envelopeItems,
|
||||
recipientCount: result._count.recipients,
|
||||
documentTeamUrl: result.team?.url ?? '',
|
||||
documentTeamUrl: result.team.url,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CREATE INDEX "Envelope_qrToken_idx" ON "Envelope"("qrToken");
|
||||
@@ -437,6 +437,7 @@ model Envelope {
|
||||
@@index([teamId])
|
||||
@@index([folderId])
|
||||
@@index([createdAt])
|
||||
@@index([qrToken])
|
||||
}
|
||||
|
||||
model EnvelopeItem {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { isPublicDocumentAccessEnabled } from '@documenso/lib/universal/document-access';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { maybeAuthenticatedProcedure } from '../trpc';
|
||||
@@ -62,15 +63,15 @@ const handleGetEnvelopeItemsByToken = async ({
|
||||
envelopeId: string;
|
||||
token: string;
|
||||
}) => {
|
||||
const isQrToken = token.startsWith('qr_');
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
type: EnvelopeType.DOCUMENT, // You cannot get template envelope items by token.
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
...(isQrToken
|
||||
? { qrToken: token, status: DocumentStatus.COMPLETED }
|
||||
: { recipients: { some: { token } } }),
|
||||
},
|
||||
include: {
|
||||
envelopeItems: {
|
||||
@@ -78,6 +79,20 @@ const handleGetEnvelopeItemsByToken = async ({
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
include: {
|
||||
teamGlobalSettings: {
|
||||
select: { allowPublicCompletedDocumentAccess: true },
|
||||
},
|
||||
organisation: {
|
||||
include: {
|
||||
organisationGlobalSettings: {
|
||||
select: { allowPublicCompletedDocumentAccess: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,6 +102,12 @@ const handleGetEnvelopeItemsByToken = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (isQrToken && !isPublicDocumentAccessEnabled(envelope.team)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Public completed-document access is disabled',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
envelopeItems: envelope.envelopeItems,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user