Files
clean-architecture-frontend…/tests/browser-capabilities/presigned-streaming.spec.ts
T

367 lines
11 KiB
TypeScript

import { createHash } from "node:crypto";
import { expect, test } from "../support/browser/strict-browser-test.ts";
const RESOURCE_ID = "browser-artifact-1";
const OBJECT_ORIGIN = "https://objects.example.test";
const OBJECT_PATH = "/files/browser-artifact-1";
const OBJECT_HREF =
`${OBJECT_ORIGIN}${OBJECT_PATH}?sig=opaque-browser-signature`;
const DOWNLOAD_BYTES = Buffer.from([
0x10, 0x20, 0x30, 0x40, 0x50,
0x60, 0x70, 0x80, 0x90, 0xa0,
]);
const DOWNLOAD_SHA256 = createHash("sha256")
.update(DOWNLOAD_BYTES)
.digest("hex");
test("issues an opaque capability and streams a verified object into the writable save path", async ({
page,
}) => {
const bffRequests: unknown[] = [];
const objectRequests: Readonly<{
url: string;
method: string;
accept: string | undefined;
cookie: string | undefined;
}>[] = [];
await page.route("**/api/download-capability", async (route) => {
bffRequests.push(route.request().postDataJSON());
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
capabilityReceipt: "browser-download-capability-1",
method: "GET",
binding: {
kind: "DOWNLOAD",
resourceId: RESOURCE_ID,
},
href: OBJECT_HREF,
origin: OBJECT_ORIGIN,
path: OBJECT_PATH,
allowedQueryParameters: ["sig"],
requestHeaders: [
{
name: "accept",
value: "application/octet-stream",
},
],
requiredResponseHeaders: [
{
name: "x-policy-version",
value: "v1",
},
],
digestRequestHeader: null,
digestResponseHeader: "x-content-sha256",
receiptResponseHeader: null,
expectedStatus: 200,
expectedResponseByteLength: null,
mediaType: "application/octet-stream",
byteLength: DOWNLOAD_BYTES.byteLength,
maxBytes: 64,
expectedSha256: DOWNLOAD_SHA256,
expiresAtEpochMs: Date.now() + 5 * 60_000,
singleUse: true,
}),
});
});
await page.route(`${OBJECT_ORIGIN}/**`, async (route) => {
const headers = route.request().headers();
objectRequests.push({
url: route.request().url(),
method: route.request().method(),
accept: headers.accept,
cookie: headers.cookie,
});
await route.fulfill({
status: 200,
body: DOWNLOAD_BYTES,
headers: {
"access-control-allow-origin": "*",
"access-control-expose-headers":
"content-length, x-content-sha256, x-policy-version",
"content-length": String(DOWNLOAD_BYTES.byteLength),
"content-type": "application/octet-stream",
"x-content-sha256": DOWNLOAD_SHA256,
"x-policy-version": "v1",
},
});
});
await page.goto("/");
const result = await page.evaluate(
async ({ resourceId }) => {
const vaultModulePath =
"/src/adapters/browser-transfer/presigned/presigned-capability-vault.ts";
const providerModulePath =
"/src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts";
const executorModulePath =
"/src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts";
const policyModulePath =
"/src/adapters/browser-files/browser-file-policy-registry.ts";
const deliveryModulePath =
"/src/adapters/browser-files/download-delivery-adapter.ts";
const [
{
createPresignedCapabilityVault,
createSingleUsePresignedReplayGuard,
},
{ createPresignedCapabilityHttpProvider },
{ createPresignedTransferExecutor },
{
BrowserFilePolicyRegistry,
browserFilePolicyReference,
},
{ createDownloadDeliveryAdapter },
] = await Promise.all([
import(
/* @vite-ignore */ vaultModulePath
) as Promise<
typeof import("../../src/adapters/browser-transfer/presigned/presigned-capability-vault.ts")
>,
import(
/* @vite-ignore */ providerModulePath
) as Promise<
typeof import("../../src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts")
>,
import(
/* @vite-ignore */ executorModulePath
) as Promise<
typeof import("../../src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts")
>,
import(
/* @vite-ignore */ policyModulePath
) as Promise<
typeof import("../../src/adapters/browser-files/browser-file-policy-registry.ts")
>,
import(
/* @vite-ignore */ deliveryModulePath
) as Promise<
typeof import("../../src/adapters/browser-files/download-delivery-adapter.ts")
>,
]);
const vault = createPresignedCapabilityVault({
maxActiveCapabilities: 4,
});
const provider = createPresignedCapabilityHttpProvider({
endpoint: `${location.origin}/api/download-capability`,
vault,
allowedDataOrigins: ["https://objects.example.test"],
allowedDataPathPrefixes: ["/files/"],
allowedQueryParameters: ["sig"],
allowedRequestHeaders: ["accept"],
allowedResponseHeaders: [
"x-content-sha256",
"x-policy-version",
],
hardMaxTransferBytes: 64,
hardMaxUploadResponseBytes: 1_024,
maxCapabilityTtlMs: 10 * 60_000,
minimumRemainingLifetimeMs: 30_000,
timeoutMs: 5_000,
allowInsecureLocalhost: true,
});
const executor = createPresignedTransferExecutor({
vault,
replayGuard: createSingleUsePresignedReplayGuard(),
hardMaxTransferBytes: 64,
hardMaxChunkBytes: 3,
hardMaxUploadResponseBytes: 1_024,
minimumRemainingLifetimeMs: 30_000,
timeoutMs: 5_000,
});
const signal = new AbortController().signal;
const issued = await provider.issueDownload({
resourceId,
signal,
});
if (!issued.ok) {
vault.dispose();
return {
issued,
delivery: null,
capabilityKeys: [],
capabilityBindingKeys: [],
written: [],
writeChunkSizes: [],
writableClosed: false,
writableAborted: false,
progressPhases: [],
};
}
const downloadPolicy = browserFilePolicyReference(
"browser-presigned-download",
"save-verified-artifact",
);
const policies = new BrowserFilePolicyRegistry({
profiles: [
{
reference: downloadPolicy,
download: {
strategy: "PROMPT_AND_STREAM",
mediaType: "application/octet-stream",
safeExtension: ".bin",
maxTransferBytes: 64,
maxBufferedBytes: 8,
integrity: "REQUIRED",
},
},
],
hardLimits: {
maxInspectionBytes: 64,
maxRetainedFileBytes: 64,
maxPreviewBytes: 64,
maxObjectUrlBytes: 64,
maxTransferBytes: 64,
},
});
const written: number[] = [];
const writeChunkSizes: number[] = [];
let writableClosed = false;
let writableAborted = false;
const downloads = createDownloadDeliveryAdapter({
host: {
handoff() {
throw new TypeError(
"Writable delivery must not use browser handoff.",
);
},
},
policies,
hardMaxObjectUrlBytes: 64,
hardMaxTransferBytes: 64,
browserManagedCapabilities: {
resolve() {
throw new TypeError(
"Browser-managed capability is not used.",
);
},
},
openAuthorizedSource:
executor.downloadSources.open.bind(
executor.downloadSources,
),
showSaveFilePicker: async () => ({
async createWritable() {
return new WritableStream<Uint8Array>({
write(chunk) {
writeChunkSizes.push(chunk.byteLength);
written.push(...chunk);
},
close() {
writableClosed = true;
},
abort() {
writableAborted = true;
},
});
},
}),
createTransferId: () => "transfer:browser-presigned",
progressMinIntervalMs: 0,
userActivation: { isActive: true },
});
const progressPhases: string[] = [];
const capabilityKeys = Object.keys(issued.value).sort();
const capabilityBindingKeys = Object.keys(
issued.value.binding,
).sort();
const delivery = await downloads.deliver({
policy: downloadPolicy,
source: {
kind: "AUTHORIZED_STREAM_RESOURCE",
resourceId,
capability: issued.value,
},
suggestedFileName: "browser-artifact.bin",
signal,
onProgress(progress) {
progressPhases.push(progress.phase);
},
});
downloads.dispose();
vault.dispose();
return {
issued: {
ok: true as const,
exposedHref: "href" in issued.value,
exposedRequestHeaders: "requestHeaders" in issued.value,
},
delivery,
capabilityKeys,
capabilityBindingKeys,
written,
writeChunkSizes,
writableClosed,
writableAborted,
progressPhases,
};
},
{ resourceId: RESOURCE_ID },
);
expect(bffRequests).toEqual([
{
method: "GET",
binding: {
kind: "DOWNLOAD",
resourceId: RESOURCE_ID,
},
},
]);
expect(objectRequests).toEqual([
{
url: OBJECT_HREF,
method: "GET",
accept: "application/octet-stream",
cookie: undefined,
},
]);
expect(result.issued).toEqual({
ok: true,
exposedHref: false,
exposedRequestHeaders: false,
});
expect(result.capabilityKeys).toEqual([
"binding",
"byteLength",
"capabilityReceipt",
"expectedSha256",
"expiresAtEpochMs",
"maxBytes",
"mediaType",
"method",
]);
expect(result.capabilityBindingKeys).toEqual([
"kind",
"resourceId",
]);
expect(result.delivery).toEqual({
ok: true,
value: {
kind: "SAVED",
transferId: "transfer:browser-presigned",
bytesWritten: DOWNLOAD_BYTES.byteLength,
integrity: "VERIFIED",
},
});
expect(result.written).toEqual([...DOWNLOAD_BYTES]);
expect(result.writeChunkSizes).toEqual([3, 3, 3, 1]);
expect(result.writableClosed).toBe(true);
expect(result.writableAborted).toBe(false);
expect(result.progressPhases).toEqual([
"PREPARING",
"TRANSFERRING",
"TRANSFERRING",
"TRANSFERRING",
"TRANSFERRING",
"VERIFYING",
"FINALIZING",
]);
});