Skip to content

feat(signing): add external/guest user signing support#6011

Draft
ConnorYoh wants to merge 2 commits intomainfrom
worktree-guest-signing-support
Draft

feat(signing): add external/guest user signing support#6011
ConnorYoh wants to merge 2 commits intomainfrom
worktree-guest-signing-support

Conversation

@ConnorYoh
Copy link
Copy Markdown
Member

Summary

Adds support for inviting external (non-registered) users to sign documents via a one-time share link, completing the guest signing flow end-to-end.

Backend

  • GuestCertificateService — generates ephemeral PKCS12 keystores keyed by signer email; X.509 cert includes rfc822Name SAN for industry-traceable digital signatures, uses emailProtection EKU, cryptographically random serial numbers, and HMAC-SHA256 derived passwords (never persisted)
  • SigningFinalizationService — handles GUEST_CERT case in buildKeystore() and getKeystorePassword()
  • WorkflowParticipantController — defaults certType to GUEST_CERT for unregistered participants, captures SHA-256 hashed-IP audit trail on signature submission, skips password encryption for GUEST_CERT paths
  • WorkflowSessionService — sends signing invitation email to guest participants on session creation; email PII redacted to domain-only in logs
  • EmailService — adds sendSigningInvitationEmail() with full HTML escaping on all user-supplied values and javascript: URL guard

Frontend

  • GuestSignPage — new token-gated route /sign/:token rendered outside AppProviders (no auth required); full page-state machine: loading → ready → expired / signed / declined / error; includes PDF preview iframe, wet signature canvas, and cert chooser
  • GuestCertificateChooser — Radio group: auto-generated guest cert (recommended) vs upload own P12
  • SelectParticipantsStep — refactored with Registered / External tabs; external tab accepts email + optional name with format validation and duplicate detection
  • AddParticipantsFlow — same external email tab for mid-session participant adds
  • CreateSessionFlow / SignPopout — thread new Participant[] shape through; split into participantUserIds and participantEmails on submit
  • App.tsx/sign/:token route with minimal GuestSigningProviders wrapper (Mantine + preferences only, no backend guard)
  • i18n[guestSigning] section added to en-GB/translation.toml

Tests

  • GuestCertificateServiceTest — keystore validity, SAN contains rfc822Name, password determinism and uniqueness
  • WorkflowParticipantControllerTestGUEST_CERT defaulting, audit trail capture, encrypt() never called for guest cert, all token guard cases (expired / already-signed / declined / unknown / blank)
  • EmailServiceTest — invitation email, HTML injection safety, javascript: URL guard, null optional params
  • GuestSignPage.test.tsx — 9 vitest tests covering all page states + submission
  • SelectParticipantsStep.test.tsx — 15 vitest tests: tabs, validation, duplicate detection, remove, callbacks
  • GuestSigningE2E.spec.ts — 19 Playwright tests (route-mocked, no backend required): full flow from loading spinner through submit success and decline confirmation
  • playwright.config.ts — fixed testDir to src/core/tests, updated port to 5174

Test plan

  • Run ./gradlew :proprietary:test — all 31 new backend tests pass
  • Run npm test -- --run --project=core in frontend/ — all 24 new unit tests pass
  • Run npx playwright test guestSigning --project=chromium — all 19 E2E tests pass
  • Owner creates session with an external email address → guest receives invitation email
  • Guest opens /sign/:token, reviews PDF, signs with auto-generated cert → session finalises with CN=<email> + SAN in digital signature
  • Guest opens expired link → shows "link has expired" state, no form rendered
  • Guest revisits already-signed link → shows "already signed" state
  • Validate finalised PDF signature via POST /api/v1/security/validate-signature — expect rfc822Name SAN and emailProtection EKU

@stirlingbot
Copy link
Copy Markdown
Contributor

stirlingbot bot commented Mar 27, 2026

🌐 TOML Translation Verification Summary

🔄 Reference Branch: pr-branch

📃 File Check: en-GB/translation.toml

  1. Test Status:Passed
  2. Test Status:Passed
  3. Test Status:Passed

✅ Overall Check Status: Success

Thanks @ConnorYoh for your help in keeping the translations up to date.

@stirlingbot stirlingbot bot added Documentation Improvements or additions to documentation enhancement New feature or request Java Pull requests that update Java code Front End Issues or pull requests related to front-end development Translation Security Security-related issues or pull requests Test Testing-related issues or pull requests labels Mar 27, 2026
Allow document owners to invite external users (no Stirling-PDF account)
to sign documents via a one-time share link.

Backend:
- GuestCertificateService: generates ephemeral PKCS12 keystores keyed by
  email, with rfc822Name SAN for industry-traceable digital signatures;
  uses emailProtection EKU, cryptographically random serial numbers, and
  HMAC-SHA256 derived passwords
- SigningFinalizationService: handles GUEST_CERT case in buildKeystore()
  and getKeystorePassword()
- WorkflowParticipantController: defaults certType to GUEST_CERT for
  unregistered participants, captures hashed-IP audit trail on submission,
  skips password encryption for GUEST_CERT paths
- WorkflowSessionService: sends signing invitation email to guest
  participants on session creation; redacts email PII in logs
- EmailService: adds sendSigningInvitationEmail() with HTML-escaped
  user-supplied values and javascript: URL guard to prevent XSS

Frontend:
- GuestSignPage: new token-gated route /sign/:token outside AppProviders;
  full page-state machine (loading/ready/expired/signed/declined/error)
- GuestCertificateChooser: auto-generated cert vs upload P12 chooser
- SelectParticipantsStep: refactored to support registered + external
  (email) participants via tabbed UI with validation
- AddParticipantsFlow: same external email tab added for mid-session adds
- CreateSessionFlow/SignPopout: thread new Participant[] shape through
  to split userIds and emails on submit
- App.tsx: /sign/:token route with minimal GuestSigningProviders (no auth)
- i18n: [guestSigning] keys added to en-GB/translation.toml

Tests:
- GuestCertificateServiceTest: keystore generation, SAN, deterministic passwords
- WorkflowParticipantControllerTest: GUEST_CERT defaulting, audit trail,
  token guards, encrypt() not called for guest cert
- EmailServiceTest: invitation email, HTML injection safety, JS URL guard
- GuestSignPage.test.tsx: 9 vitest unit tests (all page states + submit)
- SelectParticipantsStep.test.tsx: 15 vitest unit tests
- GuestSigningE2E.spec.ts: 19 Playwright E2E tests (route-mocked, no backend)
- playwright.config.ts: fix testDir to src/core/tests, use port 5174
@ConnorYoh ConnorYoh force-pushed the worktree-guest-signing-support branch from 28f404a to 5f553ec Compare March 27, 2026 17:51
@stirlingbot
Copy link
Copy Markdown
Contributor

stirlingbot bot commented Mar 30, 2026

🚀 V2 Auto-Deployment Complete!

Your V2 PR with embedded architecture has been deployed!

🔗 Direct Test URL (non-SSL) http://54.175.155.236:6011

🔐 Secure HTTPS URL: https://6011.ssl.stirlingpdf.cloud

This deployment will be automatically cleaned up when the PR is closed.

🔄 Auto-deployed for approved V2 contributors.

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

Labels

Documentation Improvements or additions to documentation enhancement New feature or request Front End Issues or pull requests related to front-end development Java Pull requests that update Java code Security Security-related issues or pull requests Test Testing-related issues or pull requests Translation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant