107 lines
4.5 KiB
JavaScript
107 lines
4.5 KiB
JavaScript
// Generated, do not edit. sha256:47f02343d278744260374448334893708a776affbe44624b7fc8f089e85783c7
|
|||
|
|
import { describe, it, before, after } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { createServer } from 'node:http';
|
||
|
|
|
||
|
|
import { createRequestListener } from '../../server/app.mjs';
|
||
|
|
import { originOf, connectSrcOrigins, originAllowedByCsp } from '../../shared/csp.js';
|
||
|
|
import { CONFIG } from '../../shared/config.js';
|
||
|
|
|
||
|
|
describe('originOf', () => {
|
||
|
|
it('geeft het origin van een gewone URL', () => {
|
||
|
|
assert.equal(originOf('https://mempool.space/api/blocks/tip/height'), 'https://mempool.space');
|
||
|
|
assert.equal(originOf('https://pool.example.nl:3006/api'), 'https://pool.example.nl:3006');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft null bij iets dat geen URL is, in plaats van te gooien', () => {
|
||
|
|
for (const rommel of ['', ' ', 'pool.example.nl', 'niet eens een adres', null, undefined, 42]) {
|
||
|
|
assert.equal(originOf(rommel), null, `${rommel} hoort null te geven`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('connectSrcOrigins', () => {
|
||
|
|
it('bevat altijd de publieke mempool.space, ook zonder verbindingen', () => {
|
||
|
|
assert.deepEqual(connectSrcOrigins(), [originOf(CONFIG.PRICE_SOURCES.MEMPOOL_PUBLIC_BASE)]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('neemt een eigen explorer-verbinding op, zonder codewijziging', () => {
|
||
|
|
const origins = connectSrcOrigins(['https://pool.example.nl:3006/', 'https://mempool.space']);
|
||
|
|
assert.deepEqual(origins, ['https://mempool.space', 'https://pool.example.nl:3006']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat met een verbindingsadres niets anders in de header komen dan een origin', () => {
|
||
|
|
const origins = connectSrcOrigins([
|
||
|
|
'javascript:alert(1)', 'data:text/plain,x', 'https://a.example; script-src *',
|
||
|
|
'https://b.example *', 'file:///etc/passwd', 'niet eens een adres',
|
||
|
|
]);
|
||
|
|
assert.deepEqual(origins, ['https://mempool.space']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ontdubbelt en bevat geen null', () => {
|
||
|
|
const origins = connectSrcOrigins(['https://mempool.space/api', 'https://mempool.space']);
|
||
|
|
assert.equal(new Set(origins).size, origins.length);
|
||
|
|
assert.equal(origins.some(o => !o), false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('originAllowedByCsp', () => {
|
||
|
|
it('staat een origin uit de allowlist toe', () => {
|
||
|
|
assert.equal(originAllowedByCsp(`${CONFIG.PRICE_SOURCES.MEMPOOL_PUBLIC_BASE}/api/blocks/tip/height`), true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('WEIGERT een profiel dat niet in de lijst van het document stond', () => {
|
||
|
|
assert.equal(originAllowedByCsp('https://mijn-eigen-explorer.example/api'), false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('staat een profiel toe dat bij het laden al een verbinding was', () => {
|
||
|
|
assert.equal(originAllowedByCsp('https://mijn-eigen-explorer.example/api/blocks/tip/height', null,
|
||
|
|
['https://mijn-eigen-explorer.example']), true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert rommel in plaats van hem door te laten', () => {
|
||
|
|
for (const rommel of ['', 'pool.example.nl', null, undefined]) {
|
||
|
|
assert.equal(originAllowedByCsp(rommel), false);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('staat de eigen origin toe, maar alleen als die is meegegeven', () => {
|
||
|
|
assert.equal(originAllowedByCsp('https://localhost:8080/api/data', 'https://localhost:8080'), true);
|
||
|
|
assert.equal(originAllowedByCsp('https://localhost:8080/api/data'), false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('de CSP-header van een echt verzoek', () => {
|
||
|
|
let server, basis, csp;
|
||
|
|
|
||
|
|
before(async () => {
|
||
|
|
server = createServer(createRequestListener({ credentials: null }));
|
||
|
|
await new Promise(klaar => server.listen(0, '127.0.0.1', klaar));
|
||
|
|
basis = `http://127.0.0.1:${server.address().port}`;
|
||
|
|
const res = await fetch(`${basis}/index.html`);
|
||
|
|
assert.equal(res.status, 200, 'het inlaadpunt hoort gewoon geserveerd te worden');
|
||
|
|
csp = res.headers.get('content-security-policy');
|
||
|
|
});
|
||
|
|
|
||
|
|
after(() => server.close());
|
||
|
|
|
||
|
|
it('stuurt een header mee', () => {
|
||
|
|
assert.ok(csp, 'zonder header is er geen grens en zegt de rest van deze suite niets');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('staat in connect-src precies de origins toe die de helper noemt', () => {
|
||
|
|
const directive = csp.split(';').map(d => d.trim()).find(d => d.startsWith('connect-src'));
|
||
|
|
const bronnen = directive.split(/\s+/).slice(1);
|
||
|
|
assert.deepEqual(bronnen, [`'self'`, ...connectSrcOrigins()]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('houdt script-src kaal op self, zonder enige uitzondering', () => {
|
||
|
|
const directive = csp.split(';').map(d => d.trim()).find(d => d.startsWith('script-src'));
|
||
|
|
assert.equal(directive, `script-src 'self'`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('sluit object-src en frame-ancestors helemaal af', () => {
|
||
|
|
assert.match(csp, /object-src 'none'/);
|
||
|
|
assert.match(csp, /frame-ancestors 'none'/);
|
||
|
|
});
|
||
|
|
});
|