This commit is contained in:
Anthony Stirling 2025-09-03 09:20:47 +00:00 committed by GitHub
commit ee712f38da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 897 additions and 13 deletions

View File

@ -426,6 +426,10 @@
"title": "Flatten", "title": "Flatten",
"desc": "Remove all interactive elements and forms from a PDF" "desc": "Remove all interactive elements and forms from a PDF"
}, },
"manageSignatures": {
"title": "Sign with Certificate",
"desc": "Add digital signatures to PDF documents using certificates"
},
"repair": { "repair": {
"title": "Repair", "title": "Repair",
"desc": "Tries to repair a corrupt/broken PDF" "desc": "Tries to repair a corrupt/broken PDF"
@ -2384,5 +2388,146 @@
"processImages": "Process Images", "processImages": "Process Images",
"processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images." "processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
} }
},
"manageSignatures": {
"title": "Sign with Certificate",
"filenamePrefix": "signed",
"files": {
"placeholder": "Select PDF files to sign with certificates"
},
"fileStatus": {
"stepTitle": "File Status"
},
"fileNavigation": "File {{current}} of {{total}}",
"hasSignatures": "Contains {{count}} signature(s)",
"noSignatures": "No signatures detected",
"signed": "Signed",
"certType": {
"stepTitle": "Certificate Type",
"tooltip": {
"header": {
"title": "About Certificate Types"
},
"what": {
"title": "What's a certificate?",
"text": "It's a secure ID for your signature that proves you signed. Unless you're required to sign via certificate, we recommend using another secure method like Type, Draw, or Upload."
},
"which": {
"title": "Which option should I use?",
"text": "Choose the format that matches your certificate file:",
"bullet1": "PKCS#12 (.p12 / .pfx) one combined file (most common)",
"bullet2": "PEM separate private-key and certificate .pem files",
"bullet3": "JKS Java .jks keystore for dev / CI-CD workflows"
},
"convert": {
"title": "Key not listed?",
"text": "Convert your file to a Java keystore (.jks) with keytool, then pick JKS."
}
}
},
"certFiles": {
"stepTitle": "Certificate Files"
},
"appearance": {
"stepTitle": "Signature Appearance",
"title": "Signature Appearance",
"invisible": "Invisible",
"visible": "Visible",
"options": {
"title": "Signature Details"
},
"tooltip": {
"header": {
"title": "About Signature Appearance"
},
"invisible": {
"title": "Invisible Signatures",
"text": "The signature is added to the PDF for security but won't be visible when viewing the document. Perfect for legal requirements without changing the document's appearance.",
"bullet1": "Provides security without visual changes",
"bullet2": "Meets legal requirements for digital signing",
"bullet3": "Doesn't affect document layout or design"
},
"visible": {
"title": "Visible Signatures",
"text": "Shows a signature block on the PDF with your name, date, and optional details. Useful when you want readers to clearly see the document is signed.",
"bullet1": "Shows signer name and date on the document",
"bullet2": "Can include reason and location for signing",
"bullet3": "Choose which page to place the signature",
"bullet4": "Optional logo can be included"
}
}
},
"mode": {
"title": "Action",
"validate": "Check for Signatures",
"viewEdit": "View/Edit Signatures",
"sign": "Add New Signature"
},
"validation": {
"title": "Validation Options",
"customCert": "Custom Certificate (Optional)",
"customCert.desc": "Upload a custom certificate for validation"
},
"signing": {
"title": "Certificate Settings",
"certType": "Certificate Type",
"choosePrivateKey": "Choose Private Key File",
"chooseCertificate": "Choose Certificate File",
"chooseP12File": "Choose PKCS12 File",
"chooseJksFile": "Choose JKS File",
"password": "Certificate Password",
"passwordOptional": "Leave empty if no password",
"showSignature": "Show visible signature on PDF",
"reason": "Reason for Signing",
"location": "Location",
"name": "Signer Name",
"pageNumber": "Page Number",
"logoTitle": "Logo",
"noLogo": "No Logo",
"showLogo": "Show Logo"
},
"validate": {
"submit": "Validate Signatures",
"results": "Signature Validation Results"
},
"sign": {
"submit": "Sign PDF",
"results": "Signed PDF"
},
"results": {
"title": "Signature Results"
},
"error": {
"failed": "An error occurred whilst processing signatures."
},
"tooltip": {
"header": {
"title": "About Managing Signatures"
},
"overview": {
"title": "What can this tool do?",
"text": "This tool lets you check if your PDFs are digitally signed and add new digital signatures. Digital signatures prove who created or approved a document and show if it has been changed since signing.",
"bullet1": "Check existing signatures and their validity",
"bullet2": "View detailed information about signers and certificates",
"bullet3": "Add new digital signatures to secure your documents",
"bullet4": "Multiple files supported with easy navigation"
},
"validation": {
"title": "Checking Signatures",
"text": "When you check signatures, the tool tells you if they're valid, who signed the document, when it was signed, and whether the document has been changed since signing.",
"bullet1": "Shows if signatures are valid or invalid",
"bullet2": "Displays signer information and signing date",
"bullet3": "Checks if the document was modified after signing",
"bullet4": "Can use custom certificates for verification"
},
"signing": {
"title": "Adding Signatures",
"text": "To sign a PDF, you need a digital certificate (like PEM, PKCS12, or JKS). You can choose to make the signature visible on the document or keep it invisible for security only.",
"bullet1": "Supports PEM, PKCS12, and JKS certificate formats",
"bullet2": "Option to show or hide signature on the PDF",
"bullet3": "Add reason, location, and signer name",
"bullet4": "Choose which page to place visible signatures"
}
}
} }
} }

