68 lines
4.8 KiB
JavaScript
68 lines
4.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createHash } from 'node:crypto';
|
|
import { REQUEST_ROUTE, requestHTTP } from '../src/request-http.mjs';
|
|
|
|
test('owner downloads the exact authorized bytes as a private inert attachment', async () => {
|
|
const path = '/api/requests/r1/artifact/d1';
|
|
assert.match(path, REQUEST_ROUTE);
|
|
const bytes = Buffer.from('<!doctype html><title>Timer</title>');
|
|
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
const session = { name: 'owner', provider: 'test', iss: 'test', sub: 'owner' };
|
|
const req = { method: 'GET', headersDistinct: { 'sec-fetch-site': ['same-origin'] } };
|
|
const res = { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(data) { this.data = data; } };
|
|
const calls = [];
|
|
await requestHTTP(req, res, { path, session, live: () => session, origin: 'https://console.test', log() {}, requestWrite: null,
|
|
requestRead: async (verb, args) => { calls.push({ verb, args }); return verb === 'request.get.console'
|
|
? { version: 1, request: 'r1', revision: 1, phase: 'complete', delivery: 'd1' }
|
|
: { mime: 'text/html', filename: 'timer.html', base64: bytes.toString('base64'), sha256: hash }; } });
|
|
assert.equal(res.status, 200);
|
|
assert.deepEqual(res.data, bytes);
|
|
assert.match(res.headers['content-disposition'], /^attachment/);
|
|
assert.equal(res.headers['x-content-type-options'], 'nosniff');
|
|
assert.match(res.headers['cache-control'], /private/);
|
|
assert.match(res.headers['cache-control'], /no-store/);
|
|
assert.deepEqual(calls.map(c => c.verb), ['request.get.console', 'request.artifact.console']);
|
|
});
|
|
|
|
test('artifact access refuses foreign ownership, tampered bytes and expired sessions', async () => {
|
|
const path = '/api/requests/r1/artifact/d1', session = { name: 'owner', provider: 'test', iss: 'test', sub: 'owner' };
|
|
const req = { method: 'GET', headersDistinct: { 'sec-fetch-site': ['same-origin'] } };
|
|
const attempt = async (read, live = () => session) => {
|
|
const res = { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(data) { this.data = data; } };
|
|
await requestHTTP(req, res, { path, session, live, origin: 'https://console.test', log() {}, requestWrite: null, requestRead: read }); return res;
|
|
};
|
|
const state = { version: 1, request: 'r1', revision: 1, phase: 'complete', delivery: 'd1' };
|
|
assert.equal((await attempt(async () => ({ ...state, delivery: 'someone-else' }))).status, 404);
|
|
assert.equal((await attempt(async verb => verb === 'request.get.console' ? state : { mime: 'text/html', filename: 'timer.html', base64: 'eA==', sha256: '0'.repeat(64) })).status, 503);
|
|
assert.equal((await attempt(async () => state, () => null)).status, 401);
|
|
});
|
|
|
|
test('text view is inert and uses the same current delivery and digest checks', async () => {
|
|
const path = '/api/requests/r1/artifact/d1/text';
|
|
assert.match(path, REQUEST_ROUTE);
|
|
const bytes = Buffer.from('<script>window.executed = true</script>');
|
|
const session = { name: 'owner', provider: 'test', iss: 'test', sub: 'owner' };
|
|
const req = { method: 'GET', headersDistinct: { 'sec-fetch-site': ['same-origin'] } };
|
|
const state = { version: 1, request: 'r1', revision: 1, phase: 'complete', delivery: 'd1' };
|
|
const artifact = { mime: 'text/html', filename: 'timer.html', base64: bytes.toString('base64'), sha256: createHash('sha256').update(bytes).digest('hex') };
|
|
const attempt = async (read, live = () => session) => {
|
|
const res = { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(data) { this.data = data; } };
|
|
await requestHTTP(req, res, { path, session, live, origin: 'https://console.test', log() {}, requestRead: read });
|
|
return res;
|
|
};
|
|
const good = await attempt(async verb => verb === 'request.get.console' ? state : artifact);
|
|
assert.equal(good.status, 200);
|
|
assert.deepEqual(good.data, bytes);
|
|
assert.equal(good.headers['content-type'], 'text/plain; charset=utf-8');
|
|
assert.equal(good.headers['x-content-type-options'], 'nosniff');
|
|
assert.equal(good.headers['content-security-policy'], "sandbox; default-src 'none'");
|
|
assert.equal(good.headers['cache-control'], 'private, no-store');
|
|
assert.equal((await attempt(async verb => verb === 'request.get.console' ? { ...state, delivery: 'd2' } : artifact)).status, 404);
|
|
assert.equal((await attempt(async verb => verb === 'request.get.console' ? state : { ...artifact, sha256: '0'.repeat(64) })).status, 503);
|
|
let reads = 0;
|
|
assert.equal((await attempt(async verb => { reads++; return verb === 'request.get.console' ? state : artifact; }, () => reads < 2 ? session : null)).status, 401);
|
|
assert.doesNotMatch('/api/requests/r1/artifact/d1/text/extra', REQUEST_ROUTE);
|
|
assert.doesNotMatch('/api/requests/r1/artifact/d1/html', REQUEST_ROUTE);
|
|
});
|