From 76aa5c7e2f2606662a3695795fc665f2c4e155d8 Mon Sep 17 00:00:00 2001
From: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Date: Mon, 13 Apr 2026 13:20:43 +0100
Subject: [PATCH] Fix encrypted pdf handling (#6088)
Fix and improve encrypted pdf handling
---
frontend/package-lock.json | 69 ++---
.../public/locales/en-GB/translation.toml | 5 +
.../shared/EncryptedPdfUnlockModal.tsx | 17 +-
.../core/components/viewer/EmbedPdfViewer.tsx | 29 +-
frontend/src/core/contexts/FileContext.tsx | 62 ++++
.../src/core/contexts/file/fileActions.ts | 16 +
.../hooks/tools/shared/useToolOperation.ts | 19 ++
frontend/src/core/services/fileAnalyzer.ts | 62 +++-
.../EncryptedPdfUnlockE2E.spec.ts | 285 ++++++++++++++++++
.../core/tests/test-fixtures/encrypted.pdf | Bin 0 -> 50417 bytes
frontend/src/core/utils/thumbnailUtils.ts | 9 +-
frontend/src/core/utils/toolErrorHandler.ts | 21 +-
12 files changed, 527 insertions(+), 67 deletions(-)
create mode 100644 frontend/src/core/tests/encryptedPdf/EncryptedPdfUnlockE2E.spec.ts
create mode 100644 frontend/src/core/tests/test-fixtures/encrypted.pdf
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index d6a59cbed7..d6328aa1c9 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -477,7 +477,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -526,7 +525,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -589,7 +587,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.9.1.tgz",
"integrity": "sha512-DlFV2o+tv9S+j4TeBVkRaIjjE9o3Tq3+hvJNoIOFtl87cR77UVQqEIRqOf61yk85Y+T2LfmnVPWjNcMuiKUh8w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/engines": "2.9.1",
"@embedpdf/models": "2.9.1"
@@ -685,7 +682,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.9.1.tgz",
"integrity": "sha512-aNtXjI3NUwz7kdmWsQIWzuS1QdZmuHXGCc+Kwl9u5O0PAgoj74OLsgoNEcFzz9m1rljyq3WPVnLczO6ByiifpQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1",
"@embedpdf/utils": "2.9.1"
@@ -775,7 +771,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.9.1.tgz",
"integrity": "sha512-3AcvSTT7fmqe1ve/FvR3lJ5q7t5JYmnnAg8LKc9ATsDjS9J5b0WE03Omz9a8/sL19iKq8xeR1+W28phgvlcKNw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1"
},
@@ -793,7 +788,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.9.1.tgz",
"integrity": "sha512-/wpdStr1NeyMCvAEMVSCPC0a3zaMd+TSK4u8INsIo3b1RoFfb9iTlBB+qW/aaxvZJ/C7MChQ7cLX6VSKXK/6JQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1"
},
@@ -869,7 +863,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.9.1.tgz",
"integrity": "sha512-mtfu6uDxlz3+j0xPXfKyvuu8iCFjapPkbnx8vGQ0z2PBNAMm+05hsNIzxJSGMP2VCFo09SOz2zCs7ch9J6NeNg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1"
},
@@ -904,7 +897,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.9.1.tgz",
"integrity": "sha512-+U3PSIUuNlIOTXzRhnPBP+Rx20sFOd3OPiowyI2EP/Kx/j5R/amgL/t2rjrpw9gjXEMEGsli9Fn4UqnVgMrPaQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1"
},
@@ -940,7 +932,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.9.1.tgz",
"integrity": "sha512-dVLjiLGnZDo0xO7lZulLGl3cJ/mO7BcA3PGO2uMdhqSWK4tAF/DrakvwXdD581VBwXD/C25EJhxiNa2L7mU4wg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1",
"@embedpdf/utils": "2.9.1"
@@ -1015,7 +1006,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.9.1.tgz",
"integrity": "sha512-bVhBuZHTppKV+OB5lBLqXQv+5oW1A7kAIc5UzsImBwl6NpwH+2PdVkelfrF37yEqnEF/mdxobriWSP0aOVl93w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "2.9.1"
},
@@ -1118,7 +1108,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1162,7 +1151,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -2021,7 +2009,6 @@
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.18.tgz",
"integrity": "sha512-9tph1lTVogKPjTx02eUxDUOdXacPzK62UuSqb4TdGliI54/Xgxftq0Dfqu6XuhCxn9J5MDJaNiLDvL/1KRkYqA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@floating-ui/react": "^0.27.16",
"clsx": "^2.1.1",
@@ -2072,7 +2059,6 @@
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.18.tgz",
"integrity": "sha512-QoWr9+S8gg5050TQ06aTSxtlpGjYOpIllRbjYYXlRvZeTsUqiTbVfvQROLexu4rEaK+yy9Wwriwl9PMRgbLqPw==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"react": "^18.x || ^19.x"
}
@@ -2149,7 +2135,6 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.9.tgz",
"integrity": "sha512-I8yO3t4T0y7bvDiR1qhIN6iBWZOTBfVOnmLlM7K6h3dx5YX2a7rnkuXzc2UkZaqhxY9NgTnEbdPlokR1RxCNRQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.6",
"@mui/core-downloads-tracker": "^7.3.9",
@@ -2597,7 +2582,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -3513,7 +3497,6 @@
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12.16"
}
@@ -3609,6 +3592,7 @@
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
"integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"acorn": "^8.9.0"
}
@@ -4436,7 +4420,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -5024,7 +5007,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -5035,7 +5017,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -5116,7 +5097,6 @@
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.57.2",
"@typescript-eslint/types": "8.57.2",
@@ -5565,6 +5545,7 @@
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
"integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/shared": "3.5.30"
}
@@ -5574,6 +5555,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
"integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/shared": "3.5.30"
@@ -5584,6 +5566,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
"integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/runtime-core": "3.5.30",
@@ -5596,6 +5579,7 @@
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
"integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30"
@@ -5622,7 +5606,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5883,6 +5866,7 @@
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -6164,7 +6148,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -7076,7 +7059,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -7539,15 +7521,15 @@
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/devtools-protocol": {
"version": "0.0.1581282",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
"dev": true,
- "license": "BSD-3-Clause",
- "peer": true
+ "license": "BSD-3-Clause"
},
"node_modules/dezalgo": {
"version": "1.0.4",
@@ -7880,7 +7862,6 @@
"integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -7990,7 +7971,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/espree": {
"version": "11.2.0",
@@ -8055,6 +8037,7 @@
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
"integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@typescript-eslint/types": "^8.2.0"
@@ -8932,7 +8915,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.29.2"
},
@@ -9213,6 +9195,7 @@
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "^1.0.6"
}
@@ -9388,7 +9371,6 @@
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6",
@@ -9950,7 +9932,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -10970,7 +10953,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -11231,7 +11213,6 @@
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.363.3.tgz",
"integrity": "sha512-j1+MTbHO17kKXJMGDnaiW1EMOiA4AprE8EML6QnbSds+XbqHR2CdHa8T+/zIriZSoXlkZH4R+A4gY29lb5hdlA==",
"license": "SEE LICENSE IN LICENSE",
- "peer": true,
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.208.0",
@@ -11253,7 +11234,6 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz",
"integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -11645,7 +11625,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -11655,7 +11634,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -11738,8 +11716,7 @@
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/react-number-format": {
"version": "5.4.5",
@@ -11756,7 +11733,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -12138,8 +12114,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -12989,6 +12964,7 @@
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -13261,7 +13237,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -13455,7 +13430,6 @@
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
@@ -13523,7 +13497,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -13774,7 +13747,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -13949,7 +13921,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -13963,7 +13934,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -14474,7 +14444,8 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/zod": {
"version": "3.25.76",
diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml
index 693144ffd6..94248c215e 100644
--- a/frontend/public/locales/en-GB/translation.toml
+++ b/frontend/public/locales/en-GB/translation.toml
@@ -44,6 +44,8 @@ downloadPdf = "Download PDF"
downloadUnavailable = "Download unavailable for this item"
edit = "Edit"
editYourNewFiles = "Edit your new file(s)"
+encryptedFileBlocked = "File is password-protected. Unlock it first."
+encryptedFilesBlocked = "{{count}} files are password-protected. Unlock them first."
exportAndContinue = "Export & Continue"
false = "False"
fileSavedToDisk = "File saved to disk"
@@ -3341,6 +3343,9 @@ successBodyWithName = "Password removed from {{fileName}}"
successTitle = "Password removed"
title = "Remove password to continue"
unlock = "Unlock & Continue"
+unlockAll = "Use for all ({{count}})"
+unlockAllPartialFail = "Wrong password for: {{names}}"
+unlockAllSuccess = "Unlocked {{count}} file(s)."
unlockPrompt = "Unlock PDF to continue"
[encryptedPdfUnlock.password]
diff --git a/frontend/src/core/components/shared/EncryptedPdfUnlockModal.tsx b/frontend/src/core/components/shared/EncryptedPdfUnlockModal.tsx
index 765f90704b..e726e10823 100644
--- a/frontend/src/core/components/shared/EncryptedPdfUnlockModal.tsx
+++ b/frontend/src/core/components/shared/EncryptedPdfUnlockModal.tsx
@@ -9,8 +9,10 @@ interface EncryptedPdfUnlockModalProps {
password: string;
errorMessage?: string | null;
isProcessing: boolean;
+ remainingCount: number;
onPasswordChange: (value: string) => void;
onUnlock: () => void;
+ onUnlockAll: () => void;
onSkip: () => void;
}
@@ -20,8 +22,10 @@ const EncryptedPdfUnlockModal = ({
password,
errorMessage,
isProcessing,
+ remainingCount,
onPasswordChange,
onUnlock,
+ onUnlockAll,
onSkip,
}: EncryptedPdfUnlockModalProps) => {
const { t } = useTranslation();
@@ -75,9 +79,16 @@ const EncryptedPdfUnlockModal = ({
-
+
+ {remainingCount > 0 && (
+
+ )}
+
+
diff --git a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
index 7d40b2b4d7..2df8bd351a 100644
--- a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
+++ b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
-import { Box, Center, Text, ActionIcon } from "@mantine/core";
+import { Box, Center, Text, ActionIcon, Button, Stack } from "@mantine/core";
import CloseIcon from "@mui/icons-material/Close";
+import LockIcon from "@mui/icons-material/Lock";
import { useFileState, useFileActions } from "@app/contexts/FileContext";
import { useFileWithUrl } from "@app/hooks/useFileWithUrl";
@@ -350,6 +351,13 @@ const EmbedPdfViewerContent = ({
}
}, [previewFile, fileWithUrl]);
+ // Check if the current file is encrypted (gate the viewer to prevent PDFium crash)
+ const isCurrentFileEncrypted = React.useMemo(() => {
+ if (!currentFile || !isStirlingFile(currentFile)) return false;
+ const stub = selectors.getStirlingFileStub(currentFile.fileId);
+ return stub?.processedFile?.isEncrypted === true;
+ }, [currentFile, selectors]);
+
const bookmarkCacheKey = React.useMemo(() => {
if (currentFile && isStirlingFile(currentFile)) {
return currentFile.fileId;
@@ -1045,7 +1053,7 @@ const EmbedPdfViewerContent = ({
console.log("[FormFill] Fetching form fields for:", currentFileId);
fetchFormFields(currentFile, currentFileId ?? undefined);
}
- }, [isFormFillToolActive, currentFile, currentFileId, fetchFormFields]);
+ }, [isFormFillToolActive, currentFile, currentFileId, fetchFormFields, isCurrentFileEncrypted]);
const sidebarWidthRem = 15;
const commentsSidebarWidthRem = 18;
@@ -1087,6 +1095,23 @@ const EmbedPdfViewerContent = ({
Error: No file provided to viewer
+ ) : isCurrentFileEncrypted ? (
+
+
+
+ This PDF is password-protected
+
+
+
) : (
<>
{/* EmbedPDF Viewer */}
diff --git a/frontend/src/core/contexts/FileContext.tsx b/frontend/src/core/contexts/FileContext.tsx
index a4ec2ec1e1..88f43690cd 100644
--- a/frontend/src/core/contexts/FileContext.tsx
+++ b/frontend/src/core/contexts/FileContext.tsx
@@ -371,6 +371,66 @@ function FileContextInner({ children, enablePersistence = true }: FileContextPro
}
}, [activeEncryptedFileId, unlockPassword, runAutomaticPasswordRemoval, t]);
+ const handleUnlockAll = useCallback(async () => {
+ if (!activeEncryptedFileId) return;
+ const pw = unlockPassword.trim();
+ if (!pw) {
+ setUnlockError(t("encryptedPdfUnlock.required", "Enter the password to continue."));
+ return;
+ }
+
+ setIsUnlocking(true);
+ setUnlockError(null);
+
+ const allIds = [activeEncryptedFileId, ...encryptedQueue];
+ let successCount = 0;
+ const failedNames: string[] = [];
+
+ for (const fileId of allIds) {
+ try {
+ await runAutomaticPasswordRemoval(fileId, pw);
+ dismissedEncryptedFilesRef.current.delete(fileId);
+ successCount++;
+ } catch {
+ const name = stateRef.current.files.byId[fileId]?.name ?? fileId;
+ failedNames.push(name);
+ }
+ }
+
+ if (successCount > 0) {
+ alert({
+ alertType: "success",
+ title: t("encryptedPdfUnlock.successTitle", "Password removed"),
+ body: t("encryptedPdfUnlock.unlockAllSuccess", {
+ defaultValue: "Unlocked {{count}} file(s).",
+ count: successCount,
+ }),
+ expandable: false,
+ isPersistentPopup: false,
+ });
+ }
+
+ if (failedNames.length > 0) {
+ setUnlockError(
+ t("encryptedPdfUnlock.unlockAllPartialFail", {
+ defaultValue: "Wrong password for: {{names}}",
+ names: failedNames.join(", "),
+ }),
+ );
+ const failedIds = allIds.filter((id) => {
+ const name = stateRef.current.files.byId[id]?.name ?? id;
+ return failedNames.includes(name);
+ });
+ setEncryptedQueue(failedIds.slice(1));
+ setActiveEncryptedFileId(failedIds[0]);
+ } else {
+ setEncryptedQueue([]);
+ setActiveEncryptedFileId(null);
+ }
+
+ setIsUnlocking(false);
+ }, [activeEncryptedFileId, encryptedQueue, unlockPassword, runAutomaticPasswordRemoval, t]);
+
const undoConsumeFilesWrapper = useCallback(
async (inputFiles: File[], inputStirlingFileStubs: StirlingFileStub[], outputFileIds: FileId[]): Promise => {
return undoConsumeFiles(inputFiles, inputStirlingFileStubs, outputFileIds, filesRef, dispatch, indexedDB);
@@ -520,8 +580,10 @@ function FileContextInner({ children, enablePersistence = true }: FileContextPro
password={unlockPassword}
errorMessage={unlockError}
isProcessing={isUnlocking}
+ remainingCount={encryptedQueue.length}
onPasswordChange={setUnlockPassword}
onUnlock={handleUnlockSubmit}
+ onUnlockAll={handleUnlockAll}
onSkip={handleUnlockSkip}
/>
diff --git a/frontend/src/core/contexts/file/fileActions.ts b/frontend/src/core/contexts/file/fileActions.ts
index 17caafbb38..ac6d71d691 100644
--- a/frontend/src/core/contexts/file/fileActions.ts
+++ b/frontend/src/core/contexts/file/fileActions.ts
@@ -19,6 +19,7 @@ import { buildQuickKeySet } from "@app/contexts/file/fileSelectors";
import { StirlingFile } from "@app/types/fileContext";
import { fileStorage } from "@app/services/fileStorage";
import { zipFileService } from "@app/services/zipFileService";
+import { FileAnalyzer } from "@app/services/fileAnalyzer";
const DEBUG = process.env.NODE_ENV === "development";
const HYDRATION_CONCURRENCY = 2;
let activeHydrations = 0;
@@ -328,6 +329,21 @@ export async function addFiles(
// Create new filestub with minimal metadata; hydrate thumbnails/processedFile asynchronously
const fileStub = createNewStirlingFileStub(file, fileId);
+ // Early encryption detection for PDFs — set the flag before dispatch so the
+ // viewer gate and modal queue pick it up immediately instead of after hydration
+ if (file.type === "application/pdf") {
+ try {
+ if (await FileAnalyzer.isPDFUserPasswordProtected(file)) {
+ fileStub.processedFile = (fileStub.processedFile || { pages: [] }) as any;
+ fileStub.processedFile!.isEncrypted = true;
+ }
+ } catch (error) {
+ // Never block upload on analysis failure — but log so it's debuggable
+ // if an unencrypted file later appears to "hang" during processing.
+ console.warn("[FileActions] Early encryption detection failed for", file.name, error);
+ }
+ }
+
// Check for pending file path mapping from Tauri file dialog (desktop only)
try {
const { pendingFilePathMappings } = await import("@app/services/pendingFilePathMappings");
diff --git a/frontend/src/core/hooks/tools/shared/useToolOperation.ts b/frontend/src/core/hooks/tools/shared/useToolOperation.ts
index 064dc3aa9f..e92dd874f1 100644
--- a/frontend/src/core/hooks/tools/shared/useToolOperation.ts
+++ b/frontend/src/core/hooks/tools/shared/useToolOperation.ts
@@ -116,6 +116,25 @@ export const useToolOperation = (config: ToolOperationConfig):
return;
}
+ // Block encrypted files from being sent to backend tools
+ const encryptedFiles = validFiles.filter((f) => {
+ const stub = selectors.getStirlingFileStub(f.fileId);
+ return stub?.processedFile?.isEncrypted === true;
+ });
+ if (encryptedFiles.length > 0) {
+ for (const ef of encryptedFiles) {
+ fileActions.openEncryptedUnlockPrompt(ef.fileId);
+ }
+ actions.setError(
+ encryptedFiles.length === 1
+ ? t("encryptedFileBlocked", "File is password-protected. Unlock it first.")
+ : t("encryptedFilesBlocked", "{{count}} files are password-protected. Unlock them first.", {
+ count: encryptedFiles.length,
+ }),
+ );
+ return;
+ }
+
// Resolve the runtime endpoint from params (static string or function result).
// Custom processors may omit endpoint entirely — result is undefined in that case.
const runtimeEndpoint: string | undefined = config.endpoint
diff --git a/frontend/src/core/services/fileAnalyzer.ts b/frontend/src/core/services/fileAnalyzer.ts
index 7c8e62ee3c..f2ac0e6405 100644
--- a/frontend/src/core/services/fileAnalyzer.ts
+++ b/frontend/src/core/services/fileAnalyzer.ts
@@ -1,5 +1,24 @@
import { FileAnalysis, ProcessingStrategy } from "@app/types/processing";
import { pdfWorkerManager } from "@app/services/pdfWorkerManager";
+import type { PDFDocumentProxy } from "pdfjs-dist";
+
+// Scan the last ~8KB of the PDF for an /Encrypt entry. The trailer lives near
+// the tail of the file, so this is enough in practice while staying cheap.
+// For files smaller than the window, the whole file is scanned.
+function hasEncryptMarker(buffer: ArrayBuffer): boolean {
+ const TAIL_BYTES = 8 * 1024;
+ const offset = Math.max(0, buffer.byteLength - TAIL_BYTES);
+ const view = new Uint8Array(buffer, offset);
+ // "/Encrypt" as ASCII bytes
+ const needle = [0x2f, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74];
+ outer: for (let i = 0; i <= view.length - needle.length; i++) {
+ for (let j = 0; j < needle.length; j++) {
+ if (view[i + j] !== needle[j]) continue outer;
+ }
+ return true;
+ }
+ return false;
+}
export class FileAnalyzer {
private static readonly SIZE_THRESHOLDS = {
@@ -55,30 +74,59 @@ export class FileAnalyzer {
/**
* Quick PDF analysis without full processing
*/
+ /**
+ * Cheap encryption-only probe for the upload-time detection path.
+ *
+ * Looks for a /Encrypt entry in the last 8KB of the file (where the PDF
+ * trailer lives). If absent, the file is definitely not encrypted and we
+ * can skip a full pdf.js parse. If present, falls back to pdf.js so we can
+ * distinguish user-password (blocks open) from owner-password-only (opens
+ * fine) — only the former should prompt.
+ */
+ static async isPDFUserPasswordProtected(file: File): Promise {
+ const arrayBuffer = await file.arrayBuffer();
+ if (!hasEncryptMarker(arrayBuffer)) return false;
+
+ let pdf: PDFDocumentProxy | undefined;
+ try {
+ pdf = await pdfWorkerManager.createDocument(arrayBuffer, {
+ stopAtErrors: false,
+ verbosity: 0,
+ });
+ // pdf.js opened it — owner-password-only case, no prompt needed.
+ return false;
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
+ return errorMessage.includes("password") || errorMessage.includes("encrypted");
+ } finally {
+ if (pdf) pdfWorkerManager.destroyDocument(pdf);
+ }
+ }
+
static async quickPDFAnalysis(file: File): Promise<{
pageCount: number;
isEncrypted: boolean;
isCorrupted: boolean;
}> {
+ let pdf: PDFDocumentProxy | undefined;
try {
// For small files, read the whole file
// For large files, try the whole file first (PDF.js needs the complete structure)
const arrayBuffer = await file.arrayBuffer();
- const pdf = await pdfWorkerManager.createDocument(arrayBuffer, {
+ pdf = await pdfWorkerManager.createDocument(arrayBuffer, {
stopAtErrors: false, // Don't stop at minor errors
verbosity: 0, // Suppress PDF.js warnings
});
const pageCount = pdf.numPages;
- const isEncrypted = (pdf as any).isEncrypted;
-
- // Clean up using worker manager
- pdfWorkerManager.destroyDocument(pdf);
+ // If pdf.js opened the document successfully, the user can view it — even if
+ // the PDF carries encryption dictionaries (owner-password-only case). We only
+ // flag isEncrypted when pdf.js *fails* to open the file (caught below).
return {
pageCount,
- isEncrypted,
+ isEncrypted: false,
isCorrupted: false,
};
} catch (error) {
@@ -91,6 +139,8 @@ export class FileAnalyzer {
isEncrypted,
isCorrupted: !isEncrypted, // If not encrypted, probably corrupted
};
+ } finally {
+ if (pdf) pdfWorkerManager.destroyDocument(pdf);
}
}
diff --git a/frontend/src/core/tests/encryptedPdf/EncryptedPdfUnlockE2E.spec.ts b/frontend/src/core/tests/encryptedPdf/EncryptedPdfUnlockE2E.spec.ts
new file mode 100644
index 0000000000..2bce945f24
--- /dev/null
+++ b/frontend/src/core/tests/encryptedPdf/EncryptedPdfUnlockE2E.spec.ts
@@ -0,0 +1,285 @@
+/**
+ * End-to-End Tests for Encrypted PDF Password Prompting
+ *
+ * Tests the EncryptedPdfUnlockModal flow when uploading password-protected PDFs.
+ * All backend API calls are mocked via page.route() — no real backend required.
+ * The Vite dev server must be running (handled by playwright.config.ts webServer).
+ */
+
+import { test, expect, type Page } from "@playwright/test";
+import path from "path";
+import fs from "fs";
+
+const FIXTURES_DIR = path.join(__dirname, "../test-fixtures");
+const ENCRYPTED_PDF = path.join(FIXTURES_DIR, "encrypted.pdf");
+const SAMPLE_PDF = path.join(FIXTURES_DIR, "sample.pdf");
+
+// Minimal valid PDF returned by the mocked remove-password endpoint
+const FAKE_UNLOCKED_PDF = Buffer.from(
+ "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n" +
+ "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" +
+ "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n" +
+ "xref\n0 4\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n" +
+ "0000000115 00000 n \ntrailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n190\n%%EOF",
+);
+
+// ---------------------------------------------------------------------------
+// Helper: mock all standard app APIs needed to load the main UI
+// ---------------------------------------------------------------------------
+async function mockAppApis(page: Page) {
+ await page.route("**/api/v1/info/status", (route) => route.fulfill({ json: { status: "UP" } }));
+
+ await page.route("**/api/v1/config/app-config", (route) =>
+ route.fulfill({
+ json: { enableLogin: false, languages: ["en-GB"], defaultLocale: "en-GB" },
+ }),
+ );
+
+ await page.route("**/api/v1/auth/me", (route) =>
+ route.fulfill({
+ json: { id: 1, username: "testuser", email: "test@example.com", roles: ["ROLE_USER"] },
+ }),
+ );
+
+ await page.route("**/api/v1/config/endpoints-availability", (route) => route.fulfill({ json: {} }));
+
+ await page.route("**/api/v1/config/endpoint-enabled*", (route) => route.fulfill({ json: true }));
+
+ await page.route("**/api/v1/config/group-enabled*", (route) => route.fulfill({ json: true }));
+
+ await page.route("**/api/v1/ui-data/footer-info", (route) => route.fulfill({ json: {} }));
+
+ await page.route("**/api/v1/proprietary/**", (route) => route.fulfill({ json: {} }));
+}
+
+// ---------------------------------------------------------------------------
+// Helper: mock the remove-password endpoint to succeed
+// ---------------------------------------------------------------------------
+function mockRemovePasswordSuccess(page: Page) {
+ return page.route("**/api/v1/security/remove-password", (route) =>
+ route.fulfill({
+ status: 200,
+ contentType: "application/pdf",
+ headers: { "Content-Disposition": 'attachment; filename="encrypted.pdf"' },
+ body: FAKE_UNLOCKED_PDF,
+ }),
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Helper: mock the remove-password endpoint to fail with wrong password
+// ---------------------------------------------------------------------------
+function mockRemovePasswordWrongPassword(page: Page) {
+ return page.route("**/api/v1/security/remove-password", (route) =>
+ route.fulfill({
+ status: 400,
+ contentType: "application/problem+json",
+ body: JSON.stringify({
+ type: "/errors/pdf-password",
+ title: "PDF password incorrect",
+ status: 400,
+ detail: "The PDF is passworded and requires the correct password to open.",
+ }),
+ }),
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Helper: upload a file through the Files modal and wait for it to close
+// ---------------------------------------------------------------------------
+async function uploadFile(page: Page, filePath: string) {
+ await page.getByTestId("files-button").click();
+ await page.waitForSelector(".mantine-Modal-overlay", { state: "visible", timeout: 5000 });
+ await page.locator('[data-testid="file-input"]').setInputFiles(filePath);
+ // Modal auto-closes after file is selected
+ await page.waitForSelector(".mantine-Modal-overlay", { state: "hidden", timeout: 10000 });
+}
+
+// ---------------------------------------------------------------------------
+// Helper: upload encrypted file — the Files modal closes, then the unlock
+// modal should appear on top. We don't wait for the Files modal to vanish
+// since the unlock modal may appear while it is still closing.
+// ---------------------------------------------------------------------------
+async function uploadEncryptedFile(page: Page, filePath: string) {
+ await page.getByTestId("files-button").click();
+ await page.waitForSelector(".mantine-Modal-overlay", { state: "visible", timeout: 5000 });
+ await page.locator('[data-testid="file-input"]').setInputFiles(filePath);
+}
+
+// ---------------------------------------------------------------------------
+// Selectors for the unlock modal (Mantine Modal with known text content)
+// ---------------------------------------------------------------------------
+const MODAL_TITLE = "Remove password to continue";
+const PASSWORD_PLACEHOLDER = "Enter the PDF password";
+const UNLOCK_BUTTON_TEXT = "Unlock & Continue";
+const SKIP_BUTTON_TEXT = "Skip for now";
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+test.describe.configure({ mode: "serial" });
+
+test.describe("Encrypted PDF Unlock Modal", () => {
+ test.beforeEach(async ({ page }) => {
+ await mockAppApis(page);
+ await page.goto("/?bypassOnboarding=true");
+ await page.waitForSelector('[data-testid="files-button"]', { timeout: 10000 });
+
+ // Dismiss onboarding tooltip if it appears (can block clicks in Firefox/WebKit)
+ const tooltip = page.locator('button:has-text("Close tooltip")');
+ if (await tooltip.isVisible({ timeout: 1000 }).catch(() => false)) {
+ await tooltip.click();
+ }
+ });
+
+ test("uploading an encrypted PDF shows the unlock modal", async ({ page }) => {
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+
+ // The unlock modal should appear with the expected title
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+ await expect(page.getByPlaceholder(PASSWORD_PLACEHOLDER)).toBeVisible();
+ await expect(page.getByRole("button", { name: UNLOCK_BUTTON_TEXT })).toBeVisible();
+ await expect(page.getByRole("button", { name: SKIP_BUTTON_TEXT })).toBeVisible();
+ });
+
+ test("unlock button is disabled when password field is empty", async ({ page }) => {
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ const unlockBtn = page.getByRole("button", { name: UNLOCK_BUTTON_TEXT });
+ await expect(unlockBtn).toBeDisabled();
+ });
+
+ test("unlock button becomes enabled after entering a password", async ({ page }) => {
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ const passwordInput = page.getByPlaceholder(PASSWORD_PLACEHOLDER);
+ await passwordInput.fill("somepassword");
+
+ const unlockBtn = page.getByRole("button", { name: UNLOCK_BUTTON_TEXT });
+ await expect(unlockBtn).toBeEnabled();
+ });
+
+ test("successful unlock removes the modal and shows success alert", async ({ page }) => {
+ await mockRemovePasswordSuccess(page);
+
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ await page.getByPlaceholder(PASSWORD_PLACEHOLDER).fill("testpass123");
+ await page.getByRole("button", { name: UNLOCK_BUTTON_TEXT }).click();
+
+ // Modal should close after successful unlock
+ await expect(page.getByText(MODAL_TITLE)).toBeHidden({ timeout: 10000 });
+
+ // Success alert should appear
+ await expect(page.getByText("Password removed", { exact: true })).toBeVisible({ timeout: 5000 });
+ });
+
+ test("incorrect password shows error message in modal", async ({ page }) => {
+ await mockRemovePasswordWrongPassword(page);
+
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ await page.getByPlaceholder(PASSWORD_PLACEHOLDER).fill("wrongpassword");
+ await page.getByRole("button", { name: UNLOCK_BUTTON_TEXT }).click();
+
+ // Error message should appear within the modal
+ await expect(page.getByText("Incorrect password")).toBeVisible({ timeout: 5000 });
+
+ // Modal should remain open
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible();
+ });
+
+ test("skip button closes the modal without unlocking", async ({ page }) => {
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ await page.getByRole("button", { name: SKIP_BUTTON_TEXT }).click();
+
+ // Modal should close
+ await expect(page.getByText(MODAL_TITLE)).toBeHidden({ timeout: 5000 });
+ });
+
+ test("pressing Enter in password field triggers unlock", async ({ page }) => {
+ await mockRemovePasswordSuccess(page);
+
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ const passwordInput = page.getByPlaceholder(PASSWORD_PLACEHOLDER);
+ await passwordInput.fill("testpass123");
+ await passwordInput.press("Enter");
+
+ // Modal should close after successful unlock via Enter key
+ await expect(page.getByText(MODAL_TITLE)).toBeHidden({ timeout: 10000 });
+ });
+
+ test("uploading a normal PDF does not show the unlock modal", async ({ page }) => {
+ await uploadFile(page, SAMPLE_PDF);
+
+ // Wait for the file to finish processing, then verify no unlock modal appeared
+ await page.waitForTimeout(3000);
+ await expect(page.getByText(MODAL_TITLE)).toBeHidden();
+ });
+
+ test("unlock all button is hidden with only one encrypted file", async ({ page }) => {
+ await uploadEncryptedFile(page, ENCRYPTED_PDF);
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+
+ // The "Use for all" button should NOT appear with only one file
+ await expect(page.getByRole("button", { name: /Use for all/ })).toBeHidden();
+ });
+
+ test("unlock all button appears with multiple encrypted files and unlocks all", async ({ page }) => {
+ await mockRemovePasswordSuccess(page);
+
+ // Upload two encrypted files at once (different names to avoid deduplication)
+ await page.getByTestId("files-button").click();
+ await page.waitForSelector(".mantine-Modal-overlay", { state: "visible", timeout: 5000 });
+ await page.locator('[data-testid="file-input"]').setInputFiles([
+ { name: "encrypted-a.pdf", mimeType: "application/pdf", buffer: fs.readFileSync(ENCRYPTED_PDF) },
+ { name: "encrypted-b.pdf", mimeType: "application/pdf", buffer: fs.readFileSync(ENCRYPTED_PDF) },
+ ]);
+
+ // The unlock modal should appear for the first file with "Use for all" visible
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+ const unlockAllBtn = page.getByRole("button", { name: /Use for all/ });
+ await expect(unlockAllBtn).toBeVisible({ timeout: 10000 });
+
+ // Enter password and click unlock all
+ await page.getByPlaceholder(PASSWORD_PLACEHOLDER).fill("testpass123");
+ await unlockAllBtn.click();
+
+ // Modal should close — all files unlocked
+ await expect(page.getByText(MODAL_TITLE)).toBeHidden({ timeout: 10000 });
+ });
+
+ test("unlock all with wrong password shows which files failed", async ({ page }) => {
+ await mockRemovePasswordWrongPassword(page);
+
+ // Upload two encrypted files at once (different names to avoid deduplication)
+ await page.getByTestId("files-button").click();
+ await page.waitForSelector(".mantine-Modal-overlay", { state: "visible", timeout: 5000 });
+ await page.locator('[data-testid="file-input"]').setInputFiles([
+ { name: "encrypted-a.pdf", mimeType: "application/pdf", buffer: fs.readFileSync(ENCRYPTED_PDF) },
+ { name: "encrypted-b.pdf", mimeType: "application/pdf", buffer: fs.readFileSync(ENCRYPTED_PDF) },
+ ]);
+
+ // The unlock modal should appear with "Use for all"
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 10000 });
+ const unlockAllBtn = page.getByRole("button", { name: /Use for all/ });
+ await expect(unlockAllBtn).toBeVisible({ timeout: 10000 });
+
+ await page.getByPlaceholder(PASSWORD_PLACEHOLDER).fill("wrongpassword");
+ await unlockAllBtn.click();
+
+ // Modal should remain open with error about failed files
+ await expect(page.getByText(MODAL_TITLE)).toBeVisible({ timeout: 5000 });
+ await expect(page.getByText(/Wrong password for/)).toBeVisible({ timeout: 5000 });
+ });
+});
diff --git a/frontend/src/core/tests/test-fixtures/encrypted.pdf b/frontend/src/core/tests/test-fixtures/encrypted.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..28fb757bd2e15c6b6f9862a407b8f20e39c36f55
GIT binary patch
literal 50417
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