View File

@ -0,0 +1,78 @@
import { Stack, Text, TextInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
import FileUploadButton from "../../shared/FileUploadButton";
interface CertificateFilesSettingsProps {
parameters: ManageSignaturesParameters;
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
disabled?: boolean;
}
const CertificateFilesSettings = ({ parameters, onParameterChange, disabled = false }: CertificateFilesSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
{/* Certificate Files based on type */}
{parameters.certType === 'PEM' && (
<Stack gap="sm">
<FileUploadButton
file={parameters.privateKeyFile}
onChange={(file) => onParameterChange('privateKeyFile', file || undefined)}
accept=".pem,.der"
disabled={disabled}
placeholder={t('manageSignatures.signing.choosePrivateKey', 'Choose Private Key File')}
/>
{parameters.privateKeyFile && (
<FileUploadButton
file={parameters.certFile}
onChange={(file) => onParameterChange('certFile', file || undefined)}
accept=".pem,.der"
disabled={disabled}
placeholder={t('manageSignatures.signing.chooseCertificate', 'Choose Certificate File')}
/>
)}
</Stack>
)}
{parameters.certType === 'PKCS12' && (
<FileUploadButton
file={parameters.p12File}
onChange={(file) => onParameterChange('p12File', file || undefined)}
accept=".p12,.pfx"
disabled={disabled}
placeholder={t('manageSignatures.signing.chooseP12File', 'Choose PKCS12 File')}
/>
)}
{parameters.certType === 'JKS' && (
<FileUploadButton
file={parameters.jksFile}
onChange={(file) => onParameterChange('jksFile', file || undefined)}
accept=".jks,.keystore"
disabled={disabled}
placeholder={t('manageSignatures.signing.chooseJksFile', 'Choose JKS File')}
/>
)}
{/* Password - only show when files are uploaded */}
{parameters.certType && (
(parameters.certType === 'PEM' && parameters.privateKeyFile && parameters.certFile) ||
(parameters.certType === 'PKCS12' && parameters.p12File) ||
(parameters.certType === 'JKS' && parameters.jksFile)
) && (
<TextInput
label={t('manageSignatures.signing.password', 'Certificate Password')}
placeholder={t('manageSignatures.signing.passwordOptional', 'Leave empty if no password')}
type="password"
value={parameters.password}
onChange={(event) => onParameterChange('password', event.currentTarget.value)}
disabled={disabled}
/>
)}
</Stack>
);
};
export default CertificateFilesSettings;

View File

