feat: sealing robustness (#1170)
A series of changes to improve sealing robustness avoiding errors that have appeared during monitoring. Additionally handles the recently published CVE affecting the `pdfjs-dist` library.
This commit is contained in:
@@ -18,6 +18,10 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
|
|||||||
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FONT_NOTO_SANS_BYTES = fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../packages/assets/fonts/noto-sans.ttf'),
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
experimental: {
|
experimental: {
|
||||||
@@ -38,6 +42,7 @@ const config = {
|
|||||||
env: {
|
env: {
|
||||||
NEXT_PUBLIC_PROJECT: 'marketing',
|
NEXT_PUBLIC_PROJECT: 'marketing',
|
||||||
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
||||||
|
FONT_NOTO_SANS_URI: `data:font/ttf;base64,${FONT_NOTO_SANS_BYTES.toString('base64')}`,
|
||||||
},
|
},
|
||||||
modularizeImports: {
|
modularizeImports: {
|
||||||
'lucide-react': {
|
'lucide-react': {
|
||||||
|
|||||||
2
apps/marketing/public/pdf.worker.min.js
vendored
2
apps/marketing/public/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -18,6 +18,10 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
|
|||||||
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FONT_NOTO_SANS_BYTES = fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../packages/assets/fonts/noto-sans.ttf'),
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
||||||
@@ -42,6 +46,7 @@ const config = {
|
|||||||
APP_VERSION: version,
|
APP_VERSION: version,
|
||||||
NEXT_PUBLIC_PROJECT: 'web',
|
NEXT_PUBLIC_PROJECT: 'web',
|
||||||
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
||||||
|
FONT_NOTO_SANS_URI: `data:font/ttf;base64,${FONT_NOTO_SANS_BYTES.toString('base64')}`,
|
||||||
},
|
},
|
||||||
modularizeImports: {
|
modularizeImports: {
|
||||||
'lucide-react': {
|
'lucide-react': {
|
||||||
|
|||||||
56591
apps/web/public/pdf.worker.min.js
vendored
56591
apps/web/public/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { type Document, DocumentStatus } from '@documenso/prisma/client';
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
|
import { type Document, SigningStatus } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@@ -17,9 +18,10 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
|||||||
export type AdminActionsProps = {
|
export type AdminActionsProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
document: Document;
|
document: Document;
|
||||||
|
recipients: Recipient[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminActions = ({ className, document }: AdminActionsProps) => {
|
export const AdminActions = ({ className, document, recipients }: AdminActionsProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { mutate: resealDocument, isLoading: isResealDocumentLoading } =
|
const { mutate: resealDocument, isLoading: isResealDocumentLoading } =
|
||||||
@@ -47,7 +49,9 @@ export const AdminActions = ({ className, document }: AdminActionsProps) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
loading={isResealDocumentLoading}
|
loading={isResealDocumentLoading}
|
||||||
disabled={document.status !== DocumentStatus.COMPLETED}
|
disabled={recipients.some(
|
||||||
|
(recipient) => recipient.signingStatus !== SigningStatus.SIGNED,
|
||||||
|
)}
|
||||||
onClick={() => resealDocument({ id: document.id })}
|
onClick={() => resealDocument({ id: document.id })}
|
||||||
>
|
>
|
||||||
Reseal document
|
Reseal document
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument
|
|||||||
|
|
||||||
<h2 className="text-lg font-semibold">Admin Actions</h2>
|
<h2 className="text-lg font-semibold">Admin Actions</h2>
|
||||||
|
|
||||||
<AdminActions className="mt-2" document={document} />
|
<AdminActions className="mt-2" document={document} recipients={document.Recipient} />
|
||||||
|
|
||||||
<hr className="my-4" />
|
<hr className="my-4" />
|
||||||
<h2 className="text-lg font-semibold">Recipients</h2>
|
<h2 className="text-lg font-semibold">Recipients</h2>
|
||||||
|
|||||||
108
package-lock.json
generated
108
package-lock.json
generated
@@ -17536,6 +17536,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
|
||||||
"integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
|
"integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
|
||||||
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -17580,18 +17581,15 @@
|
|||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
"node_modules/pdfjs-dist": {
|
"node_modules/pdfjs-dist": {
|
||||||
"version": "3.6.172",
|
"version": "3.11.174",
|
||||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.6.172.tgz",
|
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
|
||||||
"integrity": "sha512-bfOhCg+S9DXh/ImWhWYTOiq3aVMFSCvzGiBzsIJtdMC71kVWDBw7UXr32xh0y56qc5wMVylIeqV3hBaRsu+e+w==",
|
"integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
|
||||||
"dependencies": {
|
|
||||||
"path2d-polyfill": "^2.0.1",
|
|
||||||
"web-streams-polyfill": "^3.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"canvas": "^2.11.2"
|
"canvas": "^2.11.2",
|
||||||
|
"path2d-polyfill": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/peberminta": {
|
"node_modules/peberminta": {
|
||||||
@@ -19011,42 +19009,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||||
},
|
},
|
||||||
"node_modules/react-pdf": {
|
|
||||||
"version": "7.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.3.3.tgz",
|
|
||||||
"integrity": "sha512-d7WAxcsjOogJfJ+I+zX/mdip3VjR1yq/yDa4hax4XbQVjbbbup6rqs4c8MGx0MLSnzob17TKp1t4CsNbDZ6GeQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"clsx": "^2.0.0",
|
|
||||||
"make-cancellable-promise": "^1.3.1",
|
|
||||||
"make-event-props": "^1.6.0",
|
|
||||||
"merge-refs": "^1.2.1",
|
|
||||||
"pdfjs-dist": "3.6.172",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"tiny-invariant": "^1.0.0",
|
|
||||||
"tiny-warning": "^1.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-pdf/node_modules/clsx": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-property": {
|
"node_modules/react-property": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
|
||||||
@@ -21357,11 +21319,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||||
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
|
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
|
||||||
},
|
},
|
||||||
"node_modules/tiny-warning": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
|
||||||
},
|
|
||||||
"node_modules/tinybench": {
|
"node_modules/tinybench": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
|
||||||
@@ -22986,6 +22943,14 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/warning": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||||
@@ -25390,11 +25355,13 @@
|
|||||||
"lucide-react": "^0.279.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.2",
|
"luxon": "^3.4.2",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.11.174",
|
||||||
|
"react": "18.2.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-day-picker": "^8.7.1",
|
"react-day-picker": "^8.7.1",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
"react-pdf": "7.3.3",
|
"react-pdf": "7.7.3",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
@@ -25411,6 +25378,43 @@
|
|||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/ui/node_modules/react-pdf": {
|
||||||
|
"version": "7.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.3.tgz",
|
||||||
|
"integrity": "sha512-a2VfDl8hiGjugpqezBTUzJHYLNB7IS7a2t7GD52xMI9xHg8LdVaTMsnM9ZlNmKadnStT/tvX5IfV0yLn+JvYmw==",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"dequal": "^2.0.3",
|
||||||
|
"make-cancellable-promise": "^1.3.1",
|
||||||
|
"make-event-props": "^1.6.0",
|
||||||
|
"merge-refs": "^1.2.1",
|
||||||
|
"pdfjs-dist": "3.11.174",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"tiny-invariant": "^1.0.0",
|
||||||
|
"warning": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/ui/node_modules/react-pdf/node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/ui/node_modules/typescript": {
|
"packages/ui/node_modules/typescript": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
|
|||||||
BIN
packages/assets/fonts/noto-sans.ttf
Normal file
BIN
packages/assets/fonts/noto-sans.ttf
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
import { APP_BASE_URL } from './app';
|
import { APP_BASE_URL } from './app';
|
||||||
|
|
||||||
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
export const DEFAULT_STANDARD_FONT_SIZE = 12;
|
||||||
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
||||||
|
|
||||||
export const MIN_STANDARD_FONT_SIZE = 8;
|
export const MIN_STANDARD_FONT_SIZE = 8;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { getFile } from '../../universal/upload/get-file';
|
|||||||
import { putPdfFile } from '../../universal/upload/put-file';
|
import { putPdfFile } from '../../universal/upload/put-file';
|
||||||
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
|
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
|
||||||
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
||||||
|
import { flattenForm } from '../pdf/flatten-form';
|
||||||
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
||||||
import { normalizeSignatureAppearances } from '../pdf/normalize-signature-appearances';
|
import { normalizeSignatureAppearances } from '../pdf/normalize-signature-appearances';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
@@ -101,7 +102,7 @@ export const sealDocument = async ({
|
|||||||
|
|
||||||
// Normalize and flatten layers that could cause issues with the signature
|
// Normalize and flatten layers that could cause issues with the signature
|
||||||
normalizeSignatureAppearances(doc);
|
normalizeSignatureAppearances(doc);
|
||||||
doc.getForm().flatten();
|
flattenForm(doc);
|
||||||
flattenAnnotations(doc);
|
flattenAnnotations(doc);
|
||||||
|
|
||||||
if (certificate) {
|
if (certificate) {
|
||||||
|
|||||||
112
packages/lib/server-only/pdf/flatten-form.ts
Normal file
112
packages/lib/server-only/pdf/flatten-form.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import type { PDFField, PDFWidgetAnnotation } from 'pdf-lib';
|
||||||
|
import { PDFCheckBox, PDFRadioGroup, PDFRef } from 'pdf-lib';
|
||||||
|
import {
|
||||||
|
PDFDict,
|
||||||
|
type PDFDocument,
|
||||||
|
PDFName,
|
||||||
|
drawObject,
|
||||||
|
popGraphicsState,
|
||||||
|
pushGraphicsState,
|
||||||
|
rotateInPlace,
|
||||||
|
translate,
|
||||||
|
} from 'pdf-lib';
|
||||||
|
|
||||||
|
export const flattenForm = (document: PDFDocument) => {
|
||||||
|
const form = document.getForm();
|
||||||
|
|
||||||
|
form.updateFieldAppearances();
|
||||||
|
|
||||||
|
for (const field of form.getFields()) {
|
||||||
|
for (const widget of field.acroField.getWidgets()) {
|
||||||
|
flattenWidget(document, field, widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
form.removeField(field);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPageForWidget = (document: PDFDocument, widget: PDFWidgetAnnotation) => {
|
||||||
|
const pageRef = widget.P();
|
||||||
|
|
||||||
|
let page = document.getPages().find((page) => page.ref === pageRef);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
const widgetRef = document.context.getObjectRef(widget.dict);
|
||||||
|
|
||||||
|
if (!widgetRef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
page = document.findPageForAnnotationRef(widgetRef);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAppearanceRefForWidget = (field: PDFField, widget: PDFWidgetAnnotation) => {
|
||||||
|
try {
|
||||||
|
const normalAppearance = widget.getNormalAppearance();
|
||||||
|
let normalAppearanceRef: PDFRef | null = null;
|
||||||
|
|
||||||
|
if (normalAppearance instanceof PDFRef) {
|
||||||
|
normalAppearanceRef = normalAppearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalAppearance instanceof PDFDict &&
|
||||||
|
(field instanceof PDFCheckBox || field instanceof PDFRadioGroup)
|
||||||
|
) {
|
||||||
|
const value = field.acroField.getValue();
|
||||||
|
const ref = normalAppearance.get(value) ?? normalAppearance.get(PDFName.of('Off'));
|
||||||
|
|
||||||
|
if (ref instanceof PDFRef) {
|
||||||
|
normalAppearanceRef = ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalAppearanceRef;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidgetAnnotation) => {
|
||||||
|
try {
|
||||||
|
const page = getPageForWidget(document, widget);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appearanceRef = getAppearanceRefForWidget(field, widget);
|
||||||
|
|
||||||
|
if (!appearanceRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xObjectKey = page.node.newXObject('FlatWidget', appearanceRef);
|
||||||
|
|
||||||
|
const rectangle = widget.getRectangle();
|
||||||
|
const operators = [
|
||||||
|
pushGraphicsState(),
|
||||||
|
translate(rectangle.x, rectangle.y),
|
||||||
|
...rotateInPlace({ ...rectangle, rotation: 0 }),
|
||||||
|
drawObject(xObjectKey),
|
||||||
|
popGraphicsState(),
|
||||||
|
].filter((op) => !!op);
|
||||||
|
|
||||||
|
page.pushOperators(...operators);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821
|
// https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821
|
||||||
import fontkit from '@pdf-lib/fontkit';
|
import fontkit from '@pdf-lib/fontkit';
|
||||||
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_HANDWRITING_FONT_SIZE,
|
DEFAULT_HANDWRITING_FONT_SIZE,
|
||||||
@@ -17,6 +17,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
res.arrayBuffer(),
|
res.arrayBuffer(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fontNoto = await fetch(process.env.FONT_NOTO_SANS_URI).then(async (res) =>
|
||||||
|
res.arrayBuffer(),
|
||||||
|
);
|
||||||
|
|
||||||
const isSignatureField = isSignatureFieldType(field.type);
|
const isSignatureField = isSignatureFieldType(field.type);
|
||||||
|
|
||||||
pdf.registerFontkit(fontkit);
|
pdf.registerFontkit(fontkit);
|
||||||
@@ -41,7 +45,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
||||||
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
||||||
|
|
||||||
const font = await pdf.embedFont(isSignatureField ? fontCaveat : StandardFonts.Helvetica);
|
const font = await pdf.embedFont(isSignatureField ? fontCaveat : fontNoto);
|
||||||
|
|
||||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||||
await pdf.embedFont(fontCaveat);
|
await pdf.embedFont(fontCaveat);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
||||||
|
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||||
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
||||||
import { updateUser } from '@documenso/lib/server-only/admin/update-user';
|
import { updateUser } from '@documenso/lib/server-only/admin/update-user';
|
||||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
||||||
@@ -10,6 +11,7 @@ import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upse
|
|||||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { adminProcedure, router } from '../trpc';
|
import { adminProcedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@@ -100,7 +102,11 @@ export const adminRouter = router({
|
|||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await sealDocument({ documentId: id, isResealing: true });
|
const document = await getEntireDocument({ id });
|
||||||
|
|
||||||
|
const isResealing = document.status === DocumentStatus.COMPLETED;
|
||||||
|
|
||||||
|
return await sealDocument({ documentId: id, isResealing });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('resealDocument error', err);
|
console.error('resealDocument error', err);
|
||||||
|
|
||||||
|
|||||||
1
packages/tsconfig/process-env.d.ts
vendored
1
packages/tsconfig/process-env.d.ts
vendored
@@ -73,6 +73,7 @@ declare namespace NodeJS {
|
|||||||
|
|
||||||
DEPLOYMENT_TARGET?: 'webapp' | 'marketing';
|
DEPLOYMENT_TARGET?: 'webapp' | 'marketing';
|
||||||
FONT_CAVEAT_URI: string;
|
FONT_CAVEAT_URI: string;
|
||||||
|
FONT_NOTO_SANS_URI: string;
|
||||||
|
|
||||||
POSTGRES_URL?: string;
|
POSTGRES_URL?: string;
|
||||||
DATABASE_URL?: string;
|
DATABASE_URL?: string;
|
||||||
|
|||||||
@@ -63,11 +63,13 @@
|
|||||||
"lucide-react": "^0.279.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.2",
|
"luxon": "^3.4.2",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.11.174",
|
||||||
|
"react": "18.2.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-day-picker": "^8.7.1",
|
"react-day-picker": "^8.7.1",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
"react-pdf": "7.3.3",
|
"react-pdf": "7.7.3",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
"NODE_ENV",
|
"NODE_ENV",
|
||||||
"DEPLOYMENT_TARGET",
|
"DEPLOYMENT_TARGET",
|
||||||
"FONT_CAVEAT_URI",
|
"FONT_CAVEAT_URI",
|
||||||
|
"FONT_NOTO_SANS_URI",
|
||||||
"POSTGRES_URL",
|
"POSTGRES_URL",
|
||||||
"DATABASE_URL",
|
"DATABASE_URL",
|
||||||
"DATABASE_URL_UNPOOLED",
|
"DATABASE_URL_UNPOOLED",
|
||||||
|
|||||||
Reference in New Issue
Block a user