190 lines
6.8 KiB
TypeScript
190 lines
6.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { test } from "node:test";
|
|
import {
|
|
FILEDROP_CONTRACT_VERSION,
|
|
FILEDROP_WEBHOOK_SIGNATURE_HEADER,
|
|
FILEDROP_WEBHOOK_TIMESTAMP_HEADER,
|
|
createLocalFileDropStorage,
|
|
createSignedFileDropWebhook,
|
|
developmentRecipientPublicKey,
|
|
fileDropEventSchema,
|
|
fileDropUploadCommitSchema,
|
|
getActiveDropboxFromEnvironment,
|
|
parseFileDropWebhook,
|
|
receiveCiphertextIntakeFromFormData,
|
|
verifyFileDropWebhookSignature,
|
|
type FileDropStorage
|
|
} from "../dist/index.js";
|
|
|
|
const fixture = JSON.parse(await readFile(new URL("./fixtures/filedrop-v1.json", import.meta.url), "utf8")) as {
|
|
ciphertext: string;
|
|
contactEmail: string;
|
|
contactName: string;
|
|
dropboxId: string;
|
|
envelope: Record<string, unknown>;
|
|
languagePair: string;
|
|
now: string;
|
|
requestedDeadline: string;
|
|
routingLabel: string;
|
|
serviceType: string;
|
|
uploadId: string;
|
|
webhookId: string;
|
|
};
|
|
|
|
test("discovery is generic and exposes key lifecycle state", async () => {
|
|
const discovery = await getActiveDropboxFromEnvironment({
|
|
FILEDROP_DROPBOX_ID: fixture.dropboxId,
|
|
FILEDROP_DROPBOX_LABEL: "Fixture secure drop",
|
|
FILEDROP_RECIPIENT_KEY_ROTATED_AT: fixture.now,
|
|
FILEDROP_RECIPIENT_KEY_STATUS: "rotating",
|
|
FILEDROP_RECIPIENT_PUBLIC_KEY_JWK: JSON.stringify(developmentRecipientPublicKey)
|
|
});
|
|
|
|
assert.equal(discovery.contractVersion, FILEDROP_CONTRACT_VERSION);
|
|
assert.equal(discovery.dropboxId, fixture.dropboxId);
|
|
assert.equal(discovery.label, "Fixture secure drop");
|
|
assert.equal(discovery.status, "rotating");
|
|
assert.equal(discovery.key.status, "rotating");
|
|
assert.equal(discovery.key.rotatedAt, fixture.now);
|
|
assert.equal(discovery.source, "configured");
|
|
});
|
|
|
|
test("discovery defaults do not contain host-specific branding", async () => {
|
|
const discovery = await getActiveDropboxFromEnvironment({});
|
|
assert.equal(discovery.label, "Secure file drop");
|
|
assert.equal(discovery.dropboxId, "default");
|
|
assert.equal(discovery.source, "development");
|
|
});
|
|
|
|
test("receiveCiphertextIntakeFromFormData validates filedrop.v1 and supports custom storage", async () => {
|
|
const saved: Array<{ id: string; manifest: Record<string, unknown>; text: string }> = [];
|
|
const storage: FileDropStorage = {
|
|
async saveCiphertextUpload(input) {
|
|
saved.push({
|
|
id: input.id,
|
|
manifest: input.manifest,
|
|
text: Buffer.from(await input.ciphertext.arrayBuffer()).toString("utf8")
|
|
});
|
|
return {
|
|
driver: "memory",
|
|
manifestKey: `memory/${input.id}.json`,
|
|
storageKey: `memory/${input.id}.enc`
|
|
};
|
|
}
|
|
};
|
|
|
|
const received = await receiveCiphertextIntakeFromFormData(createFixtureFormData(), {}, {
|
|
id: fixture.uploadId,
|
|
now: new Date(fixture.now),
|
|
storage
|
|
});
|
|
|
|
assert.equal(received.id, fixture.uploadId);
|
|
assert.equal(received.storageDriver, "memory");
|
|
assert.equal(received.storageKey, `memory/${fixture.uploadId}.enc`);
|
|
assert.equal(received.commit.contractVersion, FILEDROP_CONTRACT_VERSION);
|
|
assert.equal(received.commit.sender.contactEmail, fixture.contactEmail);
|
|
assert.equal(received.commit.routing.dropboxId, fixture.dropboxId);
|
|
assert.equal(received.event.eventType, "filedrop.upload.received");
|
|
assert.equal(received.event.storage.driver, "memory");
|
|
assert.equal(saved[0]?.text, fixture.ciphertext);
|
|
|
|
assert.doesNotThrow(() => fileDropUploadCommitSchema.parse(received.commit));
|
|
assert.doesNotThrow(() => fileDropEventSchema.parse(received.event));
|
|
});
|
|
|
|
test("local storage adapter writes ciphertext and manifest", async () => {
|
|
const storageDir = await mkdtemp(path.join(tmpdir(), "filedrop-core-"));
|
|
try {
|
|
const received = await receiveCiphertextIntakeFromFormData(createFixtureFormData(), {}, {
|
|
id: fixture.uploadId,
|
|
now: new Date(fixture.now),
|
|
storage: createLocalFileDropStorage({ storageDir })
|
|
});
|
|
|
|
const ciphertext = await readFile(path.join(storageDir, received.storageKey), "utf8");
|
|
const manifest = JSON.parse(await readFile(path.join(storageDir, received.manifestKey), "utf8")) as {
|
|
contractVersion: string;
|
|
commit: { sender: { contactEmail: string } };
|
|
};
|
|
|
|
assert.equal(ciphertext, fixture.ciphertext);
|
|
assert.equal(manifest.contractVersion, FILEDROP_CONTRACT_VERSION);
|
|
assert.equal(manifest.commit.sender.contactEmail, fixture.contactEmail);
|
|
} finally {
|
|
await rm(storageDir, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
test("webhook signatures verify and reject tampered payloads", async () => {
|
|
const received = await receiveCiphertextIntakeFromFormData(createFixtureFormData(), {}, {
|
|
id: fixture.uploadId,
|
|
now: new Date(fixture.now),
|
|
storage: memoryStorage()
|
|
});
|
|
const signed = createSignedFileDropWebhook(received.event, "test-secret", {
|
|
now: new Date(fixture.now),
|
|
webhookId: fixture.webhookId
|
|
});
|
|
|
|
assert.equal(
|
|
verifyFileDropWebhookSignature({
|
|
body: signed.body,
|
|
now: new Date(fixture.now),
|
|
secret: "test-secret",
|
|
signatureHeader: signed.headers[FILEDROP_WEBHOOK_SIGNATURE_HEADER],
|
|
timestampHeader: signed.headers[FILEDROP_WEBHOOK_TIMESTAMP_HEADER]
|
|
}),
|
|
true
|
|
);
|
|
assert.equal(
|
|
verifyFileDropWebhookSignature({
|
|
body: `${signed.body} `,
|
|
now: new Date(fixture.now),
|
|
secret: "test-secret",
|
|
signatureHeader: signed.headers[FILEDROP_WEBHOOK_SIGNATURE_HEADER],
|
|
timestampHeader: signed.headers[FILEDROP_WEBHOOK_TIMESTAMP_HEADER]
|
|
}),
|
|
false
|
|
);
|
|
|
|
const parsed = parseFileDropWebhook({
|
|
body: signed.body,
|
|
now: new Date(fixture.now),
|
|
secret: "test-secret",
|
|
signatureHeader: signed.headers[FILEDROP_WEBHOOK_SIGNATURE_HEADER],
|
|
timestampHeader: signed.headers[FILEDROP_WEBHOOK_TIMESTAMP_HEADER]
|
|
});
|
|
assert.equal(parsed.webhookId, fixture.webhookId);
|
|
assert.equal(parsed.event.upload.id, fixture.uploadId);
|
|
});
|
|
|
|
function createFixtureFormData() {
|
|
const formData = new FormData();
|
|
formData.append("ciphertext", new Blob([fixture.ciphertext], { type: "application/octet-stream" }), "payload.enc");
|
|
formData.append("contactEmail", fixture.contactEmail);
|
|
formData.append("contactName", fixture.contactName);
|
|
formData.append("dropboxId", fixture.dropboxId);
|
|
formData.append("envelope", JSON.stringify(fixture.envelope));
|
|
formData.append("languagePair", fixture.languagePair);
|
|
formData.append("requestedDeadline", fixture.requestedDeadline);
|
|
formData.append("routingLabel", fixture.routingLabel);
|
|
formData.append("serviceType", fixture.serviceType);
|
|
return formData;
|
|
}
|
|
|
|
function memoryStorage(): FileDropStorage {
|
|
return {
|
|
async saveCiphertextUpload(input) {
|
|
return {
|
|
driver: "memory",
|
|
manifestKey: `memory/${input.id}.json`,
|
|
storageKey: `memory/${input.id}.enc`
|
|
};
|
|
}
|
|
};
|
|
}
|