mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
feat(desktop): gate shared signing behind self-hosted auth (#6002)
## Summary This PR adds full desktop (Tauri) support for the shared signing feature when connected to a self-hosted server, and fixes several bugs discovered during that work. ### Feature gating Shared signing, file sharing, and share links are proprietary server features that require an authenticated self-hosted session. Previously these were read directly from `config` with no awareness of connection mode or auth state, meaning the UI could appear in SaaS/local mode or when logged out. - Introduce `useGroupSigningEnabled` and `useSharingEnabled` hooks with core implementations (web behaviour unchanged) and desktop overrides that require `selfhosted` mode + an active authenticated session - Extract shared subscription logic into `useSelfHostedAuth` (connection mode + auth state + config refetch) - `QuickAccessBar` now derives all three flags from the hooks instead of raw config ### Config timing fix When a user logs in via the SetupWizard, the `jwt-available` event fires a config fetch *before* the mode is switched to `selfhosted`. This meant the config was fetched from the local bundled backend (port ~59567) which has no knowledge of `storageGroupSigningEnabled`, causing the group signing button to stay hidden until a full page refresh. `useSelfHostedAuth` detects the mode transition and triggers a fresh config fetch at the correct moment, after the self-hosted URL is active. ### Bug fixes **`SignPopout.tsx`** — Manually setting `Content-Type: multipart/form-data` on two `FormData` POST requests stripped the auto-generated boundary, causing a `400 bad multipart` from the server. Removed the explicit headers so Axios sets them correctly. **`tauriHttpClient.ts`** — `response.json()` was called before `response.ok` was checked. A plain-text error body from the server (e.g. `"Cannot sign..."`) caused a `SyntaxError` that fell into the network error catch block and was reported as `ERR_NETWORK`, hiding the real failure. The fix checks `response.ok` first, reads error bodies as text, and handles empty 200 bodies (returning `null` instead of throwing). --- ## Testing ### Prerequisites - Desktop app running in self-hosted mode pointed at a local Stirling-PDF instance (`http://localhost:8080`) - The self-hosted instance has group signing and storage enabled in settings - At least two user accounts on the self-hosted instance ### 1. Feature gating — group signing button | Step | Expected | |---|---| | Open the desktop app in **local mode** (no server configured) | Group signing button absent from QuickAccessBar | | Switch to self-hosted mode but **do not log in** | Group signing button absent | | Log in to the self-hosted server | Group signing button appears without requiring a page refresh | | Log out | Group signing button disappears immediately | | Log back in | Group signing button reappears without a page refresh | ### 2. Feature gating — file sharing Repeat the same steps above, verifying the share and share-link buttons in the file manager follow the same visibility rules. ### 3. Create a signing session 1. Log in, open the group signing panel from QuickAccessBar 2. Select a PDF, add a participant, configure signature defaults and submit 3. Verify the session is created successfully (no `400 bad multipart` error) ### 4. Participant signing 1. As the invited participant, open the signing request from QuickAccessBar 2. Upload or draw a signature and submit 3. Verify signing completes successfully (no `ERR_NETWORK` error) ### 5. Error surfacing 1. Attempt an action that the server rejects (e.g. sign a document with an invalid certificate) 2. Verify the actual server error message is shown rather than a generic network error
This commit is contained in:
@@ -19,6 +19,8 @@ import AllToolsNavButton from '@app/components/shared/AllToolsNavButton';
|
||||
import ActiveToolButton from "@app/components/shared/quickAccessBar/ActiveToolButton";
|
||||
import AppConfigModal from '@app/components/shared/AppConfigModal';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import { useGroupSigningEnabled } from '@app/hooks/useGroupSigningEnabled';
|
||||
import { useSharingEnabled } from '@app/hooks/useSharingEnabled';
|
||||
import { useLicenseAlert } from "@app/hooks/useLicenseAlert";
|
||||
import { requestStartTour } from '@app/constants/events';
|
||||
import QuickAccessButton from '@app/components/shared/quickAccessBar/QuickAccessButton';
|
||||
@@ -77,9 +79,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
const accessButtonRef = useRef<HTMLDivElement>(null);
|
||||
const accessPopoverRef = useRef<HTMLDivElement>(null);
|
||||
const [accessPopoverPosition, setAccessPopoverPosition] = useState({ top: 160, left: 84 });
|
||||
const sharingEnabled = config?.storageSharingEnabled === true;
|
||||
const shareLinksEnabled = config?.storageShareLinksEnabled === true;
|
||||
const groupSigningEnabled = config?.storageGroupSigningEnabled === true;
|
||||
const { sharingEnabled, shareLinksEnabled } = useSharingEnabled();
|
||||
const groupSigningEnabled = useGroupSigningEnabled();
|
||||
const isSignWorkbenchActive =
|
||||
currentWorkbench === SIGN_REQUEST_WORKBENCH_TYPE ||
|
||||
currentWorkbench === SESSION_DETAIL_WORKBENCH_TYPE;
|
||||
|
||||
@@ -335,9 +335,7 @@ const SignPopout = ({ isOpen, onClose, buttonRef, isRTL, groupSigningEnabled }:
|
||||
formData.append('workflowMetadata', workflowMetadata);
|
||||
}
|
||||
|
||||
await apiClient.post('/api/v1/security/cert-sign/sessions', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
await apiClient.post('/api/v1/security/cert-sign/sessions', formData);
|
||||
|
||||
alert({
|
||||
alertType: 'success',
|
||||
@@ -493,9 +491,7 @@ const SignPopout = ({ isOpen, onClose, buttonRef, isRTL, groupSigningEnabled }:
|
||||
|
||||
// Action handlers
|
||||
const handleSign = async (sessionId: string, certificateData: FormData) => {
|
||||
await apiClient.post(`/api/v1/security/cert-sign/sign-requests/${sessionId}/sign`, certificateData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
await apiClient.post(`/api/v1/security/cert-sign/sign-requests/${sessionId}/sign`, certificateData);
|
||||
alert({
|
||||
alertType: 'success',
|
||||
title: t('success'),
|
||||
|
||||
Reference in New Issue
Block a user