Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
254fc4c
fix(mobile): fix signature placement and coordinate serialization
Phillipxh Mar 29, 2026
8a4987c
fix(mobile): harden touch placement and pdf-elements runtime behavior
Phillipxh Mar 29, 2026
5bcd6c5
test(mobile): increase coverage for signature placement flows
Phillipxh Mar 30, 2026
d90344e
fix(mobile): restore sign flow fallback for self-signer requests
Phillipxh Mar 30, 2026
ec91029
test: fix PdfEditor spec runtime mock typing
vitormattos Apr 4, 2026
7073ea0
chore(deps): bump pdf-elements to 1.1.4
vitormattos Apr 4, 2026
7cb62f0
chore(deps): refresh lockfile for pdf-elements
vitormattos Apr 4, 2026
0d3128b
refactor(pdf-editor): remove touchend workaround internals
vitormattos Apr 4, 2026
a388095
refactor(pdf-editor): decouple model from pdf-elements type export
vitormattos Apr 4, 2026
2080b48
fix(pdf-worker): import worker setup from exported asyncReader path
vitormattos Apr 4, 2026
f19ca97
test(pdf-editor): drop workaround-specific touchend specs
vitormattos Apr 4, 2026
3e574f5
refactor(vite): remove pdf-elements runtime patch plugin
vitormattos Apr 4, 2026
9c066cb
chore(deps): bump pdf-elements to 1.1.5
vitormattos Apr 4, 2026
4badda2
chore(deps): refresh lockfile for pdf-elements 1.1.5
vitormattos Apr 4, 2026
28f5668
refactor(pdf-editor): use pdf-elements exported object type
vitormattos Apr 4, 2026
da9307d
refactor(pdf-worker): restore root setWorkerPath import
vitormattos Apr 4, 2026
2fd5633
fix(sign-route): avoid stale sign_request_uuid fallback
vitormattos Apr 4, 2026
b483e3c
test(sign-route): cover stale state UUID regression
vitormattos Apr 4, 2026
714c675
fix(pdf-editor): use theme tokens for action buttons
vitormattos Apr 4, 2026
5c27bb9
fix(playwright): normalize sign links to relative app paths
vitormattos Apr 4, 2026
c15c776
refactor(playwright): simplify sign-link extraction to relative path
vitormattos Apr 4, 2026
12fb2b5
fix(playwright): normalize public sign links and sign page CSP
vitormattos Apr 4, 2026
da624f6
fix: alias pdf-elements to source in vite
vitormattos Apr 5, 2026
e24cdc1
test: align pdf-elements alias in vitest
vitormattos Apr 5, 2026
f9610e4
fix: set pdf worker path synchronously
vitormattos Apr 5, 2026
46e4bf7
fix: initialize pdf worker before editor mount
vitormattos Apr 5, 2026
8f7b809
fix: resolve nested visible element pdf urls
vitormattos Apr 5, 2026
a015570
fix: load request signature modal pdf blobs
vitormattos Apr 5, 2026
591905c
fix: sync right sidebar open state
vitormattos Apr 5, 2026
4bbd507
fix: mark sign store mounted on selection
vitormattos Apr 5, 2026
3b14cb9
fix: hydrate internal sign view from file detail
vitormattos Apr 5, 2026
f982c34
fix: use signer uuid fallbacks in file actions
vitormattos Apr 5, 2026
8865548
fix: preserve selection while sign sidebar stays open
vitormattos Apr 5, 2026
2d8e4c9
test: cover pdf editor worker ordering
vitormattos Apr 5, 2026
cba5518
test: cover visible elements modal pdf fallbacks
vitormattos Apr 5, 2026
b9761ea
test: cover right sidebar open sync
vitormattos Apr 5, 2026
fd55d3c
test: cover recursive visible element file urls
vitormattos Apr 5, 2026
1fa94bd
test: cover sign store mounted state
vitormattos Apr 5, 2026
4b88d17
test: cover internal sign route hydration
vitormattos Apr 5, 2026
67d0400
test: cover file entry signing uuid fallbacks
vitormattos Apr 5, 2026
6bfa59c
test: cover files list selection cleanup
vitormattos Apr 5, 2026
8530546
test: clear signature elements during e2e setup
vitormattos Apr 5, 2026
18808ca
test: wait for sign route before native engine signing
vitormattos Apr 5, 2026
de1577d
test: stabilize drawn signature e2e flow
vitormattos Apr 5, 2026
1b7e22e
fix: warm up pdf-elements worker from package
vitormattos Apr 5, 2026
3599017
chore: drop pdf-elements vite alias
vitormattos Apr 5, 2026
3676b60
test: drop pdf-elements vitest alias
vitormattos Apr 5, 2026
2ce5e0a
test: mock package worker warmup in visible elements
vitormattos Apr 5, 2026
e262eac
test: mock package worker warmup in request tab
vitormattos Apr 5, 2026
618912d
chore: depend on pdf-elements 1.1.6
vitormattos Apr 5, 2026
0ef1ee2
chore: refresh frontend lockfile
vitormattos Apr 5, 2026
3f231d4
refactor: simplify pdf editor add completion flow
vitormattos Apr 5, 2026
7a9ab90
fix: handle pdf editor add completion event
vitormattos Apr 5, 2026
8f1738e
test: cover pdf editor add completion flow
vitormattos Apr 5, 2026
1c66a55
test: update visible elements add completion event
vitormattos Apr 5, 2026
24b119a
chore: update @libresign/pdf-elements to v1.2.0
vitormattos Apr 5, 2026
266afab
refactor: replace setTimeout polling with event relay in PdfEditor
vitormattos Apr 5, 2026
5134301
test: update PdfEditor tests for event-driven placement detection
vitormattos Apr 5, 2026
ff46d1a
refactor: remove redundant PdfEditor lifecycle hooks
vitormattos Apr 5, 2026
85430fe
fix: stabilize pdf editor action button colors in dark themes
vitormattos Apr 5, 2026
da85c6d
fix: strengthen toolbar color override selectors
vitormattos Apr 5, 2026
ec79e64
fix: target pdf-elements scoped toolbar selector
vitormattos Apr 5, 2026
942719e
fix: correct toolbar override selector nesting
vitormattos Apr 5, 2026
8e84c32
style: align pdf action toolbar with Nextcloud button aesthetics
vitormattos Apr 5, 2026
70fd0b7
feat: consume pdf-elements action styling hooks
vitormattos Apr 5, 2026
8deffb7
chore: bump pdf-elements dependency to 1.2.1
vitormattos Apr 5, 2026
af16d73
chore: refresh lockfile for pdf-elements 1.2.1
vitormattos Apr 5, 2026
a1ab4af
fix: hide sign action while positioning signer
vitormattos Apr 5, 2026
64b36a3
fix: update package lock
vitormattos Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public function index(): TemplateResponse {
$policy = new ContentSecurityPolicy();
$policy->allowEvalScript(true);
$policy->addAllowedFrameDomain('\'self\'');
$policy->addAllowedWorkerSrcDomain("'self'");
$response->setContentSecurityPolicy($policy);

return $response;
Expand Down Expand Up @@ -387,6 +388,7 @@ public function sign(string $uuid): TemplateResponse {

$policy = new ContentSecurityPolicy();
$policy->allowEvalScript(true);
$policy->addAllowedWorkerSrcDomain("'self'");
$response->setContentSecurityPolicy($policy);

return $response;
Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.41.0",
"@fontsource/dancing-script": "^5.2.8",
"@libresign/pdf-elements": "^1.1.3",
"@libresign/pdf-elements": "^1.2.1",
"@marionebl/option": "^1.0.8",
"@mdi/js": "^7.4.47",
"@mdi/svg": "^7.4.47",
Expand Down Expand Up @@ -59,7 +59,6 @@
"codemirror": "^6.0.2",
"debounce": "^3.0.0",
"js-confetti": "^0.13.1",
"pdfjs-dist": "^5.5.207",
"pinia": "^3.0.4",
"signature_pad": "^5.1.3",
"sortablejs": "^1.15.7",
Expand Down
12 changes: 7 additions & 5 deletions playwright/e2e/multi-signer-sequential.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { login } from '../support/nc-login'
import { configureOpenSsl, setAppConfig } from '../support/nc-provisioning'
import { configureOpenSsl, deleteAppConfig, setAppConfig } from '../support/nc-provisioning'
import { createMailpitClient, waitForEmailTo, extractSignLink } from '../support/mailpit'

async function addEmailSigner(
Expand Down Expand Up @@ -51,6 +51,8 @@ test('request signatures from two signers in sequential order', async ({ page })
{ name: 'email', enabled: true, mandatory: true, signatureMethods: { clickToSign: { enabled: true } }, can_create_account: false },
]),
)
await setAppConfig(page.request, 'libresign', 'signature_engine', 'PhpNative')
await deleteAppConfig(page.request, 'libresign', 'tsa_url')

const mailpit = createMailpitClient()
await mailpit.deleteMessages()
Expand Down Expand Up @@ -83,10 +85,10 @@ test('request signatures from two signers in sequential order', async ({ page })
const afterFirst = await mailpit.searchMessages({ query: 'subject:"LibreSign: There is a file for you to sign"' })
expect(afterFirst.messages).toHaveLength(1)

// Logout before signing as signer01 — the sign link is for an email-based signer
// (no Nextcloud account), so it must be accessed without an active admin session.
await page.getByRole('button', { name: 'Settings menu' }).click()
await page.getByRole('link', { name: 'Log out' }).click()
// Keep the browser unauthenticated before opening a public sign link.
// This avoids logout redirects to absolute hosts that may differ per environment.
await page.context().clearCookies()
await page.goto('about:blank')

// Signer01 signs via the link received in the email
const signLink = extractSignLink(email01.Text)
Expand Down
18 changes: 8 additions & 10 deletions playwright/e2e/sign-email-token-unauthenticated.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { test, expect } from '@playwright/test';
import { login } from '../support/nc-login'
import { configureOpenSsl, setAppConfig } from '../support/nc-provisioning'
import { configureOpenSsl, deleteAppConfig, setAppConfig } from '../support/nc-provisioning'
import { createMailpitClient, waitForEmailTo, extractSignLink, extractTokenFromEmail } from '../support/mailpit'

test('sign document with email token as unauthenticated signer', async ({ page }) => {
Expand All @@ -32,6 +32,8 @@ test('sign document with email token as unauthenticated signer', async ({ page }
{ name: 'email', enabled: true, mandatory: true, signatureMethods: { emailToken: { enabled: true } }, can_create_account: false },
]),
)
await setAppConfig(page.request, 'libresign', 'signature_engine', 'PhpNative')
await deleteAppConfig(page.request, 'libresign', 'tsa_url')

await page.goto('./apps/libresign')
await page.getByRole('button', { name: 'Upload from URL' }).click();
Expand All @@ -54,9 +56,10 @@ test('sign document with email token as unauthenticated signer', async ({ page }
await page.getByRole('button', { name: 'Request signatures' }).click();
await page.getByRole('button', { name: 'Send' }).click();

// Logout before accessing the sign link to avoid session-related issues.
await page.getByRole('button', { name: 'Settings menu' }).click();
await page.getByRole('link', { name: 'Log out' }).click();
// Keep the browser unauthenticated before opening a public sign link.
// This avoids logout redirects to absolute hosts that may differ per environment.
await page.context().clearCookies();
await page.goto('about:blank');

const email = await waitForEmailTo(mailpit, 'signer01@libresign.coop', 'LibreSign: There is a file for you to sign')
const signLink = extractSignLink(email.Text)
Expand All @@ -82,10 +85,5 @@ test('sign document with email token as unauthenticated signer', async ({ page }
await page.waitForURL('**/validation/**');
await expect(page.getByText('This document is valid')).toBeVisible();
await expect(page.getByText('Congratulations you have')).toBeVisible();

// Revisit the sign link after the document has been signed.
// The signer must not be able to sign a second time.
await page.goto(signLink)
await expect(page.getByRole('button', { name: 'Sign the document.' })).not.toBeVisible({ timeout: 10_000 })
await expect(page.getByText('This document is valid')).toBeVisible();
await expect(page.getByRole('button', { name: 'Sign the document.' })).not.toBeVisible();
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ test('updates files list status after signing with native engine', async ({ page

await targetRow.getByRole('button', { name: 'Actions' }).click()
await page.getByRole('menuitem', { name: 'Sign' }).click()
await page.getByRole('button', { name: 'Sign the document.' }).click()
await page.waitForURL('**/f/sign/**/pdf')
const signButton = page.getByRole('button', { name: 'Sign the document.' })
await expect(signButton).toBeVisible()
await signButton.click()
await page.getByRole('button', { name: 'Sign document' }).click()
await page.waitForURL('**/validation/**')
await expect(page.getByText('This document is valid')).toBeVisible()
Expand Down
8 changes: 1 addition & 7 deletions playwright/e2e/sign-herself-with-drawn-signature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,6 @@ test('sign herself with drawn signature', async ({ page }) => {
page.getByLabel('PDF document to sign').getByRole('img', { name: 'Signature position for Admin Name' })
).toBeVisible()

// If a signature already exists from a previous run, delete it before creating a new one
const deleteSignatureBtn = page.getByRole('button', { name: 'Delete signature' })
await deleteSignatureBtn.waitFor({ state: 'visible', timeout: 3000 }).catch(() => null)
if (await deleteSignatureBtn.isVisible()) {
await deleteSignatureBtn.click()
}

await page.getByRole('button', { name: 'Define your signature.' }).click();

// The signature type chooser must use role="tab" + aria-selected, not aria-pressed toggle buttons.
Expand Down Expand Up @@ -129,6 +122,7 @@ test('sign herself with drawn signature', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Confirm your signature' })).toBeVisible();
await expect(page.getByRole('img', { name: 'Signature preview' })).toBeVisible();
await page.getByLabel('Confirm your signature').getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('button', { name: 'Sign the document.' })).toBeVisible();

await page.getByRole('button', { name: 'Sign the document.' }).click();
await page.getByRole('button', { name: 'Sign document' }).click();
Expand Down
36 changes: 35 additions & 1 deletion playwright/support/nc-provisioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ type OcsResponse<T = unknown> = {
}
}

type SignatureElementResponse = {
elements?: Array<{
type: string
file: {
nodeId: number
}
}>
}

async function ocsRequest(
request: APIRequestContext,
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
Expand All @@ -44,7 +53,6 @@ async function ocsRequest(
: body !== undefined ? { form: body } : {}),
failOnStatusCode: false,
})

if (!response.ok() && response.status() !== 404) {
throw new Error(`OCS request failed: ${method} ${path} → ${response.status()} ${await response.text()}`)
}
Expand All @@ -56,6 +64,30 @@ async function ocsRequest(
return JSON.parse(text) as OcsResponse
}

export async function clearSignatureElements(
request: APIRequestContext,
userId = process.env.NEXTCLOUD_ADMIN_USER ?? 'admin',
password = process.env.NEXTCLOUD_ADMIN_PASSWORD ?? 'admin',
): Promise<void> {
const result = await ocsRequest<SignatureElementResponse>(
request,
'GET',
'/apps/libresign/api/v1/signature/elements',
userId,
password,
)

for (const element of result.ocs.data.elements ?? []) {
await ocsRequest(
request,
'DELETE',
`/apps/libresign/api/v1/signature/elements/${element.file.nodeId}`,
userId,
password,
)
}
}

// ---------------------------------------------------------------------------
// Users
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -186,4 +218,6 @@ export async function configureOpenSsl(
if (result.ocs.meta.statuscode !== 200) {
throw new Error(`Failed to configure OpenSSL: ${result.ocs.meta.message}`)
}

await clearSignatureElements(request)
}
Loading
Loading