Skip to content

feat: simplify self-signing and add one-call PDF signing API - #9280

Open
vitormattos wants to merge 33 commits into
mainfrom
ux/self-sign-entrypoint-20261027
Open

vitormattos wants to merge 33 commits into
mainfrom
ux/self-sign-entrypoint-20261027

Conversation

@vitormattos

@vitormattos vitormattos commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

Simplifies self-signing (#4005) while keeping the standard signing lifecycle and policies.

User journeys

  • Sign myself (Nextcloud Files and LibreSign): open the ready-to-sign sidebar, add the current account as signer, and keep Sign document, visible signature positions, and additional signers available. This uses the regular request/signing flow, not the direct endpoint.
  • Sign PDF now (LibreSign Files → Request menu; Nextcloud Files → Open in LibreSign sidebar): display a confirmation for the selected PDF with two actionable choices:
    • Sign now uses Click to Sign through POST /ocs/v2.php/apps/libresign/api/v1/sign/direct, then downloads the signed PDF without a visible signature field (an enabled footer policy may still apply).
    • Prepare signing enters the standard request flow and auto-adds the current account as a ready-to-sign signer. In LibreSign, it uploads the selected local PDF, selects it, and opens its sidebar. In Nextcloud Files, it keeps the existing PDF and uses the current sidebar. The user can edit signature positions, add other signers, or use Sign document. Prepare signing does not call the direct endpoint.
  • Do not show this confirmation again is per-user and saved only after a successful Sign now. It only skips the confirmation for future explicit Sign PDF now actions, not Sign myself or ordinary requests. Users can re-enable it in LibreSign → Preferences → Signature requests.
  • API authorization is independent of the preference: account method/policy and signature-request permission are enforced server-side on every direct call. A user preference must never grant access or disable checks. Uncertain signing outcomes cannot be retried blindly; this convenience endpoint does not implement idempotency keys.
  • The initial Request screen and Nextcloud Files context menu have no additional immediate-signing button.

UI comparisons

Visual comparisons: real Playwright screenshots (main vs PR)

Surface Before (main) After (this PR)
LibreSign Files — Request menu Before Request menu After Request menu
Nextcloud Files — PDF menu Before Files PDF menu After Files PDF menu
Nextcloud Files — LibreSign sidebar Before Files sidebar After Files sidebar
Personal preferences — Signature requests Before preferences After preferences
LibreSign Files — document actions Before document actions After document actions
Add signer dialog Before signer dialog After signer dialog

Immediate signing confirmation (appears after selecting a PDF via Sign PDF now, unless dismissed):

Sign PDF now dialog: clear choices and a reversible skip-confirmation preference

Prepare signing from Sign PDF now: real Playwright screenshot of the standard sidebar after selecting Prepare signing, showing the account signer, signature-position editor, and Sign document action.

Prepared request: current account and Sign document action

Regular ready-to-sign sidebars (Sign myself; separate from direct signing):

LibreSign Files Nextcloud Files
Ready to sign LibreSign Files sidebar Ready to sign Nextcloud Files sidebar

API regression coverage (Behat + PHPUnit)

  • Behat HTTP/OCS integration: 18 scenarios / 84 steps passed on isolated Nextcloud 36 with a real signing engine. Includes authentication, manager access, forbidden parameters, invalid payloads, disabled account-signing policy and real PDF signing for Base64, Nextcloud path and node ID.
  • Signed PDF verification: checks HTTP 200, Content-Type: application/pdf, attachment filename, Cache-Control: no-store, document UUID header and the embedded PDF /ByteRange.
  • PHPUnit: 38 relevant controller/service tests with 181 assertions passed. Contract regression checks validate both checked-in OpenAPI specifications, supported fields, auth scheme, v1 route, binary PDF response, and documented error statuses.
  • Quality: PHP syntax check and PHP-CS-Fixer dry run passed on touched PHP files. No production endpoint or workflow change was needed.

Validation

  • 163 focused Vue/Vitest tests passed (5 suites; NODE_ENV=test).
  • 4/4 real Playwright E2E flows passed on isolated Nextcloud 36: immediate signing/download and guided preparation, both from LibreSign and Nextcloud Files.
  • Playwright screenshots above were captured from the actual built branch; no synthetic mockups were used.
  • Frontend TypeScript checking, ESLint on touched files, and production Vite build passed.
  • Automated GitHub checks for the updated head commit may still be running; this pull request remains in draft pending their completion and review.

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos vitormattos changed the title feat(ux): add current account directly in signer selection feat(ux): simplify self-sign in LibreSign and Nextcloud Files Oct 27, 2026
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos vitormattos changed the title feat(ux): simplify self-sign in LibreSign and Nextcloud Files feat: simplify self-signing and add one-call PDF signing API Oct 28, 2026
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
…irmation

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
…provider

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos marked this pull request as ready for review November 4, 2026 16:14
@vitormattos
vitormattos requested a review from a team as a code owner November 4, 2026 16:14
@vitormattos
vitormattos marked this pull request as draft November 4, 2026 16:15
Avoid a double slash before remote.php when WebDAV fixture upload precedes the first OCS request in a scenario.

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Require direct WebDAV responses in the Behat helper, keeping failing HTTP redirects visible rather than following malformed Location targets during fixture upload.

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos marked this pull request as ready for review November 4, 2026 19:43
@YvesCesar
YvesCesar self-requested a review November 4, 2026 20:14

@YvesCesar YvesCesar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitormattos

I made a few revisions. Could you take a look?

Comment thread lib/Controller/DirectSignController.php Resolved
Comment thread src/actions/openInLibreSignAction.js Resolved
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos requested a review from YvesCesar November 5, 2026 00:32

@YvesCesar YvesCesar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitormattos

I made a few more revisions.

…tion

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos
vitormattos requested a review from YvesCesar November 5, 2026 14:42
Comment thread src/store/files.js
})

for (const [, file] of Object.entries(allFiles)) {
if (String(file.nodeId) !== String(nodeId)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check breaks the sidebar for signed copies. The record of a signed PDF keeps the nodeId of the original file, so file/list returns the right record for nodeIds[]=<signed copy>, but this condition discards it.

I reproduced it in the browser: after signing, opening *.signed_N.pdf in the LibreSign tab shows an empty sidebar with Add signer, instead of the signer and Validation info. Saving a signer there returns 422, "A completed signer cannot be removed."

Could you remove this check? !files.value[fileId] below is enough for the case I raised. Without these three lines, the signed copy shows correctly again, and the six Sign myself cases and the file switch case keep passing.

The test does not select a different file returned by a filtered refresh would need to go too, or could be replaced by one that opens a signed copy whose record has a different nodeId.

* @return array<string, mixed>
*/
private function resolveDirectSigningSource(IUser $user, array $source): array {
if (isset($source['base64'])) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isset($source['base64']) is true for an empty string, but validateInput() ignores empty values, so {"base64": "", "nodeId": 123} passes validation and skips every check below. I reproduced it on this branch:

  • Shared PDF, including read-only: the 403 is bypassed. The document is signed, and the call returns 409.
  • The user's own file, failed preflight: isset($file['base64']) on line 98 is true, so the cleanup deletes the original file and the folder if it becomes empty.
  • base64: 123 or an array: returns 500.

Could you only treat base64 as selected when it isn't empty, and reject non-string values?

if (($source['base64'] ?? '') !== '') {
        return ['base64' => $source['base64']];
}
unset($source['base64']);
if (isset($file['base64']) && !is_string($file['base64'])) {
        throw new LibresignException('Provide exactly one PDF as file.base64, file.nodeId, or file.path.', Http::STATUS_BAD_REQUEST);
}

With this change, the shared cases return 403, the user's file is kept, and non-string values return 400. Please add tests for an empty base64 combined with nodeId and with path.

@YvesCesar

Copy link
Copy Markdown
Member

@vitormattos

I made a few more revisions. I forgot to mention it in the general last review comment.

@vitormattos vitormattos added this to the Next Major (36) milestone Nov 7, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants