mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Initial Remove Password implementation
This commit is contained in:
parent
4c17c520d7
commit
25cc9ec8d3
@ -811,16 +811,6 @@
|
|||||||
"removePages": {
|
"removePages": {
|
||||||
"tags": "Remove pages,delete pages"
|
"tags": "Remove pages,delete pages"
|
||||||
},
|
},
|
||||||
"removePassword": {
|
|
||||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
|
||||||
"title": "Remove password",
|
|
||||||
"header": "Remove password (Decrypt)",
|
|
||||||
"selectText": {
|
|
||||||
"1": "Select PDF to Decrypt",
|
|
||||||
"2": "Password"
|
|
||||||
},
|
|
||||||
"submit": "Remove"
|
|
||||||
},
|
|
||||||
"compressPdfs": {
|
"compressPdfs": {
|
||||||
"tags": "squish,small,tiny"
|
"tags": "squish,small,tiny"
|
||||||
},
|
},
|
||||||
@ -1875,5 +1865,21 @@
|
|||||||
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"removePassword": {
|
||||||
|
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||||
|
"title": "Remove Password",
|
||||||
|
"password": {
|
||||||
|
"label": "Current Password",
|
||||||
|
"placeholder": "Enter current password",
|
||||||
|
"completed": "Password configured"
|
||||||
|
},
|
||||||
|
"filenamePrefix": "decrypted",
|
||||||
|
"error": {
|
||||||
|
"failed": "An error occurred while removing the password from the PDF."
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"title": "Decrypted PDFs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,16 +752,6 @@
|
|||||||
"removePages": {
|
"removePages": {
|
||||||
"tags": "Remove pages,delete pages"
|
"tags": "Remove pages,delete pages"
|
||||||
},
|
},
|
||||||
"removePassword": {
|
|
||||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
|
||||||
"title": "Remove password",
|
|
||||||
"header": "Remove password (Decrypt)",
|
|
||||||
"selectText": {
|
|
||||||
"1": "Select PDF to Decrypt",
|
|
||||||
"2": "Password"
|
|
||||||
},
|
|
||||||
"submit": "Remove"
|
|
||||||
},
|
|
||||||
"compressPdfs": {
|
"compressPdfs": {
|
||||||
"tags": "squish,small,tiny"
|
"tags": "squish,small,tiny"
|
||||||
},
|
},
|
||||||
@ -1737,5 +1727,21 @@
|
|||||||
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"removePassword": {
|
||||||
|
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||||
|
"title": "Remove Password",
|
||||||
|
"password": {
|
||||||
|
"label": "Current Password",
|
||||||
|
"placeholder": "Enter current password",
|
||||||
|
"completed": "Password configured"
|
||||||
|
},
|
||||||
|
"filenamePrefix": "decrypted",
|
||||||
|
"error": {
|
||||||
|
"failed": "An error occurred while removing the password from the PDF."
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"title": "Decrypted PDFs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Stack, Text, PasswordInput } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { RemovePasswordParameters } from "../../../hooks/tools/removePassword/useRemovePasswordParameters";
|
||||||
|
|
||||||
|
interface RemovePasswordSettingsProps {
|
||||||
|
parameters: RemovePasswordParameters;
|
||||||
|
onParameterChange: (key: keyof RemovePasswordParameters, value: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemovePasswordSettings = ({ parameters, onParameterChange, disabled = false }: RemovePasswordSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<PasswordInput
|
||||||
|
label={t('removePassword.password.label', 'Current Password')}
|
||||||
|
placeholder={t('removePassword.password.placeholder', 'Enter current password')}
|
||||||
|
value={parameters.password}
|
||||||
|
onChange={(e) => onParameterChange('password', e.target.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RemovePasswordSettings;
|
@ -0,0 +1,24 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
|
import { RemovePasswordParameters } from './useRemovePasswordParameters';
|
||||||
|
|
||||||
|
export const useRemovePasswordOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const buildFormData = (parameters: RemovePasswordParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("fileInput", file);
|
||||||
|
formData.append("password", parameters.password);
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useToolOperation<RemovePasswordParameters>({
|
||||||
|
operationType: 'removePassword',
|
||||||
|
endpoint: '/api/v1/security/remove-password',
|
||||||
|
buildFormData,
|
||||||
|
filePrefix: t('removePassword.filenamePrefix', 'decrypted') + '_',
|
||||||
|
multiFileEndpoint: false,
|
||||||
|
getErrorMessage: createStandardErrorHandler(t('removePassword.error.failed', 'An error occurred while removing the password from the PDF.'))
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export interface RemovePasswordParameters {
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemovePasswordParametersHook {
|
||||||
|
parameters: RemovePasswordParameters;
|
||||||
|
updateParameter: <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => void;
|
||||||
|
resetParameters: () => void;
|
||||||
|
validateParameters: () => boolean;
|
||||||
|
getEndpointName: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultParameters: RemovePasswordParameters = {
|
||||||
|
password: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRemovePasswordParameters = (): RemovePasswordParametersHook => {
|
||||||
|
const [parameters, setParameters] = useState<RemovePasswordParameters>(defaultParameters);
|
||||||
|
|
||||||
|
const updateParameter = <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => {
|
||||||
|
setParameters(prev => ({
|
||||||
|
...prev,
|
||||||
|
[parameter]: value,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetParameters = () => {
|
||||||
|
setParameters(defaultParameters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateParameters = () => {
|
||||||
|
return parameters.password !== '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEndpointName = () => {
|
||||||
|
return 'remove-password';
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
parameters,
|
||||||
|
updateParameter,
|
||||||
|
resetParameters,
|
||||||
|
validateParameters,
|
||||||
|
getEndpointName,
|
||||||
|
};
|
||||||
|
};
|
@ -6,8 +6,9 @@ import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
|||||||
import ApiIcon from "@mui/icons-material/Api";
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||||
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
||||||
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
|
import { Tool, ToolDefinition, ToolRegistry } from "../types/tool";
|
||||||
|
|
||||||
|
|
||||||
// Add entry here with maxFiles, endpoints, and lazy component
|
// Add entry here with maxFiles, endpoints, and lazy component
|
||||||
@ -104,6 +105,15 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|||||||
description: "Change document restrictions and permissions",
|
description: "Change document restrictions and permissions",
|
||||||
endpoints: ["add-password"]
|
endpoints: ["add-password"]
|
||||||
},
|
},
|
||||||
|
removePassword: {
|
||||||
|
id: "removePassword",
|
||||||
|
icon: <LockOpenIcon />,
|
||||||
|
component: React.lazy(() => import("../tools/RemovePassword")),
|
||||||
|
maxFiles: -1,
|
||||||
|
category: "security",
|
||||||
|
description: "Remove password protection from PDF files",
|
||||||
|
endpoints: ["remove-password"]
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
167
frontend/src/tools/RemovePassword.tsx
Normal file
167
frontend/src/tools/RemovePassword.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { Box, Button, Stack, Text } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
|
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
||||||
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
|
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
||||||
|
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
||||||
|
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
||||||
|
|
||||||
|
import RemovePasswordSettings from "../components/tools/removePassword/RemovePasswordSettings";
|
||||||
|
|
||||||
|
import { useRemovePasswordParameters } from "../hooks/tools/removePassword/useRemovePasswordParameters";
|
||||||
|
import { useRemovePasswordOperation } from "../hooks/tools/removePassword/useRemovePasswordOperation";
|
||||||
|
import { BaseToolProps } from "../types/tool";
|
||||||
|
|
||||||
|
const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setCurrentMode } = useFileContext();
|
||||||
|
const { selectedFiles } = useToolFileSelection();
|
||||||
|
|
||||||
|
const removePasswordParams = useRemovePasswordParameters();
|
||||||
|
const removePasswordOperation = useRemovePasswordOperation();
|
||||||
|
|
||||||
|
// Endpoint validation
|
||||||
|
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(removePasswordParams.getEndpointName());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
removePasswordOperation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
}, [removePasswordParams.parameters, selectedFiles]);
|
||||||
|
|
||||||
|
const handleRemovePassword = async () => {
|
||||||
|
try {
|
||||||
|
await removePasswordOperation.executeOperation(
|
||||||
|
removePasswordParams.parameters,
|
||||||
|
selectedFiles
|
||||||
|
);
|
||||||
|
if (removePasswordOperation.files && onComplete) {
|
||||||
|
onComplete(removePasswordOperation.files);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (onError) {
|
||||||
|
onError(error instanceof Error ? error.message : t('removePassword.error.failed', 'Remove password operation failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThumbnailClick = (file: File) => {
|
||||||
|
onPreviewFile?.(file);
|
||||||
|
sessionStorage.setItem('previousMode', 'removePassword');
|
||||||
|
setCurrentMode('viewer');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSettingsReset = () => {
|
||||||
|
removePasswordOperation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
setCurrentMode('removePassword');
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFiles = selectedFiles.length > 0;
|
||||||
|
const hasResults = removePasswordOperation.files.length > 0 || removePasswordOperation.downloadUrl !== null;
|
||||||
|
const filesCollapsed = hasFiles;
|
||||||
|
const passwordCollapsed = hasResults;
|
||||||
|
|
||||||
|
const previewResults = useMemo(() =>
|
||||||
|
removePasswordOperation.files?.map((file, index) => ({
|
||||||
|
file,
|
||||||
|
thumbnail: removePasswordOperation.thumbnails[index]
|
||||||
|
})) || [],
|
||||||
|
[removePasswordOperation.files, removePasswordOperation.thumbnails]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolStepContainer>
|
||||||
|
<Stack gap="sm" h="94vh" p="sm" style={{ overflow: 'auto' }}>
|
||||||
|
{/* Files Step */}
|
||||||
|
<ToolStep
|
||||||
|
title={t('files.title', 'Files')}
|
||||||
|
isVisible={true}
|
||||||
|
isCollapsed={filesCollapsed}
|
||||||
|
isCompleted={filesCollapsed}
|
||||||
|
completedMessage={hasFiles ?
|
||||||
|
selectedFiles.length === 1
|
||||||
|
? t('files.selected.single', 'Selected: {{filename}}', { filename: selectedFiles[0].name })
|
||||||
|
: t('files.selected.multiple', 'Selected: {{count}} files', { count: selectedFiles.length })
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
<FileStatusIndicator
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
placeholder={t('files.placeholder', 'Select a PDF file in the main view to get started')}
|
||||||
|
/>
|
||||||
|
</ToolStep>
|
||||||
|
|
||||||
|
{/* Password Step */}
|
||||||
|
<ToolStep
|
||||||
|
title={t('removePassword.title', 'Remove Password')}
|
||||||
|
isVisible={hasFiles}
|
||||||
|
isCollapsed={passwordCollapsed}
|
||||||
|
isCompleted={passwordCollapsed}
|
||||||
|
onCollapsedClick={hasResults ? handleSettingsReset : undefined}
|
||||||
|
completedMessage={passwordCollapsed ? t('removePassword.password.completed', 'Password configured') : undefined}
|
||||||
|
>
|
||||||
|
<RemovePasswordSettings
|
||||||
|
parameters={removePasswordParams.parameters}
|
||||||
|
onParameterChange={removePasswordParams.updateParameter}
|
||||||
|
disabled={endpointLoading}
|
||||||
|
/>
|
||||||
|
</ToolStep>
|
||||||
|
|
||||||
|
<Box mt="md">
|
||||||
|
<OperationButton
|
||||||
|
onClick={handleRemovePassword}
|
||||||
|
isLoading={removePasswordOperation.isLoading}
|
||||||
|
disabled={!removePasswordParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||||
|
loadingText={t('loading')}
|
||||||
|
submitText={t('removePassword.submit', 'Remove Password')}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Results Step */}
|
||||||
|
<ToolStep
|
||||||
|
title={t('results.title', 'Results')}
|
||||||
|
isVisible={hasResults}
|
||||||
|
>
|
||||||
|
<Stack gap="sm">
|
||||||
|
{removePasswordOperation.status && (
|
||||||
|
<Text size="sm" c="dimmed">{removePasswordOperation.status}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ErrorNotification
|
||||||
|
error={removePasswordOperation.errorMessage}
|
||||||
|
onClose={removePasswordOperation.clearError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{removePasswordOperation.downloadUrl && (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
href={removePasswordOperation.downloadUrl}
|
||||||
|
download={removePasswordOperation.downloadFilename}
|
||||||
|
leftSection={<DownloadIcon />}
|
||||||
|
color="green"
|
||||||
|
fullWidth
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
{t("download", "Download")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ResultsPreview
|
||||||
|
files={previewResults}
|
||||||
|
onFileClick={handleThumbnailClick}
|
||||||
|
isGeneratingThumbnails={removePasswordOperation.isGeneratingThumbnails}
|
||||||
|
title={t('removePassword.results.title', 'Decrypted PDFs')}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ToolStep>
|
||||||
|
</Stack>
|
||||||
|
</ToolStepContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemovePassword;
|
@ -16,7 +16,8 @@ export type ModeType =
|
|||||||
| 'convert'
|
| 'convert'
|
||||||
| 'sanitize'
|
| 'sanitize'
|
||||||
| 'addPassword'
|
| 'addPassword'
|
||||||
| 'changePermissions';
|
| 'changePermissions'
|
||||||
|
| 'removePassword';
|
||||||
|
|
||||||
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user