@ -0,0 +1,60 @@
import { Stack, Button } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
interface CertificateTypeSettingsProps {
parameters: ManageSignaturesParameters;
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
disabled?: boolean;
}
const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = false }: CertificateTypeSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
{/* Certificate Type Selection */}
<Stack gap="sm">
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={parameters.certType === 'PKCS12' ? 'filled' : 'outline'}
color={parameters.certType === 'PKCS12' ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('certType', 'PKCS12')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
PKCS#12
</div>
</Button>
<Button
variant={parameters.certType === 'PEM' ? 'filled' : 'outline'}
color={parameters.certType === 'PEM' ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('certType', 'PEM')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
PEM
</div>
</Button>
</div>
<Button
variant={parameters.certType === 'JKS' ? 'filled' : 'outline'}
color={parameters.certType === 'JKS' ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('certType', 'JKS')}
disabled={disabled}
style={{ width: '100%', height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
JKS
</div>
</Button>
</div>
</Stack>
</Stack>
);
};
export default CertificateTypeSettings;

View File

@ -0,0 +1,113 @@
import { Stack, Text, Button, TextInput, NumberInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
interface SignatureAppearanceSettingsProps {
parameters: ManageSignaturesParameters;
onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void;
disabled?: boolean;
}
const SignatureAppearanceSettings = ({ parameters, onParameterChange, disabled = false }: SignatureAppearanceSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
{/* Signature Visibility */}
<Stack gap="sm">
<Text size="sm" fw={500}>
{t('manageSignatures.appearance.title', 'Signature Appearance')}
</Text>
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={!parameters.showSignature ? 'filled' : 'outline'}
color={!parameters.showSignature ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('showSignature', false)}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
{t('manageSignatures.appearance.invisible', 'Invisible')}
</div>
</Button>
<Button
variant={parameters.showSignature ? 'filled' : 'outline'}
color={parameters.showSignature ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('showSignature', true)}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
{t('manageSignatures.appearance.visible', 'Visible')}
</div>
</Button>
</div>
</Stack>
{/* Visible Signature Options */}
{parameters.showSignature && (
<Stack gap="sm">
<Text size="sm" fw={500}>
{t('manageSignatures.appearance.options.title', 'Signature Details')}
</Text>
<TextInput
label={t('manageSignatures.signing.reason', 'Reason for Signing')}
value={parameters.reason}
onChange={(event) => onParameterChange('reason', event.currentTarget.value)}
disabled={disabled}
/>
<TextInput
label={t('manageSignatures.signing.location', 'Location')}
value={parameters.location}
onChange={(event) => onParameterChange('location', event.currentTarget.value)}
disabled={disabled}
/>
<TextInput
label={t('manageSignatures.signing.name', 'Signer Name')}
value={parameters.name}
onChange={(event) => onParameterChange('name', event.currentTarget.value)}
disabled={disabled}
/>
<NumberInput
label={t('manageSignatures.signing.pageNumber', 'Page Number')}
value={parameters.pageNumber}
onChange={(value) => onParameterChange('pageNumber', value || 1)}
min={1}
disabled={disabled}
/>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('manageSignatures.signing.logoTitle', 'Logo')}
</Text>
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={!parameters.showLogo ? 'filled' : 'outline'}
color={!parameters.showLogo ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('showLogo', false)}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
{t('manageSignatures.signing.noLogo', 'No Logo')}
</div>
</Button>
<Button
variant={parameters.showLogo ? 'filled' : 'outline'}
color={parameters.showLogo ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('showLogo', true)}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
{t('manageSignatures.signing.showLogo', 'Show Logo')}
</div>
</Button>
</div>
</Stack>
</Stack>
)}
</Stack>
);
};
export default SignatureAppearanceSettings;

View File

@ -0,0 +1,31 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useCertificateTypeTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("manageSignatures.certType.tooltip.header.title", "About Certificate Types")
},
tips: [
{
title: t("manageSignatures.certType.tooltip.what.title", "What's a certificate?"),
description: t("manageSignatures.certType.tooltip.what.text", "It's a secure ID for your signature that proves you signed. Unless you're required to sign via certificate, we recommend using another secure method like Type, Draw, or Upload.")
},
{
title: t("manageSignatures.certType.tooltip.which.title", "Which option should I use?"),
description: t("manageSignatures.certType.tooltip.which.text", "Choose the format that matches your certificate file:"),
bullets: [
t("manageSignatures.certType.tooltip.which.bullet1", "PKCS#12 (.p12 / .pfx) one combined file (most common)"),
t("manageSignatures.certType.tooltip.which.bullet2", "PEM separate private-key and certificate .pem files"),
t("manageSignatures.certType.tooltip.which.bullet3", "JKS Java .jks keystore for dev / CI-CD workflows")
]
},
{
title: t("manageSignatures.certType.tooltip.convert.title", "Key not listed?"),
description: t("manageSignatures.certType.tooltip.convert.text", "Convert your file to a Java keystore (.jks) with keytool, then pick JKS.")
}
]
};
};

