Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
d2c9986
fix(mobile): fix signature placement and coordinate serialization
Phillipxh Mar 29, 2026
bb8658f
fix(mobile): harden touch placement and pdf-elements runtime behavior
Phillipxh Mar 29, 2026
d4f371f
test(mobile): increase coverage for signature placement flows
Phillipxh Mar 30, 2026
217532d
fix(mobile): restore sign flow fallback for self-signer requests
Phillipxh Mar 30, 2026
67c9657
test: fix PdfEditor spec runtime mock typing
vitormattos Apr 4, 2026
16732f4
chore(deps): bump pdf-elements to 1.1.4
vitormattos Apr 4, 2026
3e8be6b
chore(deps): refresh lockfile for pdf-elements
vitormattos Apr 4, 2026
e8aeb6c
refactor(pdf-editor): remove touchend workaround internals
vitormattos Apr 4, 2026
7b07878
refactor(pdf-editor): decouple model from pdf-elements type export
vitormattos Apr 4, 2026
b9b2129
fix(pdf-worker): import worker setup from exported asyncReader path
vitormattos Apr 4, 2026
5a3e185
test(pdf-editor): drop workaround-specific touchend specs
vitormattos Apr 4, 2026
08bc316
refactor(vite): remove pdf-elements runtime patch plugin
vitormattos Apr 4, 2026
0492c60
chore(deps): bump pdf-elements to 1.1.5
vitormattos Apr 4, 2026
96cf506
chore(deps): refresh lockfile for pdf-elements 1.1.5
vitormattos Apr 4, 2026
6b223ff
refactor(pdf-editor): use pdf-elements exported object type
vitormattos Apr 4, 2026
5e66415
refactor(pdf-worker): restore root setWorkerPath import
vitormattos Apr 4, 2026
1239608
fix(sign-route): avoid stale sign_request_uuid fallback
vitormattos Apr 4, 2026
91d9c73
test(sign-route): cover stale state UUID regression
vitormattos Apr 4, 2026
9b68376
fix(pdf-editor): use theme tokens for action buttons
vitormattos Apr 4, 2026
1edfda9
fix(playwright): normalize sign links to relative app paths
vitormattos Apr 4, 2026
c4086a9
refactor(playwright): simplify sign-link extraction to relative path
vitormattos Apr 4, 2026
011e208
fix(playwright): normalize public sign links and sign page CSP
vitormattos Apr 4, 2026
b2277af
fix: alias pdf-elements to source in vite
vitormattos Apr 5, 2026
bcb90b3
test: align pdf-elements alias in vitest
vitormattos Apr 5, 2026
d6c2162
fix: set pdf worker path synchronously
vitormattos Apr 5, 2026
5772241
fix: initialize pdf worker before editor mount
vitormattos Apr 5, 2026
453d891
fix: resolve nested visible element pdf urls
vitormattos Apr 5, 2026
7e3b7d5
fix: load request signature modal pdf blobs
vitormattos Apr 5, 2026
8fc4528
fix: sync right sidebar open state
vitormattos Apr 5, 2026
d72c36f
fix: mark sign store mounted on selection
vitormattos Apr 5, 2026
3aae517
fix: hydrate internal sign view from file detail
vitormattos Apr 5, 2026
66e8bf7
fix: use signer uuid fallbacks in file actions
vitormattos Apr 5, 2026
dc5e110
fix: preserve selection while sign sidebar stays open
vitormattos Apr 5, 2026
627d50b
test: cover pdf editor worker ordering
vitormattos Apr 5, 2026
f3b7fa2
test: cover visible elements modal pdf fallbacks
vitormattos Apr 5, 2026
7b380ca
test: cover right sidebar open sync
vitormattos Apr 5, 2026
9d896f2
test: cover recursive visible element file urls
vitormattos Apr 5, 2026
ce1f737
test: cover sign store mounted state
vitormattos Apr 5, 2026
364e249
test: cover internal sign route hydration
vitormattos Apr 5, 2026
0397525
test: cover file entry signing uuid fallbacks
vitormattos Apr 5, 2026
b135419
test: cover files list selection cleanup
vitormattos Apr 5, 2026
ec5d9bc
test: clear signature elements during e2e setup
vitormattos Apr 5, 2026
978ba69
test: wait for sign route before native engine signing
vitormattos Apr 5, 2026
cfa891d
test: stabilize drawn signature e2e flow
vitormattos Apr 5, 2026
3dc808d
fix: warm up pdf-elements worker from package
vitormattos Apr 5, 2026
c09579c
chore: drop pdf-elements vite alias
vitormattos Apr 5, 2026
9d9aa74
test: drop pdf-elements vitest alias
vitormattos Apr 5, 2026
e5018cf
test: mock package worker warmup in visible elements
vitormattos Apr 5, 2026
66dc7be
test: mock package worker warmup in request tab
vitormattos Apr 5, 2026
f8d92de
chore: depend on pdf-elements 1.1.6
vitormattos Apr 5, 2026
f8cfbc0
chore: refresh frontend lockfile
vitormattos Apr 5, 2026
9a41f27
refactor: simplify pdf editor add completion flow
vitormattos Apr 5, 2026
941a028
fix: handle pdf editor add completion event
vitormattos Apr 5, 2026
89d77d3
test: cover pdf editor add completion flow
vitormattos Apr 5, 2026
7428e74
test: update visible elements add completion event
vitormattos Apr 5, 2026
cd571fd
chore: update @libresign/pdf-elements to v1.2.0
vitormattos Apr 5, 2026
c5e6cf7
refactor: replace setTimeout polling with event relay in PdfEditor
vitormattos Apr 5, 2026
2179ad9
test: update PdfEditor tests for event-driven placement detection
vitormattos Apr 5, 2026
cff5b50
refactor: remove redundant PdfEditor lifecycle hooks
vitormattos Apr 5, 2026
4d76316
fix: stabilize pdf editor action button colors in dark themes
vitormattos Apr 5, 2026
d40d1e5
fix: strengthen toolbar color override selectors
vitormattos Apr 5, 2026
d9abb07
fix: target pdf-elements scoped toolbar selector
vitormattos Apr 5, 2026
dae1c72
fix: correct toolbar override selector nesting
vitormattos Apr 5, 2026
44a901c
style: align pdf action toolbar with Nextcloud button aesthetics
vitormattos Apr 5, 2026
02ec3a5
feat: consume pdf-elements action styling hooks
vitormattos Apr 5, 2026
f86158e
chore: bump pdf-elements dependency to 1.2.1
vitormattos Apr 5, 2026
b44b9a0
chore: refresh lockfile for pdf-elements 1.2.1
vitormattos Apr 5, 2026
17db053
fix: hide sign action while positioning signer
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
9 changes: 4 additions & 5 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