fix(timestamp): fall back to the default CORS proxy on official domains

The timestamp tool requires a CORS proxy: none of the built-in TSA endpoints send Access-Control-Allow-Origin, so the browser blocks the RFC 3161 POST. The runtime only used a proxy when VITE_CORS_PROXY_URL was set, and the official build never set it, leaving timestamping broken on www.bentopdf.com and on the published Docker images.

Fall back to the project-operated proxy on the official domains only (bentopdf.com, www.bentopdf.com), matching the origin already allowed in the generated CSP. Self-hosted instances keep requiring their own proxy.
This commit is contained in:
Carmine Zacchia
2026-09-13 23:18:59 +02:00
parent 597e36904e
commit 3aefd7bb06
2 changed files with 60 additions and 2 deletions
+27 -1
View File
@@ -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).
+33 -1
View File
@@ -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')
);
});
});