View File

@ -0,0 +1,44 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useManageSignaturesTooltips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("manageSignatures.tooltip.header.title", "About Managing Signatures")
},
tips: [
{
title: t("manageSignatures.tooltip.overview.title", "What can this tool do?"),
description: t("manageSignatures.tooltip.overview.text", "This tool lets you check if your PDFs are digitally signed and add new digital signatures. Digital signatures prove who created or approved a document and show if it has been changed since signing."),
bullets: [
t("manageSignatures.tooltip.overview.bullet1", "Check existing signatures and their validity"),
t("manageSignatures.tooltip.overview.bullet2", "View detailed information about signers and certificates"),
t("manageSignatures.tooltip.overview.bullet3", "Add new digital signatures to secure your documents"),
t("manageSignatures.tooltip.overview.bullet4", "Multiple files supported with easy navigation")
]
},
{
title: t("manageSignatures.tooltip.validation.title", "Checking Signatures"),
description: t("manageSignatures.tooltip.validation.text", "When you check signatures, the tool tells you if they're valid, who signed the document, when it was signed, and whether the document has been changed since signing."),
bullets: [
t("manageSignatures.tooltip.validation.bullet1", "Shows if signatures are valid or invalid"),
t("manageSignatures.tooltip.validation.bullet2", "Displays signer information and signing date"),
t("manageSignatures.tooltip.validation.bullet3", "Checks if the document was modified after signing"),
t("manageSignatures.tooltip.validation.bullet4", "Can use custom certificates for verification")
]
},
{
title: t("manageSignatures.tooltip.signing.title", "Adding Signatures"),
description: t("manageSignatures.tooltip.signing.text", "To sign a PDF, you need a digital certificate (like PEM, PKCS12, or JKS). You can choose to make the signature visible on the document or keep it invisible for security only."),
bullets: [
t("manageSignatures.tooltip.signing.bullet1", "Supports PEM, PKCS12, and JKS certificate formats"),
t("manageSignatures.tooltip.signing.bullet2", "Option to show or hide signature on the PDF"),
t("manageSignatures.tooltip.signing.bullet3", "Add reason, location, and signer name"),
t("manageSignatures.tooltip.signing.bullet4", "Choose which page to place visible signatures")
]
}
]
};
};

View File

@ -0,0 +1,33 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useSignatureAppearanceTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("manageSignatures.appearance.tooltip.header.title", "About Signature Appearance")
},
tips: [
{
title: t("manageSignatures.appearance.tooltip.invisible.title", "Invisible Signatures"),
description: t("manageSignatures.appearance.tooltip.invisible.text", "The signature is added to the PDF for security but won't be visible when viewing the document. Perfect for legal requirements without changing the document's appearance."),
bullets: [
t("manageSignatures.appearance.tooltip.invisible.bullet1", "Provides security without visual changes"),
t("manageSignatures.appearance.tooltip.invisible.bullet2", "Meets legal requirements for digital signing"),
t("manageSignatures.appearance.tooltip.invisible.bullet3", "Doesn't affect document layout or design")
]
},
{
title: t("manageSignatures.appearance.tooltip.visible.title", "Visible Signatures"),
description: t("manageSignatures.appearance.tooltip.visible.text", "Shows a signature block on the PDF with your name, date, and optional details. Useful when you want readers to clearly see the document is signed."),
bullets: [
t("manageSignatures.appearance.tooltip.visible.bullet1", "Shows signer name and date on the document"),
t("manageSignatures.appearance.tooltip.visible.bullet2", "Can include reason and location for signing"),
t("manageSignatures.appearance.tooltip.visible.bullet3", "Choose which page to place the signature"),
t("manageSignatures.appearance.tooltip.visible.bullet4", "Optional logo can be included")
]
}
]
};
};

