feat: add Timestamp PDF tool with RFC 3161 support
Add document timestamping to the Secure PDF section using RFC 3161 protocol. Users can select from preset TSA servers (DigiCert, Sectigo, SSL.com, Entrust, FreeTSA) or enter a custom TSA URL. No personal certificate is required — only a cryptographic hash is sent to the server. Key changes: - Timestamp PDF page with TSA server selector, FAQ and SEO structured data - timestampPdf() function with CORS proxy URL resolution - TimestampNode for the workflow engine - Tool entry in Secure PDF category + homepage i18n - Built-in CORS proxy middleware for dev/preview - Translations for all 16 languages Tested with DigiCert, Sectigo and Entrust TSA servers. Timestamps are verifiable in Adobe Acrobat (ETSI.RFC3161 SubFilter).
This commit is contained in:
@@ -35,6 +35,7 @@ import { SanitizeNode } from './sanitize-node';
|
||||
import { EncryptNode } from './encrypt-node';
|
||||
import { DecryptNode } from './decrypt-node';
|
||||
import { DigitalSignNode } from './digital-sign-node';
|
||||
import { TimestampNode } from './timestamp-node';
|
||||
import { RedactNode } from './redact-node';
|
||||
import { RepairNode } from './repair-node';
|
||||
import { PdfToTextNode } from './pdf-to-text-node';
|
||||
@@ -509,6 +510,13 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
|
||||
description: 'Apply a digital signature to PDF',
|
||||
factory: () => new DigitalSignNode(),
|
||||
},
|
||||
TimestampNode: {
|
||||
label: 'Timestamp',
|
||||
category: 'Secure PDF',
|
||||
icon: 'ph-clock',
|
||||
description: 'Add an RFC 3161 document timestamp',
|
||||
factory: () => new TimestampNode(),
|
||||
},
|
||||
RedactNode: {
|
||||
label: 'Redact',
|
||||
category: 'Secure PDF',
|
||||
|
||||
60
src/js/workflow/nodes/timestamp-node.ts
Normal file
60
src/js/workflow/nodes/timestamp-node.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { TIMESTAMP_TSA_PRESETS } from '../../config/timestamp-tsa.js';
|
||||
import { timestampPdf } from '../../logic/digital-sign-pdf.js';
|
||||
|
||||
export class TimestampNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
readonly icon = 'ph-clock';
|
||||
readonly description = 'Add an RFC 3161 document timestamp';
|
||||
|
||||
constructor() {
|
||||
super('Timestamp');
|
||||
this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDF'));
|
||||
this.addOutput(
|
||||
'pdf',
|
||||
new ClassicPreset.Output(pdfSocket, 'Timestamped PDF')
|
||||
);
|
||||
this.addControl(
|
||||
'tsaUrl',
|
||||
new ClassicPreset.InputControl('text', {
|
||||
initial: TIMESTAMP_TSA_PRESETS[0].url,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getTsaPresets(): { label: string; url: string }[] {
|
||||
return TIMESTAMP_TSA_PRESETS;
|
||||
}
|
||||
|
||||
async data(
|
||||
inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
const pdfInputs = requirePdfInput(inputs, 'Timestamp');
|
||||
|
||||
const tsaUrlCtrl = this.controls['tsaUrl'] as
|
||||
| ClassicPreset.InputControl<'text'>
|
||||
| undefined;
|
||||
const tsaUrl = tsaUrlCtrl?.value || TIMESTAMP_TSA_PRESETS[0].url;
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const timestampedBytes = await timestampPdf(input.bytes, tsaUrl);
|
||||
|
||||
const bytes = new Uint8Array(timestampedBytes);
|
||||
const document = await PDFDocument.load(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
document,
|
||||
bytes,
|
||||
filename: input.filename.replace(/\.pdf$/i, '_timestamped.pdf'),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user