Files
pipu-music/apps/web/e2e/uploads-csp.spec.ts
T
mrleo1nid 17bb0e1846
ci / ts / ts (packages + web) (push) Failing after 4m29s
ci / desktop / rust (desktop proxy) (push) Successful in 5m54s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 3m23s
fix(uploads): allow WebAssembly hashing under CSP
2026-10-28 23:34:42 +03:00

49 lines
2.4 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { expect, test } from '@playwright/test';
const nginxHeaders = readFileSync(new URL('../../../deploy/nginx/security-headers.conf', import.meta.url), 'utf8');
const desktopConfig = JSON.parse(readFileSync(new URL('../../desktop/src-tauri/tauri.conf.json', import.meta.url), 'utf8'));
const policies: Record<string, string> = {
web: nginxHeaders.match(/add_header Content-Security-Policy "([^"]+)" always;/)![1],
desktop: desktopConfig.app.security.csp,
};
for (const [platform, policy] of Object.entries(policies)) {
test(`upload hash worker runs under ${platform} CSP`, async ({ page }) => {
// Apply the deployed policy to both the document and worker responses.
await page.route('**/*', async (route) => {
if (new URL(route.request().url()).pathname === '/csp-upload-test') {
await route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>Upload CSP</title>', headers: { 'Content-Security-Policy': policy } });
return;
}
if (new URL(route.request().url()).pathname === '/csp-eval-test.js') {
await route.fulfill({ contentType: 'application/javascript', body: `
try { new Function('return 1')(); document.documentElement.dataset.evalBlocked = 'false'; }
catch { document.documentElement.dataset.evalBlocked = 'true'; }
` });
return;
}
const response = await route.fetch();
await route.fulfill({ response, headers: { ...response.headers(), 'Content-Security-Policy': policy } });
});
await page.goto('/csp-upload-test');
const result = await page.evaluate(() => new Promise((resolve, reject) => {
const worker = new Worker('/src/features/uploads/hash.worker.ts', { type: 'module' });
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
worker.onmessage = ({ data }) => {
if (data.type === 'progress') return;
worker.terminate();
resolve(data);
};
worker.postMessage({ id: 'csp-test', file: new File(['abc'], 'track.flac') });
}));
expect(result).toEqual({
id: 'csp-test', type: 'done',
sha256: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
});
// A page script observes CSP; Playwright's evaluate bypasses the eval restriction.
await page.addScriptTag({ url: '/csp-eval-test.js' });
await expect(page.locator('html')).toHaveAttribute('data-eval-blocked', 'true');
});
}