View File

@ -15,6 +15,7 @@ import Repair from "../tools/Repair";
import SingleLargePage from "../tools/SingleLargePage"; import SingleLargePage from "../tools/SingleLargePage";
import UnlockPdfForms from "../tools/UnlockPdfForms"; import UnlockPdfForms from "../tools/UnlockPdfForms";
import RemoveCertificateSign from "../tools/RemoveCertificateSign"; import RemoveCertificateSign from "../tools/RemoveCertificateSign";
import ManageSignatures from "../tools/ManageSignatures";
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation"; import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation"; import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation"; import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
@ -28,6 +29,7 @@ import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation"; import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation"; import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation"; import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
import { manageSignaturesOperationConfig } from "../hooks/tools/manageSignatures/useManageSignaturesOperation";
import CompressSettings from "../components/tools/compress/CompressSettings"; import CompressSettings from "../components/tools/compress/CompressSettings";
import SplitSettings from "../components/tools/split/SplitSettings"; import SplitSettings from "../components/tools/split/SplitSettings";
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings"; import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
@ -39,6 +41,7 @@ import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/Add
import OCRSettings from "../components/tools/ocr/OCRSettings"; import OCRSettings from "../components/tools/ocr/OCRSettings";
import ConvertSettings from "../components/tools/convert/ConvertSettings"; import ConvertSettings from "../components/tools/convert/ConvertSettings";
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings"; import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings";
import { ToolId } from "../types/toolId"; import { ToolId } from "../types/toolId";
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
@ -134,11 +137,15 @@ export function useFlatToolRegistry(): ToolRegistry {
certSign: { certSign: {
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />, icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.certSign.title", "Sign with Certificate"), name: t("home.certSign.title", "Certificate Sign"),
component: null, component: ManageSignatures,
description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"), description: t("home.certSign.desc", "Sign PDF documents using digital certificates"),
categoryId: ToolCategoryId.STANDARD_TOOLS, categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.SIGNING, subcategoryId: SubcategoryId.SIGNING,
maxFiles: -1,
endpoints: ["cert-sign"],
operationConfig: manageSignaturesOperationConfig,
settingsComponent: CertificateTypeSettings,
}, },
sign: { sign: {
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />, icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
@ -240,14 +247,6 @@ export function useFlatToolRegistry(): ToolRegistry {
}, },
// Verification // Verification
"get-all-info-on-pdf": {
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
component: null,
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.VERIFICATION,
},
"validate-pdf-signature": { "validate-pdf-signature": {
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />, icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.validateSignature.title", "Validate PDF Signature"), name: t("home.validateSignature.title", "Validate PDF Signature"),
@ -256,6 +255,14 @@ export function useFlatToolRegistry(): ToolRegistry {
categoryId: ToolCategoryId.STANDARD_TOOLS, categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.VERIFICATION, subcategoryId: SubcategoryId.VERIFICATION,
}, },
"get-all-info-on-pdf": {
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
component: null,
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.VERIFICATION,
},
// Document Review // Document Review

View File

@ -0,0 +1,67 @@
import { useTranslation } from 'react-i18next';
import { ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { ManageSignaturesParameters, defaultParameters } from './useManageSignaturesParameters';
// Build form data for signing
export const buildManageSignaturesFormData = (parameters: ManageSignaturesParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
formData.append('certType', parameters.certType);
formData.append('password', parameters.password);
// Add certificate files based on type
switch (parameters.certType) {
case 'PEM':
if (parameters.privateKeyFile) {
formData.append('privateKeyFile', parameters.privateKeyFile);
}
if (parameters.certFile) {
formData.append('certFile', parameters.certFile);
}
break;
case 'PKCS12':
if (parameters.p12File) {
formData.append('p12File', parameters.p12File);
}
break;
case 'JKS':
if (parameters.jksFile) {
formData.append('jksFile', parameters.jksFile);
}
break;
}
// Add signature appearance options if enabled
if (parameters.showSignature) {
formData.append('showSignature', 'true');
formData.append('reason', parameters.reason);
formData.append('location', parameters.location);
formData.append('name', parameters.name);
formData.append('pageNumber', parameters.pageNumber.toString());
formData.append('showLogo', parameters.showLogo.toString());
}
return formData;
};
// Static configuration object
export const manageSignaturesOperationConfig = {
toolType: ToolType.singleFile,
buildFormData: buildManageSignaturesFormData,
operationType: 'manageSignatures',
endpoint: '/api/v1/security/cert-sign',
filePrefix: 'signed_', // Will be overridden in hook with translation
multiFileEndpoint: false,
defaultParameters,
} as const;
export const useManageSignaturesOperation = () => {
const { t } = useTranslation();
return useToolOperation<ManageSignaturesParameters>({
...manageSignaturesOperationConfig,
filePrefix: t('manageSignatures.filenamePrefix', 'signed') + '_',
getErrorMessage: createStandardErrorHandler(t('manageSignatures.error.failed', 'An error occurred while processing signatures.'))
});
};

View File

@ -0,0 +1,58 @@
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ManageSignaturesParameters extends BaseParameters {
// Certificate signing options
certType: '' | 'PEM' | 'PKCS12' | 'JKS';
privateKeyFile?: File;
certFile?: File;
p12File?: File;
jksFile?: File;
password: string;
// Signature appearance options
showSignature: boolean;
reason: string;
location: string;
name: string;
pageNumber: number;
showLogo: boolean;
}
export const defaultParameters: ManageSignaturesParameters = {
certType: '',
password: '',
showSignature: false,
reason: '',
location: '',
name: '',
pageNumber: 1,
showLogo: true,
};
export type ManageSignaturesParametersHook = BaseParametersHook<ManageSignaturesParameters>;
export const useManageSignaturesParameters = (): ManageSignaturesParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'manage-signatures',
validateFn: (params) => {
// Requires certificate type
if (!params.certType) {
return false;
}
// Check for required files based on cert type
switch (params.certType) {
case 'PEM':
return !!(params.privateKeyFile && params.certFile);
case 'PKCS12':
return !!params.p12File;
case 'JKS':
return !!params.jksFile;
default:
return false;
}
},
});
};

View File

@ -0,0 +1,140 @@
/**
* Service for detecting signatures in PDF files using PDF.js
* This provides a quick client-side check to determine if a PDF contains signatures
* without needing to make API calls
*/
// PDF.js types (simplified)
declare global {
interface Window {
pdfjsLib?: any;
}
}
export interface SignatureDetectionResult {
hasSignatures: boolean;
signatureCount?: number;
error?: string;
}
export interface FileSignatureStatus {
file: File;
result: SignatureDetectionResult;
}
/**
* Detect signatures in a single PDF file using PDF.js
*/
const detectSignaturesInFile = async (file: File): Promise<SignatureDetectionResult> => {
try {
// Ensure PDF.js is available
if (!window.pdfjsLib) {
return {
hasSignatures: false,
error: 'PDF.js not available'
};
}
// Convert file to ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
// Load the PDF document
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
let totalSignatures = 0;
// Check each page for signature annotations
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum);
const annotations = await page.getAnnotations();
// Count signature annotations (Type: /Sig)
const signatureAnnotations = annotations.filter((annotation: any) =>
annotation.subtype === 'Widget' &&
annotation.fieldType === 'Sig'
);
totalSignatures += signatureAnnotations.length;
}
// Also check for document-level signatures in AcroForm
const metadata = await pdf.getMetadata();
if (metadata?.info?.Signature || metadata?.metadata?.has('dc:signature')) {
totalSignatures = Math.max(totalSignatures, 1);
}
// Clean up PDF.js document
pdf.destroy();
return {
hasSignatures: totalSignatures > 0,
signatureCount: totalSignatures
};
} catch (error) {
console.warn('PDF signature detection failed:', error);
return {
hasSignatures: false,
signatureCount: 0,
error: error instanceof Error ? error.message : 'Detection failed'
};
}
};
/**
* Detect if PDF files contain signatures using PDF.js client-side processing
*/
export const detectSignaturesInFiles = async (files: File[]): Promise<FileSignatureStatus[]> => {
const results: FileSignatureStatus[] = [];
for (const file of files) {
const result = await detectSignaturesInFile(file);
results.push({ file, result });
}
return results;
};
/**
* Hook for managing signature detection state
*/
export const useSignatureDetection = () => {
const [detectionResults, setDetectionResults] = React.useState<FileSignatureStatus[]>([]);
const [isDetecting, setIsDetecting] = React.useState(false);
const detectSignatures = async (files: File[]) => {
if (files.length === 0) {
setDetectionResults([]);
return;
}
setIsDetecting(true);
try {
const results = await detectSignaturesInFiles(files);
setDetectionResults(results);
} finally {
setIsDetecting(false);
}
};
const getFileSignatureStatus = (file: File): SignatureDetectionResult | null => {
const result = detectionResults.find(r => r.file === file);
return result ? result.result : null;
};
const hasAnySignatures = detectionResults.some(r => r.result.hasSignatures);
const totalSignatures = detectionResults.reduce((sum, r) => sum + (r.result.signatureCount || 0), 0);
return {
detectionResults,
isDetecting,
detectSignatures,
getFileSignatureStatus,
hasAnySignatures,
totalSignatures,
reset: () => setDetectionResults([])
};
};
// Import React for the hook
import React from 'react';

