From 9ddcd7ff6ae5bedfb380fd8b35ba4a0a68dede79 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 21 Sep 2025 15:10:20 +0100 Subject: [PATCH] remove sig stuff from this PR --- .../service/ServerCertificateService.java | 66 ----- .../SPDF/config/EndpointConfiguration.java | 2 - .../config/swagger/CsvConversionResponse.java | 38 ++- .../SPDF/config/swagger/ErrorResponse.java | 2 +- .../SPDF/config/swagger/JsonDataResponse.java | 16 +- .../config/swagger/MultiFileResponse.java | 16 +- .../config/swagger/StandardPdfResponse.java | 16 +- .../ServerCertificateInitializer.java | 27 -- .../api/BookletImpositionController.java | 206 -------------- .../api/security/CertSignController.java | 33 +-- .../api/general/BookletImpositionRequest.java | 39 --- .../api/security/SignPDFWithCertRequest.java | 2 +- .../src/main/resources/settings.yml.template | 5 - .../api/ServerCertificateController.java | 141 ---------- .../service/ServerCertificateService.java | 216 --------------- .../public/locales/en-GB/translation.json | 258 +----------------- .../public/locales/en-US/translation.json | 16 -- .../BookletImpositionSettings.tsx | 149 ---------- .../CertificateFilesSettings.tsx | 95 ------- .../CertificateFormatSettings.tsx | 72 ----- .../CertificateTypeSettings.tsx | 54 ---- .../SignatureAppearanceSettings.tsx | 110 -------- .../tooltips/useCertificateTypeTips.ts | 32 --- .../tooltips/useManageSignaturesTooltips.ts | 45 --- .../components/tooltips/useSignModeTips.ts | 36 --- .../tooltips/useSignatureAppearanceTips.ts | 33 --- .../src/data/useTranslatedToolRegistry.tsx | 42 +-- .../useBookletImpositionOperation.ts | 34 --- .../useBookletImpositionParameters.ts | 28 -- .../useManageSignaturesOperation.ts | 73 ----- .../useManageSignaturesParameters.ts | 67 ----- .../src/services/signatureDetectionService.ts | 140 ---------- frontend/src/tools/BookletImposition.tsx | 56 ---- frontend/src/tools/ManageSignatures.tsx | 132 --------- frontend/src/types/toolId.ts | 6 +- 35 files changed, 106 insertions(+), 2197 deletions(-) delete mode 100644 app/common/src/main/java/stirling/software/common/service/ServerCertificateService.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/configuration/ServerCertificateInitializer.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java delete mode 100644 app/core/src/main/java/stirling/software/SPDF/model/api/general/BookletImpositionRequest.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/ServerCertificateController.java delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java delete mode 100644 frontend/src/components/tools/bookletImposition/BookletImpositionSettings.tsx delete mode 100644 frontend/src/components/tools/manageSignatures/CertificateFilesSettings.tsx delete mode 100644 frontend/src/components/tools/manageSignatures/CertificateFormatSettings.tsx delete mode 100644 frontend/src/components/tools/manageSignatures/CertificateTypeSettings.tsx delete mode 100644 frontend/src/components/tools/manageSignatures/SignatureAppearanceSettings.tsx delete mode 100644 frontend/src/components/tooltips/useCertificateTypeTips.ts delete mode 100644 frontend/src/components/tooltips/useManageSignaturesTooltips.ts delete mode 100644 frontend/src/components/tooltips/useSignModeTips.ts delete mode 100644 frontend/src/components/tooltips/useSignatureAppearanceTips.ts delete mode 100644 frontend/src/hooks/tools/bookletImposition/useBookletImpositionOperation.ts delete mode 100644 frontend/src/hooks/tools/bookletImposition/useBookletImpositionParameters.ts delete mode 100644 frontend/src/hooks/tools/manageSignatures/useManageSignaturesOperation.ts delete mode 100644 frontend/src/hooks/tools/manageSignatures/useManageSignaturesParameters.ts delete mode 100644 frontend/src/services/signatureDetectionService.ts delete mode 100644 frontend/src/tools/BookletImposition.tsx delete mode 100644 frontend/src/tools/ManageSignatures.tsx diff --git a/app/common/src/main/java/stirling/software/common/service/ServerCertificateService.java b/app/common/src/main/java/stirling/software/common/service/ServerCertificateService.java deleted file mode 100644 index aa556a597..000000000 --- a/app/common/src/main/java/stirling/software/common/service/ServerCertificateService.java +++ /dev/null @@ -1,66 +0,0 @@ -package stirling.software.common.service; - -import java.io.InputStream; -import java.security.KeyStore; -import java.security.cert.X509Certificate; -import java.util.Date; - -public interface ServerCertificateService { - - boolean isEnabled(); - - boolean hasServerCertificate(); - - void initializeServerCertificate(); - - KeyStore getServerKeyStore() throws Exception; - - String getServerCertificatePassword(); - - X509Certificate getServerCertificate() throws Exception; - - byte[] getServerCertificatePublicKey() throws Exception; - - void uploadServerCertificate(InputStream p12Stream, String password) throws Exception; - - void deleteServerCertificate() throws Exception; - - ServerCertificateInfo getServerCertificateInfo() throws Exception; - - class ServerCertificateInfo { - private final boolean exists; - private final String subject; - private final String issuer; - private final Date validFrom; - private final Date validTo; - - public ServerCertificateInfo( - boolean exists, String subject, String issuer, Date validFrom, Date validTo) { - this.exists = exists; - this.subject = subject; - this.issuer = issuer; - this.validFrom = validFrom; - this.validTo = validTo; - } - - public boolean isExists() { - return exists; - } - - public String getSubject() { - return subject; - } - - public String getIssuer() { - return issuer; - } - - public Date getValidFrom() { - return validFrom; - } - - public Date getValidTo() { - return validTo; - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/app/core/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index a99bc4184..dab00a89d 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -237,7 +237,6 @@ public class EndpointConfiguration { addEndpointToGroup("PageOps", "pdf-organizer"); addEndpointToGroup("PageOps", "rotate-pdf"); addEndpointToGroup("PageOps", "multi-page-layout"); - addEndpointToGroup("PageOps", "booklet-imposition"); addEndpointToGroup("PageOps", "scale-pages"); addEndpointToGroup("PageOps", "crop"); addEndpointToGroup("PageOps", "extract-page"); @@ -367,7 +366,6 @@ public class EndpointConfiguration { addEndpointToGroup("Java", "cert-sign"); addEndpointToGroup("Java", "remove-cert-sign"); addEndpointToGroup("Java", "multi-page-layout"); - addEndpointToGroup("Java", "booklet-imposition"); addEndpointToGroup("Java", "scale-pages"); addEndpointToGroup("Java", "add-page-numbers"); addEndpointToGroup("Java", "auto-rename"); diff --git a/app/core/src/main/java/stirling/software/SPDF/config/swagger/CsvConversionResponse.java b/app/core/src/main/java/stirling/software/SPDF/config/swagger/CsvConversionResponse.java index 77483cb58..042d09d8b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/swagger/CsvConversionResponse.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/swagger/CsvConversionResponse.java @@ -21,14 +21,38 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @ApiResponse( responseCode = "200", description = "PDF tables extracted successfully to CSV format", + content = { + @Content( + mediaType = "text/csv", + schema = + @Schema( + type = "string", + format = "binary", + description = + "CSV file containing extracted table data")), + @Content( + mediaType = "application/zip", + schema = + @Schema( + type = "string", + format = "binary", + description = + "ZIP archive containing multiple CSV files when multiple tables are extracted")) + }), + @ApiResponse( + responseCode = "400", + description = "Invalid PDF file or no tables found for extraction", content = @Content( - mediaType = "text/csv", - schema = - @Schema( - type = "string", - format = "binary", - description = - "CSV file containing extracted table data"))) + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "204", description = "No tables found in the PDF"), + @ApiResponse( + responseCode = "500", + description = "Internal server error during table extraction", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))) }) public @interface CsvConversionResponse {} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/swagger/ErrorResponse.java b/app/core/src/main/java/stirling/software/SPDF/config/swagger/ErrorResponse.java index e24649c8f..6d125535b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/swagger/ErrorResponse.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/swagger/ErrorResponse.java @@ -25,6 +25,6 @@ public class ErrorResponse { @Schema( description = "Request path where the error occurred", - example = "/api/v1/general/rotate-pdf") + example = "/api/v1/{endpoint-path}") private String path; } diff --git a/app/core/src/main/java/stirling/software/SPDF/config/swagger/JsonDataResponse.java b/app/core/src/main/java/stirling/software/SPDF/config/swagger/JsonDataResponse.java index 18e361a59..0aa6d1fcf 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/swagger/JsonDataResponse.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/swagger/JsonDataResponse.java @@ -28,6 +28,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @Schema( type = "object", description = - "JSON object containing the requested data or analysis results"))) + "JSON object containing the requested data or analysis results"))), + @ApiResponse( + responseCode = "400", + description = "Invalid PDF file or request parameters", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error during processing", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))) }) public @interface JsonDataResponse {} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/swagger/MultiFileResponse.java b/app/core/src/main/java/stirling/software/SPDF/config/swagger/MultiFileResponse.java index 920a949be..c8f232c63 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/swagger/MultiFileResponse.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/swagger/MultiFileResponse.java @@ -52,6 +52,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; type = "string", format = "binary", description = "Single image file (JPEG)")) - }) + }), + @ApiResponse( + responseCode = "400", + description = "Invalid PDF file or request parameters", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error during processing", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))) }) public @interface MultiFileResponse {} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/swagger/StandardPdfResponse.java b/app/core/src/main/java/stirling/software/SPDF/config/swagger/StandardPdfResponse.java index ee24d2e3d..a4d41e8d3 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/swagger/StandardPdfResponse.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/swagger/StandardPdfResponse.java @@ -28,6 +28,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @Schema( type = "string", format = "binary", - description = "The processed PDF file"))) + description = "The processed PDF file"))), + @ApiResponse( + responseCode = "400", + description = "Invalid PDF file or request parameters", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error during processing", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))) }) public @interface StandardPdfResponse {} diff --git a/app/core/src/main/java/stirling/software/SPDF/configuration/ServerCertificateInitializer.java b/app/core/src/main/java/stirling/software/SPDF/configuration/ServerCertificateInitializer.java deleted file mode 100644 index f5d558f62..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/configuration/ServerCertificateInitializer.java +++ /dev/null @@ -1,27 +0,0 @@ -package stirling.software.SPDF.configuration; - -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.common.service.ServerCertificateService; - -@Component -@RequiredArgsConstructor -@Slf4j -public class ServerCertificateInitializer { - - private final ServerCertificateService serverCertificateService; - - @EventListener(ApplicationReadyEvent.class) - public void initializeServerCertificate() { - try { - serverCertificateService.initializeServerCertificate(); - } catch (Exception e) { - log.error("Failed to initialize server certificate", e); - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java deleted file mode 100644 index 9ba5a6897..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/BookletImpositionController.java +++ /dev/null @@ -1,206 +0,0 @@ -package stirling.software.SPDF.controller.api; - -import java.awt.*; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.pdfbox.multipdf.LayerUtility; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; -import org.apache.pdfbox.util.Matrix; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.multipart.MultipartFile; - -import io.github.pixee.security.Filenames; -import io.swagger.v3.oas.annotations.Operation; - -import lombok.RequiredArgsConstructor; - -import stirling.software.SPDF.config.swagger.StandardPdfResponse; -import stirling.software.SPDF.model.api.general.BookletImpositionRequest; -import stirling.software.common.annotations.AutoJobPostMapping; -import stirling.software.common.annotations.api.GeneralApi; -import stirling.software.common.service.CustomPDFDocumentFactory; -import stirling.software.common.util.WebResponseUtils; - -@GeneralApi -@RequiredArgsConstructor -public class BookletImpositionController { - - private final CustomPDFDocumentFactory pdfDocumentFactory; - - @AutoJobPostMapping(value = "/booklet-imposition", consumes = "multipart/form-data") - @StandardPdfResponse - @Operation( - summary = "Create a booklet with proper page imposition", - description = - "This operation combines page reordering for booklet printing with multi-page layout. " - + "It rearranges pages in the correct order for booklet printing and places multiple pages " - + "on each sheet for proper folding and binding. Input:PDF Output:PDF Type:SISO") - public ResponseEntity createBookletImposition( - @ModelAttribute BookletImpositionRequest request) throws IOException { - - MultipartFile file = request.getFileInput(); - String bookletType = request.getBookletType(); - int pagesPerSheet = request.getPagesPerSheet(); - boolean addBorder = Boolean.TRUE.equals(request.getAddBorder()); - String pageOrientation = request.getPageOrientation(); - - // Validate pages per sheet for booklet - if (pagesPerSheet != 2 && pagesPerSheet != 4) { - throw new IllegalArgumentException( - "pagesPerSheet must be 2 or 4 for booklet imposition"); - } - - PDDocument sourceDocument = pdfDocumentFactory.load(file); - int totalPages = sourceDocument.getNumberOfPages(); - - // Step 1: Reorder pages for booklet (reusing logic from RearrangePagesPDFController) - List bookletOrder = getBookletPageOrder(bookletType, totalPages); - - // Step 2: Create new document with multi-page layout (reusing logic from - // MultiPageLayoutController) - PDDocument newDocument = - createBookletWithLayout( - sourceDocument, bookletOrder, pagesPerSheet, addBorder, pageOrientation); - - sourceDocument.close(); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - newDocument.save(baos); - newDocument.close(); - - byte[] result = baos.toByteArray(); - return WebResponseUtils.bytesToWebResponse( - result, - Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") - + "_booklet.pdf"); - } - - // Reused logic from RearrangePagesPDFController - private List getBookletPageOrder(String bookletType, int totalPages) { - if ("SIDE_STITCH_BOOKLET".equals(bookletType)) { - return sideStitchBookletSort(totalPages); - } else { - return bookletSort(totalPages); - } - } - - private List bookletSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < totalPages / 2; i++) { - newPageOrder.add(i); - newPageOrder.add(totalPages - i - 1); - } - return newPageOrder; - } - - private List sideStitchBookletSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < (totalPages + 3) / 4; i++) { - int begin = i * 4; - newPageOrder.add(Math.min(begin + 3, totalPages - 1)); - newPageOrder.add(Math.min(begin, totalPages - 1)); - newPageOrder.add(Math.min(begin + 1, totalPages - 1)); - newPageOrder.add(Math.min(begin + 2, totalPages - 1)); - } - return newPageOrder; - } - - // Reused and adapted logic from MultiPageLayoutController - private PDDocument createBookletWithLayout( - PDDocument sourceDocument, - List pageOrder, - int pagesPerSheet, - boolean addBorder, - String pageOrientation) - throws IOException { - - PDDocument newDocument = - pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument); - - int cols = pagesPerSheet == 2 ? 2 : 2; // 2x1 for 2 pages, 2x2 for 4 pages - int rows = pagesPerSheet == 2 ? 1 : 2; - - int currentPageIndex = 0; - int totalOrderedPages = pageOrder.size(); - - while (currentPageIndex < totalOrderedPages) { - // Use landscape orientation for booklets (A4 landscape -> A5 portrait when folded) - PDRectangle pageSize = - "LANDSCAPE".equals(pageOrientation) - ? new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth()) - : PDRectangle.A4; - PDPage newPage = new PDPage(pageSize); - newDocument.addPage(newPage); - - float cellWidth = newPage.getMediaBox().getWidth() / cols; - float cellHeight = newPage.getMediaBox().getHeight() / rows; - - PDPageContentStream contentStream = - new PDPageContentStream( - newDocument, - newPage, - PDPageContentStream.AppendMode.APPEND, - true, - true); - LayerUtility layerUtility = new LayerUtility(newDocument); - - if (addBorder) { - contentStream.setLineWidth(1.5f); - contentStream.setStrokingColor(Color.BLACK); - } - - // Place pages on the current sheet - for (int sheetPosition = 0; - sheetPosition < pagesPerSheet && currentPageIndex < totalOrderedPages; - sheetPosition++) { - int sourcePageIndex = pageOrder.get(currentPageIndex); - PDPage sourcePage = sourceDocument.getPage(sourcePageIndex); - PDRectangle rect = sourcePage.getMediaBox(); - - float scaleWidth = cellWidth / rect.getWidth(); - float scaleHeight = cellHeight / rect.getHeight(); - float scale = Math.min(scaleWidth, scaleHeight); - - int rowIndex = sheetPosition / cols; - int colIndex = sheetPosition % cols; - - float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2; - float y = - newPage.getMediaBox().getHeight() - - ((rowIndex + 1) * cellHeight - - (cellHeight - rect.getHeight() * scale) / 2); - - contentStream.saveGraphicsState(); - contentStream.transform(Matrix.getTranslateInstance(x, y)); - contentStream.transform(Matrix.getScaleInstance(scale, scale)); - - PDFormXObject formXObject = - layerUtility.importPageAsForm(sourceDocument, sourcePageIndex); - contentStream.drawForm(formXObject); - - contentStream.restoreGraphicsState(); - - if (addBorder) { - float borderX = colIndex * cellWidth; - float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight; - contentStream.addRect(borderX, borderY, cellWidth, cellHeight); - contentStream.stroke(); - } - - currentPageIndex++; - } - - contentStream.close(); - } - - return newDocument; - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index d37db1c45..c68cc073b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -59,11 +59,14 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.github.pixee.security.Filenames; import io.micrometer.common.util.StringUtils; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -71,14 +74,14 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.swagger.StandardPdfResponse; import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.common.annotations.AutoJobPostMapping; -import stirling.software.common.annotations.api.SecurityApi; import stirling.software.common.service.CustomPDFDocumentFactory; -import stirling.software.common.service.ServerCertificateService; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.WebResponseUtils; -@SecurityApi +@RestController +@RequestMapping("/api/v1/security") @Slf4j +@Tag(name = "Security", description = "Security APIs") @RequiredArgsConstructor public class CertSignController { @@ -99,7 +102,6 @@ public class CertSignController { } private final CustomPDFDocumentFactory pdfDocumentFactory; - private final ServerCertificateService serverCertificateService; private static void sign( CustomPDFDocumentFactory pdfDocumentFactory, @@ -137,7 +139,12 @@ public class CertSignController { } } - @AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/cert-sign") + @AutoJobPostMapping( + consumes = { + MediaType.MULTIPART_FORM_DATA_VALUE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + }, + value = "/cert-sign") @StandardPdfResponse @Operation( summary = "Sign PDF with a Digital Certificate", @@ -170,7 +177,6 @@ public class CertSignController { } KeyStore ks = null; - String keystorePassword = password; switch (certType) { case "PEM": @@ -189,19 +195,6 @@ public class CertSignController { ks = KeyStore.getInstance("JKS"); ks.load(jksfile.getInputStream(), password.toCharArray()); break; - case "SERVER": - if (!serverCertificateService.isEnabled()) { - throw ExceptionUtils.createIllegalArgumentException( - "error.serverCertificateDisabled", - "Server certificate feature is disabled"); - } - if (!serverCertificateService.hasServerCertificate()) { - throw ExceptionUtils.createIllegalArgumentException( - "error.serverCertificateNotFound", "No server certificate configured"); - } - ks = serverCertificateService.getServerKeyStore(); - keystorePassword = serverCertificateService.getServerCertificatePassword(); - break; default: throw ExceptionUtils.createIllegalArgumentException( "error.invalidArgument", @@ -209,7 +202,7 @@ public class CertSignController { "certificate type: " + certType); } - CreateSignature createSignature = new CreateSignature(ks, keystorePassword.toCharArray()); + CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); sign( pdfDocumentFactory, diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/general/BookletImpositionRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/general/BookletImpositionRequest.java deleted file mode 100644 index e8b0b3ab5..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/general/BookletImpositionRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package stirling.software.SPDF.model.api.general; - -import io.swagger.v3.oas.annotations.media.Schema; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import stirling.software.common.model.api.PDFFile; - -@Data -@EqualsAndHashCode(callSuper = true) -public class BookletImpositionRequest extends PDFFile { - - @Schema( - description = "The booklet type to create.", - type = "string", - defaultValue = "BOOKLET", - requiredMode = Schema.RequiredMode.REQUIRED, - allowableValues = {"BOOKLET", "SIDE_STITCH_BOOKLET"}) - private String bookletType = "BOOKLET"; - - @Schema( - description = "The number of pages to fit onto a single sheet in the output PDF.", - type = "integer", - requiredMode = Schema.RequiredMode.REQUIRED, - allowableValues = {"2", "4"}) - private int pagesPerSheet = 2; - - @Schema(description = "Boolean for if you wish to add border around the pages") - private Boolean addBorder = false; - - @Schema( - description = "The page orientation for the output booklet sheets.", - type = "string", - defaultValue = "LANDSCAPE", - requiredMode = Schema.RequiredMode.REQUIRED, - allowableValues = {"LANDSCAPE", "PORTRAIT"}) - private String pageOrientation = "LANDSCAPE"; -} diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java index d75f751f1..acb4b55fd 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java @@ -15,7 +15,7 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema( description = "The type of the digital certificate", - allowableValues = {"PEM", "PKCS12", "JKS", "SERVER"}, + allowableValues = {"PEM", "PKCS12", "JKS"}, requiredMode = Schema.RequiredMode.REQUIRED) private String certType; diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 465f95fb6..bbbac5fcd 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -114,11 +114,6 @@ system: enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) maxDPI: 500 # Maximum allowed DPI for PDF to image conversion - serverCertificate: - enabled: true # Enable server-side certificate for "Sign with Stirling-PDF" option - organizationName: Stirling-PDF # Organization name for generated certificates - validity: 365 # Certificate validity in days - regenerateOnStartup: false # Generate new certificate on each startup html: urlSecurity: enabled: true # Enable URL security restrictions for HTML processing diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/ServerCertificateController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/ServerCertificateController.java deleted file mode 100644 index a7e2e551d..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/ServerCertificateController.java +++ /dev/null @@ -1,141 +0,0 @@ -package stirling.software.proprietary.security.controller.api; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import stirling.software.common.annotations.api.AdminServerCertificateApi; -import stirling.software.common.service.ServerCertificateService; - -@AdminServerCertificateApi -@Slf4j -@RequiredArgsConstructor -@PreAuthorize("hasRole('ADMIN')") -public class ServerCertificateController { - - private final ServerCertificateService serverCertificateService; - - @GetMapping("/info") - @Operation( - summary = "Get server certificate information", - description = "Returns information about the current server certificate") - public ResponseEntity - getServerCertificateInfo() { - try { - ServerCertificateService.ServerCertificateInfo info = - serverCertificateService.getServerCertificateInfo(); - return ResponseEntity.ok(info); - } catch (Exception e) { - log.error("Failed to get server certificate info", e); - return ResponseEntity.internalServerError().build(); - } - } - - @PostMapping(value = "/upload", consumes = "multipart/form-data") - @Operation( - summary = "Upload server certificate", - description = - "Upload a new PKCS12 certificate file to be used as the server certificate") - public ResponseEntity uploadServerCertificate( - @Parameter(description = "PKCS12 certificate file", required = true) - @RequestParam("file") - MultipartFile file, - @Parameter(description = "Certificate password", required = true) - @RequestParam("password") - String password) { - - if (file.isEmpty()) { - return ResponseEntity.badRequest().body("Certificate file cannot be empty"); - } - - if (!file.getOriginalFilename().toLowerCase().endsWith(".p12") - && !file.getOriginalFilename().toLowerCase().endsWith(".pfx")) { - return ResponseEntity.badRequest() - .body("Only PKCS12 (.p12 or .pfx) files are supported"); - } - - try { - serverCertificateService.uploadServerCertificate(file.getInputStream(), password); - return ResponseEntity.ok("Server certificate uploaded successfully"); - } catch (IllegalArgumentException e) { - log.warn("Invalid certificate upload: {}", e.getMessage()); - return ResponseEntity.badRequest().body(e.getMessage()); - } catch (Exception e) { - log.error("Failed to upload server certificate", e); - return ResponseEntity.internalServerError().body("Failed to upload server certificate"); - } - } - - @DeleteMapping - @Operation( - summary = "Delete server certificate", - description = "Delete the current server certificate") - public ResponseEntity deleteServerCertificate() { - try { - serverCertificateService.deleteServerCertificate(); - return ResponseEntity.ok("Server certificate deleted successfully"); - } catch (Exception e) { - log.error("Failed to delete server certificate", e); - return ResponseEntity.internalServerError().body("Failed to delete server certificate"); - } - } - - @PostMapping("/generate") - @Operation( - summary = "Generate new server certificate", - description = "Generate a new self-signed server certificate") - public ResponseEntity generateServerCertificate() { - try { - serverCertificateService.deleteServerCertificate(); // Remove existing if any - serverCertificateService.initializeServerCertificate(); // Generate new - return ResponseEntity.ok("New server certificate generated successfully"); - } catch (Exception e) { - log.error("Failed to generate server certificate", e); - return ResponseEntity.internalServerError() - .body("Failed to generate server certificate"); - } - } - - @GetMapping("/public-key") - @Operation( - summary = "Download server certificate public key", - description = - "Download the public key of the server certificate for validation purposes") - public ResponseEntity getServerCertificatePublicKey() { - try { - if (!serverCertificateService.hasServerCertificate()) { - return ResponseEntity.notFound().build(); - } - - byte[] publicKey = serverCertificateService.getServerCertificatePublicKey(); - - return ResponseEntity.ok() - .header( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"server-cert.crt\"") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(publicKey); - } catch (Exception e) { - log.error("Failed to get server certificate public key", e); - return ResponseEntity.internalServerError().build(); - } - } - - @GetMapping("/enabled") - @Operation( - summary = "Check if server certificate feature is enabled", - description = - "Returns whether the server certificate feature is enabled in configuration") - public ResponseEntity isServerCertificateEnabled() { - return ResponseEntity.ok(serverCertificateService.isEnabled()); - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java deleted file mode 100644 index 820c18a40..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java +++ /dev/null @@ -1,216 +0,0 @@ -package stirling.software.proprietary.service; - -import java.io.*; -import java.math.BigInteger; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Date; - -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import lombok.extern.slf4j.Slf4j; - -import stirling.software.common.configuration.InstallationPathConfig; - -@Service -@Slf4j -public class ServerCertificateService - implements stirling.software.common.service.ServerCertificateService { - - private static final String KEYSTORE_FILENAME = "server-certificate.p12"; - private static final String KEYSTORE_ALIAS = "stirling-pdf-server"; - private static final String DEFAULT_PASSWORD = "stirling-pdf-server-cert"; - - @Value("${system.serverCertificate.enabled:false}") - private boolean enabled; - - @Value("${system.serverCertificate.organizationName:Stirling-PDF}") - private String organizationName; - - @Value("${system.serverCertificate.validity:365}") - private int validityDays; - - @Value("${system.serverCertificate.regenerateOnStartup:false}") - private boolean regenerateOnStartup; - - static { - Security.addProvider(new BouncyCastleProvider()); - } - - private Path getKeystorePath() { - return Paths.get(InstallationPathConfig.getConfigPath(), KEYSTORE_FILENAME); - } - - public boolean isEnabled() { - return enabled; - } - - public boolean hasServerCertificate() { - return Files.exists(getKeystorePath()); - } - - public void initializeServerCertificate() { - if (!enabled) { - log.debug("Server certificate feature is disabled"); - return; - } - - Path keystorePath = getKeystorePath(); - - if (!Files.exists(keystorePath) || regenerateOnStartup) { - try { - generateServerCertificate(); - log.info("Generated new server certificate at: {}", keystorePath); - } catch (Exception e) { - log.error("Failed to generate server certificate", e); - } - } else { - log.info("Server certificate already exists at: {}", keystorePath); - } - } - - public KeyStore getServerKeyStore() throws Exception { - if (!enabled || !hasServerCertificate()) { - throw new IllegalStateException("Server certificate is not available"); - } - - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - try (FileInputStream fis = new FileInputStream(getKeystorePath().toFile())) { - keyStore.load(fis, DEFAULT_PASSWORD.toCharArray()); - } - return keyStore; - } - - public String getServerCertificatePassword() { - return DEFAULT_PASSWORD; - } - - public X509Certificate getServerCertificate() throws Exception { - KeyStore keyStore = getServerKeyStore(); - return (X509Certificate) keyStore.getCertificate(KEYSTORE_ALIAS); - } - - public byte[] getServerCertificatePublicKey() throws Exception { - X509Certificate cert = getServerCertificate(); - return cert.getEncoded(); - } - - public void uploadServerCertificate(InputStream p12Stream, String password) throws Exception { - // Validate the uploaded certificate - KeyStore uploadedKeyStore = KeyStore.getInstance("PKCS12"); - uploadedKeyStore.load(p12Stream, password.toCharArray()); - - // Find the first private key entry - String alias = null; - for (String a : java.util.Collections.list(uploadedKeyStore.aliases())) { - if (uploadedKeyStore.isKeyEntry(a)) { - alias = a; - break; - } - } - - if (alias == null) { - throw new IllegalArgumentException("No private key found in uploaded certificate"); - } - - // Create new keystore with our standard alias and password - KeyStore newKeyStore = KeyStore.getInstance("PKCS12"); - newKeyStore.load(null, null); - - PrivateKey privateKey = (PrivateKey) uploadedKeyStore.getKey(alias, password.toCharArray()); - Certificate[] chain = uploadedKeyStore.getCertificateChain(alias); - - newKeyStore.setKeyEntry(KEYSTORE_ALIAS, privateKey, DEFAULT_PASSWORD.toCharArray(), chain); - - // Save to server keystore location - Path keystorePath = getKeystorePath(); - Files.createDirectories(keystorePath.getParent()); - - try (FileOutputStream fos = new FileOutputStream(keystorePath.toFile())) { - newKeyStore.store(fos, DEFAULT_PASSWORD.toCharArray()); - } - - log.info("Server certificate updated from uploaded file"); - } - - public void deleteServerCertificate() throws Exception { - Path keystorePath = getKeystorePath(); - if (Files.exists(keystorePath)) { - Files.delete(keystorePath); - log.info("Server certificate deleted"); - } - } - - public ServerCertificateInfo getServerCertificateInfo() throws Exception { - if (!hasServerCertificate()) { - return new ServerCertificateInfo(false, null, null, null, null); - } - - X509Certificate cert = getServerCertificate(); - return new ServerCertificateInfo( - true, - cert.getSubjectX500Principal().getName(), - cert.getIssuerX500Principal().getName(), - cert.getNotBefore(), - cert.getNotAfter()); - } - - private void generateServerCertificate() throws Exception { - // Generate key pair - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); - keyPairGenerator.initialize(2048, new SecureRandom()); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - - // Certificate details - X500Name subject = - new X500Name( - "CN=" + organizationName + " Server, O=" + organizationName + ", C=US"); - BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); - Date notBefore = new Date(); - Date notAfter = new Date(notBefore.getTime() + ((long) validityDays * 24 * 60 * 60 * 1000)); - - // Build certificate - JcaX509v3CertificateBuilder certBuilder = - new JcaX509v3CertificateBuilder( - subject, serialNumber, notBefore, notAfter, subject, keyPair.getPublic()); - - // Sign certificate - ContentSigner signer = - new JcaContentSignerBuilder("SHA256WithRSA") - .setProvider("BC") - .build(keyPair.getPrivate()); - - X509CertificateHolder certHolder = certBuilder.build(signer); - X509Certificate cert = - new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder); - - // Create keystore - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load(null, null); - keyStore.setKeyEntry( - KEYSTORE_ALIAS, - keyPair.getPrivate(), - DEFAULT_PASSWORD.toCharArray(), - new Certificate[] {cert}); - - // Save keystore - Path keystorePath = getKeystorePath(); - Files.createDirectories(keystorePath.getParent()); - - try (FileOutputStream fos = new FileOutputStream(keystorePath.toFile())) { - keyStore.store(fos, DEFAULT_PASSWORD.toCharArray()); - } - } -} diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index f090fd08e..49afabb8c 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -426,10 +426,6 @@ "title": "Flatten", "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": { "title": "Repair", "desc": "Tries to repair a corrupt/broken PDF" @@ -450,10 +446,6 @@ "title": "Sign with Certificate", "desc": "Signs a PDF with a Certificate/Key (PEM/P12)" }, - "manageSignatures": { - "title": "Manage Signatures", - "desc": "Sign PDFs with certificates using manual or server-managed keys" - }, "removeCertSign": { "title": "Remove Certificate Sign", "desc": "Remove certificate signature from PDF" @@ -462,10 +454,6 @@ "title": "Multi-Page Layout", "desc": "Merge multiple pages of a PDF document into a single page" }, - "bookletImposition": { - "title": "Booklet Imposition", - "desc": "Create booklets with proper page ordering and multi-page layout for printing and binding" - }, "scalePages": { "title": "Adjust page size/scale", "desc": "Change the size/scale of a page and/or its contents." @@ -1560,98 +1548,7 @@ "location": "Location", "name": "Name", "showLogo": "Show Logo", - "submit": "Sign PDF", - "files": { - "placeholder": "Select PDF files to sign with certificates" - }, - "signMode": { - "stepTitle": "Sign Mode" - }, - "certTypeStep": { - "stepTitle": "Certificate Format" - }, - "certFiles": { - "stepTitle": "Certificate Files" - }, - "appearance": { - "stepTitle": "Signature Appearance", - "title": "Signature Appearance", - "invisible": "Invisible", - "visible": "Visible", - "options": { - "title": "Signature Details" - } - }, - "sign": { - "submit": "Sign PDF", - "results": "Signed PDF" - }, - "error": { - "failed": "An error occurred whilst signing the PDF." - }, - "choosePrivateKey": "Choose Private Key File", - "chooseCertificate": "Choose Certificate File", - "chooseP12File": "Choose PKCS12 File", - "choosePfxFile": "Choose PFX File", - "chooseJksFile": "Choose JKS File", - "passwordOptional": "Leave empty if no password", - "serverCertMessage": "Using server certificate - no files or password required", - "pageNumber": "Page Number", - "logoTitle": "Logo", - "noLogo": "No Logo" - }, - "manageSignatures": { - "tags": "sign,certificate,PEM,PKCS12,JKS,server,manual,auto", - "title": "Manage Signatures", - "desc": "Sign PDFs with certificates using manual or server-managed keys", - "signMode": { - "tooltip": { - "header": { - "title": "About PDF Signatures" - }, - "overview": { - "title": "How signatures work", - "text": "Both modes seal the document (any edits are flagged as tampering) and record who/when/how for auditing. Viewer trust depends on the certificate chain." - }, - "manual": { - "title": "Manual - Bring your certificate", - "text": "Use your own certificate files for brand-aligned identity. Can display Trusted when your CA/chain is recognised.", - "use": "Use for: customer-facing, legal, compliance." - }, - "auto": { - "title": "Auto - Zero-setup, instant system seal", - "text": "Signs with a server self-signed certificate. Same tamper-evident seal and audit trail; typically shows Unverified in viewers.", - "use": "Use when: you need speed and consistent internal identity across reviews and records." - }, - "rule": { - "title": "Rule of thumb", - "text": "Need recipient Trusted status? Manual. Need a fast, tamper-evident seal and audit trail with no setup? Auto." - } - } - }, - "certType": { - "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": "PKCS12 (.p12) – one combined file (most common)", - "bullet2": "PFX (.pfx) – Microsoft's version of PKCS12", - "bullet3": "PEM – separate private-key and certificate .pem files", - "bullet4": "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." - } - } - } + "submit": "Sign PDF" }, "removeCertSign": { "tags": "authenticate,PEM,P12,official,decrypt", @@ -1679,18 +1576,6 @@ "addBorder": "Add Borders", "submit": "Submit" }, - "bookletImposition": { - "tags": "booklet,imposition,printing,binding,folding,signature", - "title": "Booklet Imposition", - "header": "Booklet Imposition", - "submit": "Create Booklet", - "files": { - "placeholder": "Select PDF files to create booklet impositions from" - }, - "error": { - "failed": "An error occurred while creating the booklet imposition." - } - }, "scalePages": { "title": "Adjust page-scale", "header": "Adjust page-scale", @@ -2807,146 +2692,5 @@ "processImages": "Process 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" - } - } } } diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 7ee731585..4b93181b8 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -495,10 +495,6 @@ "title": "Multi-Page Layout", "desc": "Merge multiple pages of a PDF document into a single page" }, - "bookletImposition": { - "title": "Booklet Imposition", - "desc": "Create booklets with proper page ordering and multi-page layout for printing and binding" - }, "scalePages": { "title": "Adjust page size/scale", "desc": "Change the size/scale of a page and/or its contents." @@ -1101,18 +1097,6 @@ "addBorder": "Add Borders", "submit": "Submit" }, - "bookletImposition": { - "tags": "booklet,imposition,printing,binding,folding,signature", - "title": "Booklet Imposition", - "header": "Booklet Imposition", - "submit": "Create Booklet", - "files": { - "placeholder": "Select PDF files to create booklet impositions from" - }, - "error": { - "failed": "An error occurred while creating the booklet imposition." - } - }, "scalePages": { "tags": "resize,modify,dimension,adapt", "title": "Adjust page-scale", diff --git a/frontend/src/components/tools/bookletImposition/BookletImpositionSettings.tsx b/frontend/src/components/tools/bookletImposition/BookletImpositionSettings.tsx deleted file mode 100644 index 99c75b583..000000000 --- a/frontend/src/components/tools/bookletImposition/BookletImpositionSettings.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React from "react"; -import { Button, Stack, Text, Divider } from "@mantine/core"; -import { useTranslation } from "react-i18next"; -import { BookletImpositionParameters } from "../../../hooks/tools/bookletImposition/useBookletImpositionParameters"; - -interface BookletImpositionSettingsProps { - parameters: BookletImpositionParameters; - onParameterChange: (key: keyof BookletImpositionParameters, value: any) => void; - disabled?: boolean; -} - -const BookletImpositionSettings = ({ parameters, onParameterChange, disabled = false }: BookletImpositionSettingsProps) => { - const { t } = useTranslation(); - - return ( - - - - {/* Booklet Type */} - - Booklet Type -
- - -
- - {parameters.bookletType === 'BOOKLET' - ? "Standard booklet for saddle-stitched binding (fold in half)" - : "Side-stitched booklet for binding along the edge"} - -
- - - - {/* Pages Per Sheet */} - - Pages Per Sheet -
- - -
- - {parameters.pagesPerSheet === 2 - ? "Two pages side by side on each sheet (most common)" - : "Four pages arranged in a 2x2 grid on each sheet"} - -
- - - - {/* Page Orientation */} - - Page Orientation -
- - -
- - {parameters.pageOrientation === 'LANDSCAPE' - ? "A4 landscape → A5 portrait when folded (standard booklet size)" - : "A4 portrait → A6 when folded (smaller booklet)"} - -
- - - - {/* Add Border Option */} - - - - Helpful for cutting and alignment when printing - - -
- ); -}; - -export default BookletImpositionSettings; \ No newline at end of file diff --git a/frontend/src/components/tools/manageSignatures/CertificateFilesSettings.tsx b/frontend/src/components/tools/manageSignatures/CertificateFilesSettings.tsx deleted file mode 100644 index 598b2d833..000000000 --- a/frontend/src/components/tools/manageSignatures/CertificateFilesSettings.tsx +++ /dev/null @@ -1,95 +0,0 @@ -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 ( - - {/* Certificate Files based on type */} - {parameters.certType === 'PEM' && ( - - onParameterChange('privateKeyFile', file || undefined)} - accept=".pem,.der,.key" - disabled={disabled} - placeholder={t('certSign.choosePrivateKey', 'Choose Private Key File')} - /> - {parameters.privateKeyFile && ( - onParameterChange('certFile', file || undefined)} - accept=".pem,.der,.crt,.cer" - disabled={disabled} - placeholder={t('certSign.chooseCertificate', 'Choose Certificate File')} - /> - )} - - )} - - {parameters.certType === 'PKCS12' && ( - onParameterChange('p12File', file || undefined)} - accept=".p12" - disabled={disabled} - placeholder={t('certSign.chooseP12File', 'Choose PKCS12 File')} - /> - )} - - {parameters.certType === 'PFX' && ( - onParameterChange('p12File', file || undefined)} - accept=".pfx" - disabled={disabled} - placeholder={t('certSign.choosePfxFile', 'Choose PFX File')} - /> - )} - - {parameters.certType === 'JKS' && ( - onParameterChange('jksFile', file || undefined)} - accept=".jks,.keystore" - disabled={disabled} - placeholder={t('certSign.chooseJksFile', 'Choose JKS File')} - /> - )} - - {parameters.certType === 'SERVER' && ( - - {t('certSign.serverCertMessage', 'Using server certificate - no files or password required')} - - )} - - {/* Password - only show when files are uploaded */} - {parameters.certType && ( - (parameters.certType === 'PEM' && parameters.privateKeyFile && parameters.certFile) || - (parameters.certType === 'PKCS12' && parameters.p12File) || - (parameters.certType === 'PFX' && parameters.p12File) || - (parameters.certType === 'JKS' && parameters.jksFile) - ) && ( - onParameterChange('password', event.currentTarget.value)} - disabled={disabled} - /> - )} - - ); -}; - -export default CertificateFilesSettings; \ No newline at end of file diff --git a/frontend/src/components/tools/manageSignatures/CertificateFormatSettings.tsx b/frontend/src/components/tools/manageSignatures/CertificateFormatSettings.tsx deleted file mode 100644 index 6acbaf2c2..000000000 --- a/frontend/src/components/tools/manageSignatures/CertificateFormatSettings.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Stack, Button, Text } from "@mantine/core"; -import { useTranslation } from "react-i18next"; -import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters"; - -interface CertificateFormatSettingsProps { - parameters: ManageSignaturesParameters; - onParameterChange: (key: keyof ManageSignaturesParameters, value: any) => void; - disabled?: boolean; -} - -const CertificateFormatSettings = ({ parameters, onParameterChange, disabled = false }: CertificateFormatSettingsProps) => { - const { t } = useTranslation(); - - return ( - -
- {/* First row - PKCS#12 and PFX */} -
- - -
- {/* Second row - PEM and JKS */} -
- - -
-
-
- ); -}; - -export default CertificateFormatSettings; \ No newline at end of file diff --git a/frontend/src/components/tools/manageSignatures/CertificateTypeSettings.tsx b/frontend/src/components/tools/manageSignatures/CertificateTypeSettings.tsx deleted file mode 100644 index d594e21f9..000000000 --- a/frontend/src/components/tools/manageSignatures/CertificateTypeSettings.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Stack, Button, Text, Divider } 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 ( - -
- - -
-
- ); -}; - -export default CertificateTypeSettings; \ No newline at end of file diff --git a/frontend/src/components/tools/manageSignatures/SignatureAppearanceSettings.tsx b/frontend/src/components/tools/manageSignatures/SignatureAppearanceSettings.tsx deleted file mode 100644 index f59537fd7..000000000 --- a/frontend/src/components/tools/manageSignatures/SignatureAppearanceSettings.tsx +++ /dev/null @@ -1,110 +0,0 @@ -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 ( - - {/* Signature Visibility */} - -
- - -
-
- - {/* Visible Signature Options */} - {parameters.showSignature && ( - - - {t('certSign.appearance.options.title', 'Signature Details')} - - onParameterChange('reason', event.currentTarget.value)} - disabled={disabled} - /> - onParameterChange('location', event.currentTarget.value)} - disabled={disabled} - /> - onParameterChange('name', event.currentTarget.value)} - disabled={disabled} - /> - onParameterChange('pageNumber', value || 1)} - min={1} - disabled={disabled} - /> - - - {t('certSign.logoTitle', 'Logo')} - -
- - -
-
-
- )} -
- ); -}; - -export default SignatureAppearanceSettings; \ No newline at end of file diff --git a/frontend/src/components/tooltips/useCertificateTypeTips.ts b/frontend/src/components/tooltips/useCertificateTypeTips.ts deleted file mode 100644 index b01da9c2f..000000000 --- a/frontend/src/components/tooltips/useCertificateTypeTips.ts +++ /dev/null @@ -1,32 +0,0 @@ -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", "PKCS12 (.p12) – one combined file (most common)"), - t("manageSignatures.certType.tooltip.which.bullet2", "PFX (.pfx) – Microsoft's version of PKCS12"), - t("manageSignatures.certType.tooltip.which.bullet3", "PEM – separate private-key and certificate .pem files"), - t("manageSignatures.certType.tooltip.which.bullet4", "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.") - } - ] - }; -}; \ No newline at end of file diff --git a/frontend/src/components/tooltips/useManageSignaturesTooltips.ts b/frontend/src/components/tooltips/useManageSignaturesTooltips.ts deleted file mode 100644 index 3ff95cda5..000000000 --- a/frontend/src/components/tooltips/useManageSignaturesTooltips.ts +++ /dev/null @@ -1,45 +0,0 @@ -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, JKS, and server 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"), - t("manageSignatures.tooltip.signing.bullet5", "Use server certificate for simple 'Sign with Stirling-PDF' option") - ] - } - ] - }; -}; \ No newline at end of file diff --git a/frontend/src/components/tooltips/useSignModeTips.ts b/frontend/src/components/tooltips/useSignModeTips.ts deleted file mode 100644 index 63bbe93dc..000000000 --- a/frontend/src/components/tooltips/useSignModeTips.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { TooltipContent } from '../../types/tips'; - -export const useSignModeTips = (): TooltipContent => { - const { t } = useTranslation(); - - return { - header: { - title: t("manageSignatures.signMode.tooltip.header.title", "About PDF Signatures") - }, - tips: [ - { - title: t("manageSignatures.signMode.tooltip.overview.title", "How signatures work"), - description: t("manageSignatures.signMode.tooltip.overview.text", "Both modes seal the document (any edits are flagged as tampering) and record who/when/how for auditing. Viewer trust depends on the certificate chain.") - }, - { - title: t("manageSignatures.signMode.tooltip.manual.title", "Manual - Bring your certificate"), - description: t("manageSignatures.signMode.tooltip.manual.text", "Use your own certificate files for brand-aligned identity. Can display Trusted when your CA/chain is recognized."), - bullets: [ - t("manageSignatures.signMode.tooltip.manual.use", "Use for: customer-facing, legal, compliance.") - ] - }, - { - title: t("manageSignatures.signMode.tooltip.auto.title", "Auto - Zero-setup, instant system seal"), - description: t("manageSignatures.signMode.tooltip.auto.text", "Signs with a server self-signed certificate. Same tamper-evident seal and audit trail; typically shows Unverified in viewers."), - bullets: [ - t("manageSignatures.signMode.tooltip.auto.use", "Use when: you need speed and consistent internal identity across reviews and records.") - ] - }, - { - title: t("manageSignatures.signMode.tooltip.rule.title", "Rule of thumb"), - description: t("manageSignatures.signMode.tooltip.rule.text", "Need recipient Trusted status? Manual. Need a fast, tamper-evident seal and audit trail with no setup? Auto.") - } - ] - }; -}; \ No newline at end of file diff --git a/frontend/src/components/tooltips/useSignatureAppearanceTips.ts b/frontend/src/components/tooltips/useSignatureAppearanceTips.ts deleted file mode 100644 index 20580dabb..000000000 --- a/frontend/src/components/tooltips/useSignatureAppearanceTips.ts +++ /dev/null @@ -1,33 +0,0 @@ -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") - ] - } - ] - }; -}; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index bdb15635d..f3050ea01 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -17,8 +17,6 @@ import AutoRename from "../tools/AutoRename"; import SingleLargePage from "../tools/SingleLargePage"; import UnlockPdfForms from "../tools/UnlockPdfForms"; import RemoveCertificateSign from "../tools/RemoveCertificateSign"; -import ManageSignatures from "../tools/ManageSignatures"; -import BookletImposition from "../tools/BookletImposition"; import Flatten from "../tools/Flatten"; import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation"; import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation"; @@ -33,8 +31,6 @@ import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation"; import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation"; import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation"; import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation"; -import { manageSignaturesOperationConfig } from "../hooks/tools/manageSignatures/useManageSignaturesOperation"; -import { bookletImpositionOperationConfig } from "../hooks/tools/bookletImposition/useBookletImpositionOperation"; import { mergeOperationConfig } from '../hooks/tools/merge/useMergeOperation'; import { autoRenameOperationConfig } from "../hooks/tools/autoRename/useAutoRenameOperation"; import { flattenOperationConfig } from "../hooks/tools/flatten/useFlattenOperation"; @@ -50,8 +46,6 @@ import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/Add import OCRSettings from "../components/tools/ocr/OCRSettings"; import ConvertSettings from "../components/tools/convert/ConvertSettings"; import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings"; -import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings"; -import BookletImpositionSettings from "../components/tools/bookletImposition/BookletImpositionSettings"; import FlattenSettings from "../components/tools/flatten/FlattenSettings"; import RedactSingleStepSettings from "../components/tools/redact/RedactSingleStepSettings"; import Redact from "../tools/Redact"; @@ -154,15 +148,11 @@ export function useFlatToolRegistry(): ToolRegistry { certSign: { icon: , - name: t("home.certSign.title", "Certificate Sign"), - component: ManageSignatures, - description: t("home.certSign.desc", "Sign PDF documents using digital certificates"), + name: t("home.certSign.title", "Sign with Certificate"), + component: null, + description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"), categoryId: ToolCategoryId.STANDARD_TOOLS, subcategoryId: SubcategoryId.SIGNING, - maxFiles: -1, - endpoints: ["cert-sign"], - operationConfig: manageSignaturesOperationConfig, - settingsComponent: CertificateTypeSettings, }, sign: { icon: , @@ -268,14 +258,6 @@ export function useFlatToolRegistry(): ToolRegistry { }, // Verification - "validate-pdf-signature": { - icon: , - name: t("home.validateSignature.title", "Validate PDF Signature"), - component: null, - description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"), - categoryId: ToolCategoryId.STANDARD_TOOLS, - subcategoryId: SubcategoryId.VERIFICATION, - }, "get-all-info-on-pdf": { icon: , name: t("home.getPdfInfo.title", "Get ALL Info on PDF"), @@ -284,6 +266,14 @@ export function useFlatToolRegistry(): ToolRegistry { categoryId: ToolCategoryId.STANDARD_TOOLS, subcategoryId: SubcategoryId.VERIFICATION, }, + "validate-pdf-signature": { + icon: , + name: t("home.validateSignature.title", "Validate PDF Signature"), + component: null, + description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"), + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.VERIFICATION, + }, // Document Review @@ -377,16 +367,6 @@ export function useFlatToolRegistry(): ToolRegistry { categoryId: ToolCategoryId.STANDARD_TOOLS, subcategoryId: SubcategoryId.PAGE_FORMATTING, }, - "booklet-imposition": { - icon: , - name: t("home.bookletImposition.title", "Booklet Imposition"), - component: BookletImposition, - operationConfig: bookletImpositionOperationConfig, - settingsComponent: BookletImpositionSettings, - description: t("home.bookletImposition.desc", "Create booklets with proper page ordering and multi-page layout for printing and binding"), - categoryId: ToolCategoryId.STANDARD_TOOLS, - subcategoryId: SubcategoryId.PAGE_FORMATTING, - }, "single-large-page": { icon: , name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"), diff --git a/frontend/src/hooks/tools/bookletImposition/useBookletImpositionOperation.ts b/frontend/src/hooks/tools/bookletImposition/useBookletImpositionOperation.ts deleted file mode 100644 index d27fd547a..000000000 --- a/frontend/src/hooks/tools/bookletImposition/useBookletImpositionOperation.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation'; -import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { BookletImpositionParameters, defaultParameters } from './useBookletImpositionParameters'; - -// Static configuration that can be used by both the hook and automation executor -export const buildBookletImpositionFormData = (parameters: BookletImpositionParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - formData.append("bookletType", parameters.bookletType); - formData.append("pagesPerSheet", parameters.pagesPerSheet.toString()); - formData.append("addBorder", parameters.addBorder.toString()); - formData.append("pageOrientation", parameters.pageOrientation); - return formData; -}; - -// Static configuration object -export const bookletImpositionOperationConfig = { - toolType: ToolType.singleFile, - buildFormData: buildBookletImpositionFormData, - operationType: 'bookletImposition', - endpoint: '/api/v1/general/booklet-imposition', - filePrefix: 'booklet_', - defaultParameters, -} as const; - -export const useBookletImpositionOperation = () => { - const { t } = useTranslation(); - - return useToolOperation({ - ...bookletImpositionOperationConfig, - getErrorMessage: createStandardErrorHandler(t('bookletImposition.error.failed', 'An error occurred while creating the booklet imposition.')) - }); -}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/bookletImposition/useBookletImpositionParameters.ts b/frontend/src/hooks/tools/bookletImposition/useBookletImpositionParameters.ts deleted file mode 100644 index 2e951a1fa..000000000 --- a/frontend/src/hooks/tools/bookletImposition/useBookletImpositionParameters.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseParameters } from '../../../types/parameters'; -import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; - -export interface BookletImpositionParameters extends BaseParameters { - bookletType: 'BOOKLET' | 'SIDE_STITCH_BOOKLET'; - pagesPerSheet: 2 | 4; - addBorder: boolean; - pageOrientation: 'LANDSCAPE' | 'PORTRAIT'; -} - -export const defaultParameters: BookletImpositionParameters = { - bookletType: 'BOOKLET', - pagesPerSheet: 2, - addBorder: false, - pageOrientation: 'LANDSCAPE', -}; - -export type BookletImpositionParametersHook = BaseParametersHook; - -export const useBookletImpositionParameters = (): BookletImpositionParametersHook => { - return useBaseParameters({ - defaultParameters, - endpointName: 'booklet-imposition', - validateFn: (params) => { - return params.pagesPerSheet === 2 || params.pagesPerSheet === 4; - }, - }); -}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/manageSignatures/useManageSignaturesOperation.ts b/frontend/src/hooks/tools/manageSignatures/useManageSignaturesOperation.ts deleted file mode 100644 index 5265c2cf9..000000000 --- a/frontend/src/hooks/tools/manageSignatures/useManageSignaturesOperation.ts +++ /dev/null @@ -1,73 +0,0 @@ -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); - - // Handle sign mode - if (parameters.signMode === 'AUTO') { - formData.append('certType', 'SERVER'); - } else { - formData.append('certType', parameters.certType); - formData.append('password', parameters.password); - - // Add certificate files based on type (only for manual mode) - 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({ - ...manageSignaturesOperationConfig, - filePrefix: t('manageSignatures.filenamePrefix', 'signed') + '_', - getErrorMessage: createStandardErrorHandler(t('manageSignatures.error.failed', 'An error occurred while processing signatures.')) - }); -}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/manageSignatures/useManageSignaturesParameters.ts b/frontend/src/hooks/tools/manageSignatures/useManageSignaturesParameters.ts deleted file mode 100644 index 70fa80c29..000000000 --- a/frontend/src/hooks/tools/manageSignatures/useManageSignaturesParameters.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { BaseParameters } from '../../../types/parameters'; -import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; - -export interface ManageSignaturesParameters extends BaseParameters { - // Sign mode selection - signMode: 'MANUAL' | 'AUTO'; - // Certificate signing options (only for manual mode) - certType: '' | 'PEM' | 'PKCS12' | 'PFX' | '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 = { - signMode: 'MANUAL', - certType: '', - password: '', - showSignature: false, - reason: '', - location: '', - name: '', - pageNumber: 1, - showLogo: true, -}; - -export type ManageSignaturesParametersHook = BaseParametersHook; - -export const useManageSignaturesParameters = (): ManageSignaturesParametersHook => { - return useBaseParameters({ - defaultParameters, - endpointName: 'manage-signatures', - validateFn: (params) => { - // Auto mode (server certificate) - no additional validation needed - if (params.signMode === 'AUTO') { - return true; - } - - // Manual mode - requires certificate type and files - 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': - case 'PFX': - return !!params.p12File; - case 'JKS': - return !!params.jksFile; - default: - return false; - } - }, - }); -}; \ No newline at end of file diff --git a/frontend/src/services/signatureDetectionService.ts b/frontend/src/services/signatureDetectionService.ts deleted file mode 100644 index 44a12be92..000000000 --- a/frontend/src/services/signatureDetectionService.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * 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 => { - 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 => { - 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([]); - 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'; \ No newline at end of file diff --git a/frontend/src/tools/BookletImposition.tsx b/frontend/src/tools/BookletImposition.tsx deleted file mode 100644 index bcd619b34..000000000 --- a/frontend/src/tools/BookletImposition.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { createToolFlow } from "../components/tools/shared/createToolFlow"; -import BookletImpositionSettings from "../components/tools/bookletImposition/BookletImpositionSettings"; -import { useBookletImpositionParameters } from "../hooks/tools/bookletImposition/useBookletImpositionParameters"; -import { useBookletImpositionOperation } from "../hooks/tools/bookletImposition/useBookletImpositionOperation"; -import { useBaseTool } from "../hooks/tools/shared/useBaseTool"; -import { BaseToolProps, ToolComponent } from "../types/tool"; - -const BookletImposition = (props: BaseToolProps) => { - const { t } = useTranslation(); - - const base = useBaseTool( - 'bookletImposition', - useBookletImpositionParameters, - useBookletImpositionOperation, - props - ); - - return createToolFlow({ - files: { - selectedFiles: base.selectedFiles, - isCollapsed: base.hasResults, - placeholder: t("bookletImposition.files.placeholder", "Select PDF files to create booklet impositions from"), - }, - steps: [ - { - title: "Settings", - isCollapsed: base.settingsCollapsed, - onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, - content: ( - - ), - }, - ], - executeButton: { - text: t("bookletImposition.submit", "Create Booklet"), - 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("bookletImposition.title", "Booklet Imposition Results"), - onFileClick: base.handleThumbnailClick, - onUndo: base.handleUndo, - }, - }); -}; - -export default BookletImposition as ToolComponent; \ No newline at end of file diff --git a/frontend/src/tools/ManageSignatures.tsx b/frontend/src/tools/ManageSignatures.tsx deleted file mode 100644 index 8f927a862..000000000 --- a/frontend/src/tools/ManageSignatures.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { createToolFlow } from "../components/tools/shared/createToolFlow"; -import CertificateTypeSettings from "../components/tools/manageSignatures/CertificateTypeSettings"; -import CertificateFormatSettings from "../components/tools/manageSignatures/CertificateFormatSettings"; -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 { useSignModeTips } from "../components/tooltips/useSignModeTips"; -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(); - const signModeTips = useSignModeTips(); - - // Check if certificate files are configured for appearance step - const areCertFilesConfigured = () => { - const params = base.params.parameters; - - // Auto mode (server certificate) - always configured - if (params.signMode === 'AUTO') { - return true; - } - - // Manual mode - check for required files based on cert type - switch (params.certType) { - case 'PEM': - return !!(params.privateKeyFile && params.certFile); - case 'PKCS12': - case 'PFX': - return !!params.p12File; - case 'JKS': - return !!params.jksFile; - default: - return false; - } - }; - - return createToolFlow({ - forceStepNumbers: true, - files: { - selectedFiles: base.selectedFiles, - isCollapsed: base.hasResults, - placeholder: t("certSign.files.placeholder", "Select PDF files to sign with certificates"), - }, - steps: [ - { - title: t("certSign.signMode.stepTitle", "Sign Mode"), - isCollapsed: base.settingsCollapsed, - onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, - tooltip: signModeTips, - content: ( - - ), - }, - ...(base.params.parameters.signMode === 'MANUAL' ? [{ - title: t("certSign.certTypeStep.stepTitle", "Certificate Format"), - isCollapsed: base.settingsCollapsed, - onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, - tooltip: certTypeTips, - content: ( - - ), - }] : []), - ...(base.params.parameters.signMode === 'MANUAL' ? [{ - title: t("certSign.certFiles.stepTitle", "Certificate Files"), - isCollapsed: base.settingsCollapsed, - onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, - content: ( - - ), - }] : []), - { - title: t("certSign.appearance.stepTitle", "Signature Appearance"), - isCollapsed: base.settingsCollapsed || !areCertFilesConfigured(), - onCollapsedClick: (base.settingsCollapsed || !areCertFilesConfigured()) ? base.handleSettingsReset : undefined, - tooltip: appearanceTips, - content: ( - - ), - }, - ], - executeButton: { - text: t("certSign.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("certSign.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; \ No newline at end of file diff --git a/frontend/src/types/toolId.ts b/frontend/src/types/toolId.ts index 9859723e1..be38bdf37 100644 --- a/frontend/src/types/toolId.ts +++ b/frontend/src/types/toolId.ts @@ -7,13 +7,13 @@ const TOOL_IDS = [ 'detect-split-scanned-photos', 'edit-table-of-contents', 'scanner-effect', - 'auto-rename-pdf-file', 'multi-page-layout', 'booklet-imposition', 'adjust-page-size-scale', 'adjust-contrast', 'cropPdf', 'single-large-page', 'multi-tool', + 'auto-rename-pdf-file', 'multi-page-layout', 'adjust-page-size-scale', 'adjust-contrast', 'cropPdf', 'single-large-page', 'multi-tool', 'repair', 'compare', 'addPageNumbers', 'redact', 'flatten', 'remove-certificate-sign', 'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images', 'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs', - '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', 'validate-pdf-signature' + 'manage-certificates', 'get-all-info-on-pdf', 'validate-pdf-signature', 'read', 'automate', 'replace-and-invert-color', + 'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped' ] as const; // Tool identity - what PDF operation we're performing (type-safe)