| 1 | import test from "node:test" ; |
| 2 | import assert from "node:assert/strict" ; |
| 3 | import fs from "node:fs" ; |
| 4 | import vm from "node:vm" ; |
| 5 | |
| 6 | const source = fs.readFileSync("tinyclient/file-catalog.js" , "utf8" ); |
| 7 | const pubkey = "a" .repeat(64); |
| 8 | const hash = "b" .repeat(64); |
| 9 | |
| 10 | function setup({path = "/r/test/files" , actor = pubkey} = {}) { |
| 11 | const values = new Map(); |
| 12 | const rows = []; |
| 13 | const document = { |
| 14 | querySelector(selector) { |
| 15 | if (selector === "[data-pubkey]" ) return {getAttribute: () => actor}; |
| 16 | if (selector === "#file-library" ) return {querySelectorAll: () => rows}; |
| 17 | return null; |
| 18 | }, |
| 19 | createElement() { return {textContent: "" , append() {}}; } |
| 20 | }; |
| 21 | const location = {href: "https://relay.test" + path, origin: "https://relay.test" , pathname: path}; |
| 22 | const history = {replaceState(_state, _title, value) { location.href = "https://relay.test" + value; location.pathname = new URL(location.href).pathname; location.hash = new URL(location.href).hash; }}; |
| 23 | const context = { |
| 24 | location, history, document, |
| 25 | localStorage: {getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value)}, |
| 26 | URL, URLSearchParams, Date, Number, String, Array, Boolean, Object, RegExp, JSON, |
| 27 | globalThis: null, tiny: {localPath: value => "/r/test" + value} |
| 28 | }; |
| 29 | context.globalThis = context; |
| 30 | vm.runInNewContext(source, context); |
| 31 | return {context, values, rows}; |
| 32 | } |
| 33 | |
| 34 | const link = () => `https://relay.test/r/test/file?hash=${hash}#key=${"c".repeat(64)}&iv=${"d".repeat(16)}&name=video.mp4&type=video%2Fmp4` ; |
| 35 | |
| 36 | test("saves encrypted metadata per tenant and account and restores it after reload" , () => { |
| 37 | const first = setup({path: "/r/test/file?hash=" + hash}); |
| 38 | assert.equal(first.context.tiny.files.catalog.save({hash, link: link(), name: "video.mp4" , type: "video/mp4" , size: 104857600}, pubkey), true); |
| 39 | assert.equal(first.context.tiny.files.catalog.get(hash, pubkey).name, "video.mp4" ); |
| 40 | assert.equal(first.context.tiny.files.catalog.get(hash, "e" .repeat(64)), null); |
| 41 | assert.equal(first.context.tiny.files.catalog.restore(pubkey).name, "video.mp4" ); |
| 42 | assert.match(first.context.location.href, /#key=/); |
| 43 | }); |
| 44 | |
| 45 | test("rejects unsafe links, names and cross-tenant links" , () => { |
| 46 | const {context} = setup(); |
| 47 | assert.equal(context.tiny.files.catalog.save({hash, link: "https://evil.test/r/test/file?hash=" + hash + "#key=x" , name: "x" }, pubkey), false); |
| 48 | assert.equal(context.tiny.files.catalog.save({hash, link: link(), name: "../secret" }, pubkey), false); |
| 49 | assert.equal(context.tiny.files.catalog.save({hash, link: link().replace("/r/test/" , "/r/other/" ), name: "x" }, pubkey), false); |
| 50 | }); |