Skip to content

Commit 0efd836

Browse files
authored
Merge pull request #7416 from LibreSign/backport/7408/stable33
[stable33] Continue PR #7342: mobile signature placement flow
2 parents 64019e9 + 17db053 commit 0efd836

31 files changed

+998
-211
lines changed

lib/Controller/PageController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public function index(): TemplateResponse {
122122
$policy = new ContentSecurityPolicy();
123123
$policy->allowEvalScript(true);
124124
$policy->addAllowedFrameDomain('\'self\'');
125+
$policy->addAllowedWorkerSrcDomain("'self'");
125126
$response->setContentSecurityPolicy($policy);
126127

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

388389
$policy = new ContentSecurityPolicy();
389390
$policy->allowEvalScript(true);
391+
$policy->addAllowedWorkerSrcDomain("'self'");
390392
$response->setContentSecurityPolicy($policy);
391393

392394
return $response;

package-lock.json

Lines changed: 4 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@codemirror/state": "^6.6.0",
3131
"@codemirror/view": "^6.41.0",
3232
"@fontsource/dancing-script": "^5.2.8",
33-
"@libresign/pdf-elements": "^1.1.3",
33+
"@libresign/pdf-elements": "^1.2.1",
3434
"@marionebl/option": "^1.0.8",
3535
"@mdi/js": "^7.4.47",
3636
"@mdi/svg": "^7.4.47",
@@ -59,7 +59,6 @@
5959
"codemirror": "^6.0.2",
6060
"debounce": "^3.0.0",
6161
"js-confetti": "^0.13.1",
62-
"pdfjs-dist": "^5.5.207",
6362
"pinia": "^3.0.4",
6463
"signature_pad": "^5.1.3",
6564
"sortablejs": "^1.15.7",

playwright/e2e/multi-signer-sequential.spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import type { Page } from '@playwright/test'
77
import { expect, test } from '@playwright/test'
88
import { login } from '../support/nc-login'
9-
import { configureOpenSsl, setAppConfig } from '../support/nc-provisioning'
9+
import { configureOpenSsl, deleteAppConfig, setAppConfig } from '../support/nc-provisioning'
1010
import { createMailpitClient, waitForEmailTo, extractSignLink } from '../support/mailpit'
1111

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

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

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

9193
// Signer01 signs via the link received in the email
9294
const signLink = extractSignLink(email01.Text)

playwright/e2e/sign-email-token-unauthenticated.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

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

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

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

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

6164
const email = await waitForEmailTo(mailpit, 'signer01@libresign.coop', 'LibreSign: There is a file for you to sign')
6265
const signLink = extractSignLink(email.Text)
@@ -82,10 +85,5 @@ test('sign document with email token as unauthenticated signer', async ({ page }
8285
await page.waitForURL('**/validation/**');
8386
await expect(page.getByText('This document is valid')).toBeVisible();
8487
await expect(page.getByText('Congratulations you have')).toBeVisible();
85-
86-
// Revisit the sign link after the document has been signed.
87-
// The signer must not be able to sign a second time.
88-
await page.goto(signLink)
89-
await expect(page.getByRole('button', { name: 'Sign the document.' })).not.toBeVisible({ timeout: 10_000 })
90-
await expect(page.getByText('This document is valid')).toBeVisible();
88+
await expect(page.getByRole('button', { name: 'Sign the document.' })).not.toBeVisible();
9189
});

playwright/e2e/sign-herself-updates-files-list-with-native-engine.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ test('updates files list status after signing with native engine', async ({ page
7373

7474
await targetRow.getByRole('button', { name: 'Actions' }).click()
7575
await page.getByRole('menuitem', { name: 'Sign' }).click()
76-
await page.getByRole('button', { name: 'Sign the document.' }).click()
76+
await page.waitForURL('**/f/sign/**/pdf')
77+
const signButton = page.getByRole('button', { name: 'Sign the document.' })
78+
await expect(signButton).toBeVisible()
79+
await signButton.click()
7780
await page.getByRole('button', { name: 'Sign document' }).click()
7881
await page.waitForURL('**/validation/**')
7982
await expect(page.getByText('This document is valid')).toBeVisible()

playwright/e2e/sign-herself-with-drawn-signature.spec.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,6 @@ test('sign herself with drawn signature', async ({ page }) => {
9494
page.getByLabel('PDF document to sign').getByRole('img', { name: 'Signature position for Admin Name' })
9595
).toBeVisible()
9696

97-
// If a signature already exists from a previous run, delete it before creating a new one
98-
const deleteSignatureBtn = page.getByRole('button', { name: 'Delete signature' })
99-
await deleteSignatureBtn.waitFor({ state: 'visible', timeout: 3000 }).catch(() => null)
100-
if (await deleteSignatureBtn.isVisible()) {
101-
await deleteSignatureBtn.click()
102-
}
103-
10497
await page.getByRole('button', { name: 'Define your signature.' }).click();
10598

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

133127
await page.getByRole('button', { name: 'Sign the document.' }).click();
134128
await page.getByRole('button', { name: 'Sign document' }).click();

playwright/support/nc-provisioning.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ type OcsResponse<T = unknown> = {
1818
}
1919
}
2020

21+
type SignatureElementResponse = {
22+
elements?: Array<{
23+
type: string
24+
file: {
25+
nodeId: number
26+
}
27+
}>
28+
}
29+
2130
async function ocsRequest(
2231
request: APIRequestContext,
2332
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
@@ -44,7 +53,6 @@ async function ocsRequest(
4453
: body !== undefined ? { form: body } : {}),
4554
failOnStatusCode: false,
4655
})
47-
4856
if (!response.ok() && response.status() !== 404) {
4957
throw new Error(`OCS request failed: ${method} ${path}${response.status()} ${await response.text()}`)
5058
}
@@ -56,6 +64,30 @@ async function ocsRequest(
5664
return JSON.parse(text) as OcsResponse
5765
}
5866

67+
export async function clearSignatureElements(
68+
request: APIRequestContext,
69+
userId = process.env.NEXTCLOUD_ADMIN_USER ?? 'admin',
70+
password = process.env.NEXTCLOUD_ADMIN_PASSWORD ?? 'admin',
71+
): Promise<void> {
72+
const result = await ocsRequest<SignatureElementResponse>(
73+
request,
74+
'GET',
75+
'/apps/libresign/api/v1/signature/elements',
76+
userId,
77+
password,
78+
)
79+
80+
for (const element of result.ocs.data.elements ?? []) {
81+
await ocsRequest(
82+
request,
83+
'DELETE',
84+
`/apps/libresign/api/v1/signature/elements/${element.file.nodeId}`,
85+
userId,
86+
password,
87+
)
88+
}
89+
}
90+
5991
// ---------------------------------------------------------------------------
6092
// Users
6193
// ---------------------------------------------------------------------------
@@ -186,4 +218,6 @@ export async function configureOpenSsl(
186218
if (result.ocs.meta.statuscode !== 200) {
187219
throw new Error(`Failed to configure OpenSSL: ${result.ocs.meta.message}`)
188220
}
221+
222+
await clearSignatureElements(request)
189223
}

0 commit comments

Comments
 (0)