diff --git a/src/js/logic/digital-sign-pdf.ts b/src/js/logic/digital-sign-pdf.ts index 59729b5a..9fabc75a 100644 --- a/src/js/logic/digital-sign-pdf.ts +++ b/src/js/logic/digital-sign-pdf.ts @@ -103,8 +103,34 @@ export function parseCombinedPem( * and set VITE_CORS_PROXY_URL environment variable. * * If not set, certificates requiring external chain fetching will fail. + * + * On the official BentoPDF domains we fall back to the project-operated proxy. + * The generated CSP already allows that origin by default (see + * scripts/generate-security-headers.mjs), but the runtime never used it, which + * left the timestamp tool broken on the official HTTPS site. The fallback is + * deliberately scoped to the official domains so self-hosted instances keep + * requiring their own proxy. */ -const CORS_PROXY_URL = import.meta.env.VITE_CORS_PROXY_URL || ''; +const DEFAULT_CORS_PROXY_URL = + 'https://bentopdf-cors-proxy.bentopdf.workers.dev'; + +const OFFICIAL_HOSTNAMES = new Set(['bentopdf.com', 'www.bentopdf.com']); + +function resolveCorsProxyUrl(): string { + const configured = import.meta.env.VITE_CORS_PROXY_URL || ''; + if (configured) { + return configured; + } + + if (typeof window === 'undefined') { + return ''; + } + + const hostname = window.location?.hostname?.toLowerCase() ?? ''; + return OFFICIAL_HOSTNAMES.has(hostname) ? DEFAULT_CORS_PROXY_URL : ''; +} + +const CORS_PROXY_URL = resolveCorsProxyUrl(); /** * Shared secret for signing proxy requests (HMAC-SHA256). diff --git a/src/tests/digital-sign-pdf.test.ts b/src/tests/digital-sign-pdf.test.ts index 799a28b0..d6819a14 100644 --- a/src/tests/digital-sign-pdf.test.ts +++ b/src/tests/digital-sign-pdf.test.ts @@ -143,7 +143,11 @@ describe('timestampPdf', () => { vi.stubEnv('VITE_CORS_PROXY_URL', ''); Object.defineProperty(window, 'location', { configurable: true, - value: { protocol: 'https:', origin: 'https://www.bentopdf.com' }, + value: { + protocol: 'https:', + origin: 'https://selfhost.example.com', + hostname: 'selfhost.example.com', + }, }); vi.resetModules(); const { timestampPdf: freshTimestamp } = @@ -153,4 +157,32 @@ describe('timestampPdf', () => { freshTimestamp(samplePdfBytes, 'http://timestamp.digicert.com') ).rejects.toThrow(/HTTPS page|VITE_CORS_PROXY_URL/); }); + + it('should fall back to the default proxy on official domains', async () => { + vi.stubEnv('VITE_CORS_PROXY_URL', ''); + Object.defineProperty(window, 'location', { + configurable: true, + value: { + protocol: 'https:', + origin: 'https://www.bentopdf.com', + hostname: 'www.bentopdf.com', + }, + }); + vi.resetModules(); + const { timestampPdf: freshTimestamp } = + await import('@/js/logic/digital-sign-pdf'); + + mockSign.mockResolvedValueOnce(new Uint8Array([1])); + await freshTimestamp(samplePdfBytes, 'http://timestamp.digicert.com'); + + const callArg = vi.mocked(PdfSigner).mock.calls[0][0] as { + signdate: { url: string }; + }; + expect(callArg.signdate.url).toMatch( + /^https:\/\/bentopdf-cors-proxy\.bentopdf\.workers\.dev\?url=/ + ); + expect(callArg.signdate.url).toContain( + encodeURIComponent('http://timestamp.digicert.com') + ); + }); });