View File

@ -0,0 +1,108 @@
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings";
import CertificateFilesSettings from "../components/tools/manageSignatures/CertificateFilesSettings";
import SignatureAppearanceSettings from "../components/tools/manageSignatures/SignatureAppearanceSettings";
import { useManageSignaturesParameters } from "../hooks/tools/manageSignatures/useManageSignaturesParameters";
import { useManageSignaturesOperation } from "../hooks/tools/manageSignatures/useManageSignaturesOperation";
import { useCertificateTypeTips } from "../components/tooltips/useCertificateTypeTips";
import { useSignatureAppearanceTips } from "../components/tooltips/useSignatureAppearanceTips";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const ManageSignatures = (props: BaseToolProps) => {
const { t } = useTranslation();
const base = useBaseTool(
'manageSignatures',
useManageSignaturesParameters,
useManageSignaturesOperation,
props
);
const certTypeTips = useCertificateTypeTips();
const appearanceTips = useSignatureAppearanceTips();
// Check if certificate files are configured for appearance step
const areCertFilesConfigured = () => {
const params = base.params.parameters;
switch (params.certType) {
case 'PEM':
return !!(params.privateKeyFile && params.certFile);
case 'PKCS12':
return !!params.p12File;
case 'JKS':
return !!params.jksFile;
default:
return false;
}
};
return createToolFlow({
forceStepNumbers: true,
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
placeholder: t("manageSignatures.files.placeholder", "Select PDF files to sign with certificates"),
},
steps: [
{
title: t("manageSignatures.certType.stepTitle", "Certificate Type"),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: certTypeTips,
content: (
<CertificateTypeSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
{
title: t("manageSignatures.certFiles.stepTitle", "Certificate Files"),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
content: (
<CertificateFilesSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
{
title: t("manageSignatures.appearance.stepTitle", "Signature Appearance"),
isCollapsed: base.settingsCollapsed || !areCertFilesConfigured(),
onCollapsedClick: (base.settingsCollapsed || !areCertFilesConfigured()) ? base.handleSettingsReset : undefined,
tooltip: appearanceTips,
content: (
<SignatureAppearanceSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("manageSignatures.sign.submit", "Sign PDF"),
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t("manageSignatures.sign.results", "Signed PDF"),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
// Static method to get the operation hook for automation
ManageSignatures.tool = () => useManageSignaturesOperation;
export default ManageSignatures as ToolComponent;

View File

@ -12,8 +12,8 @@ const TOOL_IDS = [
'flatten', 'remove-certificate-sign', 'flatten', 'remove-certificate-sign',
'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images', 'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images',
'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs', 'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs',
'manage-certificates', 'get-all-info-on-pdf', 'validate-pdf-signature', 'read', 'automate', 'replace-and-invert-color', 'manage-certificates', 'get-all-info-on-pdf', 'read', 'automate', 'replace-and-invert-color',
'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped' 'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped', 'validate-pdf-signature'
] as const; ] as const;
// Tool identity - what PDF operation we're performing (type-safe) // Tool identity - what PDF operation we're performing (type-safe)