mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 17:35:05 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { HonoEnv } from '../../../router';
|
||||
import { handleEnvelopeItemPdfRequest } from './get-envelope-item-pdf';
|
||||
|
||||
const route = new Hono<HonoEnv>();
|
||||
|
||||
const ZGetEnvelopeItemByTokenParamsSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
envelopeId: z.string().min(1),
|
||||
envelopeItemId: z.string().min(1),
|
||||
documentDataId: z.string().min(1),
|
||||
version: z.enum(['initial', 'current']),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns a PDF file for an envelope item using a token.
|
||||
*/
|
||||
route.get(
|
||||
'/token/:token/envelope/:envelopeId/envelopeItem/:envelopeItemId/dataId/:documentDataId/:version/item.pdf',
|
||||
sValidator('param', ZGetEnvelopeItemByTokenParamsSchema),
|
||||
async (c) => {
|
||||
const { token, envelopeId, envelopeItemId, documentDataId, version } = c.req.valid('param');
|
||||
|
||||
if (!token) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
// Recipient token based query.
|
||||
let envelopeItemWhereQuery: Prisma.EnvelopeItemWhereInput = {
|
||||
id: envelopeItemId,
|
||||
documentDataId,
|
||||
envelope: {
|
||||
id: envelopeId,
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// QR token based query.
|
||||
if (token.startsWith('qr_')) {
|
||||
envelopeItemWhereQuery = {
|
||||
id: envelopeItemId,
|
||||
documentDataId,
|
||||
envelope: {
|
||||
id: envelopeId,
|
||||
qrToken: token,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Validate envelope access.
|
||||
const envelopeItem = await prisma.envelopeItem.findFirst({
|
||||
where: envelopeItemWhereQuery,
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelopeItem) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
return await handleEnvelopeItemPdfRequest({
|
||||
c,
|
||||
envelopeItem,
|
||||
version,
|
||||
cacheStrategy: 'private',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default route;
|
||||
@@ -0,0 +1,156 @@
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import type { DocumentDataVersion } from '@documenso/lib/types/document';
|
||||
import { sha256 } from '@documenso/lib/universal/crypto';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import type { DocumentData, EnvelopeItem } from '@prisma/client';
|
||||
import { type Context, Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { HonoEnv } from '../../../router';
|
||||
import { checkEnvelopeFileAccess } from '../files.helpers';
|
||||
|
||||
const route = new Hono<HonoEnv>();
|
||||
|
||||
const ZGetEnvelopeItemPdfRequestParamsSchema = z.object({
|
||||
envelopeId: z.string().min(1),
|
||||
envelopeItemId: z.string().min(1),
|
||||
documentDataId: z.string().min(1),
|
||||
version: z.enum(['initial', 'current']),
|
||||
});
|
||||
|
||||
const ZGetEnvelopeItemPdfRequestQuerySchema = z.object({
|
||||
presignToken: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns a PDF file for an envelope item.
|
||||
*/
|
||||
route.get(
|
||||
'/envelope/:envelopeId/envelopeItem/:envelopeItemId/dataId/:documentDataId/:version/item.pdf',
|
||||
sValidator('param', ZGetEnvelopeItemPdfRequestParamsSchema),
|
||||
sValidator('query', ZGetEnvelopeItemPdfRequestQuerySchema),
|
||||
async (c) => {
|
||||
const { envelopeId, envelopeItemId, documentDataId, version } = c.req.valid('param');
|
||||
|
||||
const { presignToken } = c.req.valid('query');
|
||||
|
||||
const session = await getOptionalSession(c);
|
||||
|
||||
let userId = session.user?.id;
|
||||
|
||||
// Check presignToken if provided
|
||||
if (presignToken) {
|
||||
const verifiedToken = await verifyEmbeddingPresignToken({
|
||||
token: presignToken,
|
||||
}).catch(() => undefined);
|
||||
|
||||
userId = verifiedToken?.userId;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
// Note: We authenticate whether the user can access this in the `getTeamById` below.
|
||||
const envelopeItem = await prisma.envelopeItem.findFirst({
|
||||
where: {
|
||||
id: envelopeItemId,
|
||||
envelopeId,
|
||||
documentDataId,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
envelope: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
teamId: true,
|
||||
templateType: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelopeItem) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
// Check whether the user has access to the document.
|
||||
const hasAccess = await checkEnvelopeFileAccess({
|
||||
userId,
|
||||
teamId: envelopeItem.envelope.teamId,
|
||||
envelopeType: envelopeItem.envelope.type,
|
||||
templateType: envelopeItem.envelope.templateType,
|
||||
});
|
||||
|
||||
if (!hasAccess) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
return await handleEnvelopeItemPdfRequest({
|
||||
c,
|
||||
envelopeItem,
|
||||
version,
|
||||
cacheStrategy: 'private',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
type HandleEnvelopeItemPdfRequestOptions = {
|
||||
c: Context<HonoEnv>;
|
||||
envelopeItem: EnvelopeItem & {
|
||||
documentData: DocumentData;
|
||||
};
|
||||
version: DocumentDataVersion;
|
||||
|
||||
/**
|
||||
* The type of cache strategy to use.
|
||||
*
|
||||
* For access via tokens, we can use a public cache to allow the CDN to cache it.
|
||||
*
|
||||
* For access via session, we must use a private cache.
|
||||
*/
|
||||
cacheStrategy: 'private' | 'public';
|
||||
};
|
||||
|
||||
export const handleEnvelopeItemPdfRequest = async ({
|
||||
c,
|
||||
envelopeItem,
|
||||
version,
|
||||
cacheStrategy,
|
||||
}: HandleEnvelopeItemPdfRequestOptions) => {
|
||||
// Determine which PDF data to use based on version requested.
|
||||
const documentDataToUse =
|
||||
version === 'current' ? envelopeItem.documentData.data : envelopeItem.documentData.initialData;
|
||||
|
||||
const etag = Buffer.from(sha256(documentDataToUse)).toString('hex');
|
||||
|
||||
if (c.req.header('If-None-Match') === etag) {
|
||||
return c.status(304);
|
||||
}
|
||||
|
||||
const file = await getFileServerSide({
|
||||
type: envelopeItem.documentData.type,
|
||||
data: documentDataToUse,
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!file) {
|
||||
return c.json({ error: 'Not found' }, 404);
|
||||
}
|
||||
|
||||
// Note: Only set these headers on success.
|
||||
c.header('Content-Type', 'application/pdf');
|
||||
c.header('ETag', etag);
|
||||
c.header('Cache-Control', `${cacheStrategy}, max-age=31536000, immutable`);
|
||||
|
||||
return c.body(file);
|
||||
};
|
||||
|
||||
export default route;
|
||||
Reference in New Issue
Block a user