diff --git a/Makefile b/Makefile index 464ecf64..9c2b7bd0 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ remote-store/opentdf-remote-store-$(version).tgz: lib/opentdf-client-$(version). (cd remote-store && npm ci ../lib/opentdf-client-$(version).tgz && npm pack) web-app/opentdf-web-app-$(version).tgz: lib/opentdf-client-$(version).tgz $(shell find web-app -not -path '*/dist*' -and -not -path '*/coverage*' -and -not -path '*/node_modules*') - (cd web-app && npm ci ../lib/opentdf-client-$(version).tgz && npm pack) + (cd web-app && npm ci ../lib/opentdf-client-$(version).tgz && npm pack && npm run build) lib/opentdf-client-$(version).tgz: $(shell find lib -not -path '*/dist*' -and -not -path '*/coverage*' -and -not -path '*/node_modules*') (cd lib && npm ci --including=dev && npm pack) diff --git a/remote-store/package-lock.json b/remote-store/package-lock.json index f9843628..8663192a 100644 --- a/remote-store/package-lock.json +++ b/remote-store/package-lock.json @@ -1649,7 +1649,8 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-10yZrGA4LQBNjUX52+qLld2fTjq2OLxfEmR6kkrlLo6dpuN4p+qUI+i1ducMEcr/4fruKxfj2vMr+0Tg97oolg==", + "integrity": "sha512-1m+aZ7BjED8QSsRvCTx5cts5M761oS17CncTObWU1uMI5u7P+LGSTNu1PjKyLTZFqoY4VCSpDyOIoUlMSLBFOA==", + "license": "BSD-3-Clause-Clear", "dependencies": { "ajv": "^8.12.0", "axios": "^1.6.1", diff --git a/web-app/package-lock.json b/web-app/package-lock.json index be4c9267..55bb632a 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@opentdf/client": "file:../lib/opentdf-client-2.0.0.tgz", "clsx": "^2.0.0", - "native-file-system-adapter": "^3.0.0", + "native-file-system-adapter": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -20,7 +20,7 @@ "@rollup/plugin-inject": "^5.0.3", "@types/react": "^18.2.17", "@types/react-dom": "^18.2.7", - "@types/wicg-file-system-access": "^2020.9.6", + "@types/wicg-file-system-access": "^2023.10.5", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", "@vitejs/plugin-react": "^4.0.4", @@ -602,7 +602,8 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-10yZrGA4LQBNjUX52+qLld2fTjq2OLxfEmR6kkrlLo6dpuN4p+qUI+i1ducMEcr/4fruKxfj2vMr+0Tg97oolg==", + "integrity": "sha512-1m+aZ7BjED8QSsRvCTx5cts5M761oS17CncTObWU1uMI5u7P+LGSTNu1PjKyLTZFqoY4VCSpDyOIoUlMSLBFOA==", + "license": "BSD-3-Clause-Clear", "dependencies": { "ajv": "^8.12.0", "axios": "^1.6.1", @@ -770,9 +771,10 @@ "license": "MIT" }, "node_modules/@types/wicg-file-system-access": { - "version": "2020.9.6", - "dev": true, - "license": "MIT" + "version": "2023.10.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz", + "integrity": "sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==", + "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.2.1", @@ -2563,7 +2565,9 @@ } }, "node_modules/native-file-system-adapter": { - "version": "3.0.0", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/native-file-system-adapter/-/native-file-system-adapter-3.0.1.tgz", + "integrity": "sha512-ocuhsYk2SY0906LPc3QIMW+rCV3MdhqGiy7wV5Bf0e8/5TsMjDdyIwhNiVPiKxzTJLDrLT6h8BoV9ERfJscKhw==", "funding": [ { "type": "github", @@ -2574,7 +2578,6 @@ "url": "https://paypal.me/jimmywarting" } ], - "license": "MIT", "engines": { "node": ">=14.8.0" }, @@ -4097,7 +4100,7 @@ }, "@opentdf/client": { "version": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-10yZrGA4LQBNjUX52+qLld2fTjq2OLxfEmR6kkrlLo6dpuN4p+qUI+i1ducMEcr/4fruKxfj2vMr+0Tg97oolg==", + "integrity": "sha512-1m+aZ7BjED8QSsRvCTx5cts5M761oS17CncTObWU1uMI5u7P+LGSTNu1PjKyLTZFqoY4VCSpDyOIoUlMSLBFOA==", "requires": { "ajv": "^8.12.0", "axios": "^1.6.1", @@ -4218,7 +4221,9 @@ "dev": true }, "@types/wicg-file-system-access": { - "version": "2020.9.6", + "version": "2023.10.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.5.tgz", + "integrity": "sha512-e9kZO9kCdLqT2h9Tw38oGv9UNzBBWaR1MzuAavxPcsV/7FJ3tWbU6RI3uB+yKIDPGLkGVbplS52ub0AcRLvrhA==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -5286,7 +5291,9 @@ "dev": true }, "native-file-system-adapter": { - "version": "3.0.0", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/native-file-system-adapter/-/native-file-system-adapter-3.0.1.tgz", + "integrity": "sha512-ocuhsYk2SY0906LPc3QIMW+rCV3MdhqGiy7wV5Bf0e8/5TsMjDdyIwhNiVPiKxzTJLDrLT6h8BoV9ERfJscKhw==", "requires": { "fetch-blob": "^3.2.0" } diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx index 97498af4..168011fc 100644 --- a/web-app/src/App.tsx +++ b/web-app/src/App.tsx @@ -64,19 +64,22 @@ async function getNewFileHandle( type Containers = 'html' | 'tdf' | 'nano'; type CurrentDataController = AbortController | undefined; -type FileInputSource = { file: File }; +type FileInputSource = { + type: 'file'; + file: File; +}; type UrlInputSource = { + type: 'url'; url: URL; }; -type RandomType = 'bytes'; type RandomInputSource = { - type: RandomType; + type: 'bytes'; length: number; }; -type InputSource = FileInputSource | UrlInputSource | RandomInputSource | undefined; -type SinkType = 'file' | 'fsapi' | 'none'; +type InputSource = FileInputSource | UrlInputSource | RandomInputSource; +type SinkType = 'file' | 'fsapi' | 'memory' | 'none'; function fileNameFor(inputSource: InputSource) { if (!inputSource) { @@ -204,7 +207,7 @@ function App() { const [decryptContainerType, setDecryptContainerType] = useState('tdf'); const [downloadState, setDownloadState] = useState(); const [encryptContainerType, setEncryptContainerType] = useState('tdf'); - const [inputSource, setInputSource] = useState(); + const [inputSources, setInputSources] = useState([]); const [sinkType, setSinkType] = useState('file'); const [streamController, setStreamController] = useState(); @@ -229,26 +232,27 @@ function App() { const setFileHandler = (event: ChangeEvent) => { const target = event.target as HTMLInputElement; if (target.files?.length) { - const [file] = target.files; - setInputSource({ file }); + const fileArray = Array.from(target.files); + const srcs = fileArray.map((file): FileInputSource => ({ type: 'file', file })); + setInputSources(srcs); } else { - setInputSource(undefined); + setInputSources([]); } }; const setRandomHandler = (event: ChangeEvent) => { const target = event.target as HTMLInputElement; if (target.value && target.validity.valid) { - setInputSource({ type: 'bytes', length: parseInt(target.value) }); + setInputSources([{ type: 'bytes', length: parseInt(target.value) }]); } else { - setInputSource(undefined); + setInputSources([]); } }; const setUrlHandler = (event: ChangeEvent) => { const target = event.target as HTMLInputElement; if (target.value && target.validity.valid) { - setInputSource({ url: new URL(target.value) }); + setInputSources([{ type: 'url', url: new URL(target.value) }]); } else { - setInputSource(undefined); + setInputSources([]); } }; @@ -316,7 +320,7 @@ function App() { }; const handleEncrypt = async () => { - if (!inputSource) { + if (!inputSources.length) { console.warn('No input source selected'); return false; } @@ -325,189 +329,202 @@ function App() { console.warn('PLEASE LOG IN'); return false; } - const inputFileName = fileNameFor(inputSource); - console.log(`Encrypting [${inputFileName}] as ${encryptContainerType} to ${sinkType}`); - switch (encryptContainerType) { - case 'nano': { - if ('url' in inputSource) { - throw new Error('Unsupported : fetch the url I guess?'); - } - const plainText = - 'file' in inputSource - ? await inputSource.file.arrayBuffer() - : randomArrayBuffer(inputSource); - const nanoClient = new NanoTDFClient({ - authProvider: oidcClient, - kasEndpoint: c.kas, - dpopKeys: oidcClient.getSigningKey(), - }); - setDownloadState('Encrypting...'); - switch (sinkType) { - case 'file': - { - const cipherText = await nanoClient.encrypt(plainText); - saver(new Blob([cipherText]), `${inputFileName}.ntdf`); - } - break; - case 'fsapi': - { - const file = await getNewFileHandle('ntdf', `${inputFileName}.ntdf`); - const cipherText = await nanoClient.encrypt(plainText); - const writable = await file.createWritable(); - try { - await writable.write(cipherText); - setDownloadState('Encrypt Complete'); - } catch (e) { - setDownloadState(`Encrypt Failed: ${e}`); - } finally { - await writable.close(); - } - } - break; - case 'none': - break; - } - break; - } - case 'html': { - const client = new TDF3Client({ - authProvider: oidcClient, - dpopKeys: oidcClient.getSigningKey(), - kasEndpoint: c.kas, - readerUrl: c.reader, - }); - let source: ReadableStream, size: number; - const sc = new AbortController(); - setStreamController(sc); - if ('file' in inputSource) { - size = inputSource.file.size; - source = inputSource.file.stream() as unknown as ReadableStream; - } else if ('type' in inputSource) { - size = inputSource.length; - source = randomStream(inputSource); - } else { - // NOTE: Attaching the signal to the pipeline (in pipeTo, below) - // is insufficient (at least in Chrome) to abort the fetch itself. - // So aborting a sink in a pipeline does *NOT* cancel its sources - const fr = await fetch(inputSource.url, { signal: sc.signal }); - if (!fr.ok) { - throw Error( - `Error on fetch [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); - } - if (!fr.body) { - throw Error( - `Failed to fetch input [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); + for (const inputSource of inputSources) { + + const inputFileName = fileNameFor(inputSource); + console.log(`Encrypting [${inputFileName}] as ${encryptContainerType} to ${sinkType}`); + switch (encryptContainerType) { + case 'nano': { + if ('url' in inputSource) { + throw new Error('Unsupported : fetch the url I guess?'); } - size = parseInt(fr.headers.get('Content-Length') || '-1'); - source = fr.body; - } - try { - const downloadName = `${inputFileName}.tdf.html`; - let f; - if (sinkType == 'fsapi') { - f = await getNewFileHandle('html', downloadName); - } - const progressTransformers = makeProgressPair(size, 'Encrypt'); - const cipherText = await client.encrypt({ - source: source.pipeThrough(progressTransformers.reader), - offline: true, - asHtml: true, + const plainText = + 'file' in inputSource + ? await inputSource.file.arrayBuffer() + : randomArrayBuffer(inputSource); + const nanoClient = new NanoTDFClient({ + authProvider: oidcClient, + kasEndpoint: c.kas, + dpopKeys: oidcClient.getSigningKey(), }); - cipherText.stream = cipherText.stream.pipeThrough(progressTransformers.writer); + setDownloadState('Encrypting...'); switch (sinkType) { case 'file': - await cipherText.toFile(downloadName, { signal: sc.signal }); + { + const cipherText = await nanoClient.encrypt(plainText); + saver(new Blob([cipherText]), `${inputFileName}.ntdf`); + } break; case 'fsapi': - if (!f) { - throw new Error(); + { + const file = await getNewFileHandle('ntdf', `${inputFileName}.ntdf`); + const cipherText = await nanoClient.encrypt(plainText); + const writable = await file.createWritable(); + try { + await writable.write(cipherText); + setDownloadState('Encrypt Complete'); + } catch (e) { + setDownloadState(`Encrypt Failed: ${e}`); + } finally { + await writable.close(); + } } - const writable = await f.createWritable(); - await cipherText.stream.pipeTo(writable, { signal: sc.signal }); break; case 'none': - await cipherText.stream.pipeTo(drain(), { signal: sc.signal }); break; } - } catch (e) { - setDownloadState(`Encrypt Failed: ${e}`); - console.error('Encrypt Failed', e); + break; } - setStreamController(undefined); - break; - } - case 'tdf': { - const client = new TDF3Client({ - authProvider: oidcClient, - dpopKeys: oidcClient.getSigningKey(), - kasEndpoint: c.kas, - }); - const sc = new AbortController(); - setStreamController(sc); - let source: ReadableStream, size: number; - if ('file' in inputSource) { - size = inputSource.file.size; - source = inputSource.file.stream() as unknown as ReadableStream; - } else if ('type' in inputSource) { - size = inputSource.length; - source = randomStream(inputSource); - } else { - const fr = await fetch(inputSource.url, { signal: sc.signal }); - if (!fr.ok) { - throw Error( - `Error on fetch [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); + case 'html': { + const client = new TDF3Client({ + authProvider: oidcClient, + dpopKeys: oidcClient.getSigningKey(), + kasEndpoint: c.kas, + readerUrl: c.reader, + }); + let source: ReadableStream, size: number; + const sc = new AbortController(); + setStreamController(sc); + switch (inputSource.type) { + case 'file': + size = inputSource.file.size; + source = inputSource.file.stream() as unknown as ReadableStream; + break; + + case 'bytes': + size = inputSource.length; + source = randomStream(inputSource); + break; + + case 'url': + // NOTE: Attaching the signal to the pipeline (in pipeTo, below) + // is insufficient (at least in Chrome) to abort the fetch itself. + // So aborting a sink in a pipeline does *NOT* cancel its sources + const fr = await fetch(inputSource.url, { signal: sc.signal }); + if (!fr.ok) { + throw Error( + `Error on fetch [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` + ); + } + if (!fr.body) { + throw Error( + `Failed to fetch input [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` + ); + } + size = parseInt(fr.headers.get('Content-Length') || '-1'); + source = fr.body; + break; } - if (!fr.body) { - throw Error( - `Failed to fetch input [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); + try { + const downloadName = `${inputFileName}.tdf.html`; + let f; + if (sinkType == 'fsapi') { + f = await getNewFileHandle('html', downloadName); + } + const progressTransformers = makeProgressPair(size, 'Encrypt'); + const cipherText = await client.encrypt({ + source: source.pipeThrough(progressTransformers.reader), + offline: true, + asHtml: true, + }); + cipherText.stream = cipherText.stream.pipeThrough(progressTransformers.writer); + switch (sinkType) { + case 'file': + await cipherText.toFile(downloadName, { signal: sc.signal }); + break; + case 'fsapi': + if (!f) { + throw new Error(); + } + const writable = await f.createWritable(); + await cipherText.stream.pipeTo(writable, { signal: sc.signal }); + break; + case 'none': + await cipherText.stream.pipeTo(drain(), { signal: sc.signal }); + break; + } + } catch (e) { + setDownloadState(`Encrypt Failed: ${e}`); + console.error('Encrypt Failed', e); } - size = parseInt(fr.headers.get('Content-Length') || '-1'); - source = fr.body; + setStreamController(undefined); + break; } - try { - let f; - const downloadName = `${inputFileName}.tdf`; - if (sinkType === 'fsapi') { - f = await getNewFileHandle('tdf', downloadName); - } - const progressTransformers = makeProgressPair(size, 'Encrypt'); - const cipherText = await client.encrypt({ - source: source.pipeThrough(progressTransformers.reader), - offline: true, + case 'tdf': { + const client = new TDF3Client({ + authProvider: oidcClient, + dpopKeys: oidcClient.getSigningKey(), + kasEndpoint: c.kas, }); - cipherText.stream = cipherText.stream.pipeThrough(progressTransformers.writer); - switch (sinkType) { + const sc = new AbortController(); + setStreamController(sc); + let source: ReadableStream, size: number; + switch (inputSource.type) { case 'file': - await cipherText.toFile(downloadName, { signal: sc.signal }); + size = inputSource.file.size; + source = inputSource.file.stream() as unknown as ReadableStream; break; - case 'fsapi': - if (!f) { - throw new Error(); - } - const writable = await f.createWritable(); - await cipherText.stream.pipeTo(writable, { signal: sc.signal }); + case 'bytes': + size = inputSource.length; + source = randomStream(inputSource); break; - case 'none': - await cipherText.stream.pipeTo(drain(), { signal: sc.signal }); + case 'url': + const fr = await fetch(inputSource.url, { signal: sc.signal }); + if (!fr.ok) { + throw Error( + `Error on fetch [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` + ); + } + if (!fr.body) { + throw Error( + `Failed to fetch input [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` + ); + } + size = parseInt(fr.headers.get('Content-Length') || '-1'); + source = fr.body; break; } - } catch (e) { - setDownloadState(`Encrypt Failed: ${e}`); - console.error('Encrypt Failed', e); + try { + let f; + const downloadName = `${inputFileName}.tdf`; + if (sinkType === 'fsapi') { + f = await getNewFileHandle('tdf', downloadName); + } + const progressTransformers = makeProgressPair(size, 'Encrypt'); + const cipherText = await client.encrypt({ + source: source.pipeThrough(progressTransformers.reader), + offline: true, + }); + cipherText.stream = cipherText.stream.pipeThrough(progressTransformers.writer); + switch (sinkType) { + case 'file': + await cipherText.toFile(downloadName, { signal: sc.signal }); + break; + case 'fsapi': + if (!f) { + throw new Error(); + } + const writable = await f.createWritable(); + await cipherText.stream.pipeTo(writable, { signal: sc.signal }); + break; + case 'none': + await cipherText.stream.pipeTo(drain(), { signal: sc.signal }); + break; + } + } catch (e) { + setDownloadState(`Encrypt Failed: ${e}`); + console.error('Encrypt Failed', e); + } + setStreamController(undefined); + break; } - setStreamController(undefined); - break; } } return true; }; const handleDecrypt = async () => { - if (!inputSource) { + if (!inputSources.length) { console.log('PLEASE SELECT FILE'); return false; } @@ -515,188 +532,118 @@ function App() { console.error('decrypt while logged out doesnt work'); return false; } - const dfn = decryptedFileName(fileNameFor(inputSource)); - console.log( - `Decrypting ${decryptContainerType} ${JSON.stringify(inputSource)} to ${sinkType} ${dfn}` - ); - let f; - if (sinkType === 'fsapi') { - f = await getNewFileHandle(decryptedFileExtension(fileNameFor(inputSource)), dfn); - } - switch (decryptContainerType) { - case 'tdf': { - const client = new TDF3Client({ - authProvider: oidcClient, - dpopKeys: oidcClient.getSigningKey(), - kasEndpoint: c.kas, - }); - try { - const sc = new AbortController(); - setStreamController(sc); - let source: DecryptSource; - let size: number; - if ('file' in inputSource) { - size = inputSource.file.size; - source = { type: 'file-browser', location: inputSource.file }; - } else if ('type' in inputSource) { - size = inputSource.length; - source = { type: 'chunker', location: randomChunker(inputSource) }; - } else { - const hr = await fetch(inputSource.url, { method: 'HEAD' }); - size = parseInt(hr.headers.get('Content-Length') || '-1'); - source = { type: 'remote', location: inputSource.url.toString() }; - } - const progressTransformers = makeProgressPair(size, 'Decrypt'); - // XXX chunker doesn't have an equivalent 'stream' interaface - // so we kinda fake it with percentages by tracking output, which should - // strictly be smaller than the input file. - const plainText = await client.decrypt({ source }); - plainText.stream = plainText.stream - .pipeThrough(progressTransformers.reader) - .pipeThrough(progressTransformers.writer); - switch (sinkType) { - case 'file': - await plainText.toFile(dfn, { signal: sc.signal }); - break; - case 'fsapi': - if (!f) { - throw new Error(); - } - const writable = await f.createWritable(); - await plainText.stream.pipeTo(writable, { signal: sc.signal }); - break; - case 'none': - await plainText.stream.pipeTo(drain(), { signal: sc.signal }); - break; - } - } catch (e) { - console.error('Decrypt Failed', e); - setDownloadState(`Decrypt Failed: ${e}`); - } - setStreamController(undefined); - break; + for (const inputSource of inputSources) { + + const dfn = decryptedFileName(fileNameFor(inputSource)); + console.log( + `Decrypting ${decryptContainerType} ${JSON.stringify(inputSource)} to ${sinkType} ${dfn}` + ); + let f; + if (sinkType === 'fsapi') { + f = await getNewFileHandle(decryptedFileExtension(fileNameFor(inputSource)), dfn); } - case 'nano': { - if ('url' in inputSource) { - throw new Error('Unsupported : fetch the url I guess?'); + switch (decryptContainerType) { + case 'tdf': { + const client = new TDF3Client({ + authProvider: oidcClient, + dpopKeys: oidcClient.getSigningKey(), + kasEndpoint: c.kas, + }); + try { + const sc = new AbortController(); + setStreamController(sc); + let source: DecryptSource; + let size: number; + switch (inputSource.type) { + case 'file': + size = inputSource.file.size; + source = { type: 'file-browser', location: inputSource.file }; + break; + case 'bytes': + size = inputSource.length; + source = { type: 'chunker', location: randomChunker(inputSource) }; + break; + case 'url': + const hr = await fetch(inputSource.url, { method: 'HEAD' }); + size = parseInt(hr.headers.get('Content-Length') || '-1'); + source = { type: 'remote', location: inputSource.url.toString() }; + break; + } + const progressTransformers = makeProgressPair(size, 'Decrypt'); + // XXX chunker doesn't have an equivalent 'stream' interaface + // so we kinda fake it with percentages by tracking output, which should + // strictly be smaller than the input file. + const plainText = await client.decrypt({ source }); + plainText.stream = plainText.stream + .pipeThrough(progressTransformers.reader) + .pipeThrough(progressTransformers.writer); + switch (sinkType) { + case 'file': + await plainText.toFile(dfn, { signal: sc.signal }); + break; + case 'fsapi': + if (!f) { + throw new Error(); + } + const writable = await f.createWritable(); + await plainText.stream.pipeTo(writable, { signal: sc.signal }); + break; + case 'none': + await plainText.stream.pipeTo(drain(), { signal: sc.signal }); + break; + } + } catch (e) { + console.error('Decrypt Failed', e); + setDownloadState(`Decrypt Failed: ${e}`); + } + setStreamController(undefined); + break; } - const nanoClient = new NanoTDFClient({ - authProvider: oidcClient, - kasEndpoint: c.kas, - dpopKeys: oidcClient.getSigningKey(), - }); - try { - const cipherText = - 'file' in inputSource - ? await inputSource.file.arrayBuffer() - : randomArrayBuffer(inputSource); - const plainText = await nanoClient.decrypt(cipherText); - switch (sinkType) { - case 'file': - saver(new Blob([plainText]), dfn); - break; - case 'fsapi': - if (!f) { - throw new Error(); - } - const writable = await f.createWritable(); - try { - await writable.write(plainText); - setDownloadState('Decrypt Complete'); - } finally { - await writable.close(); - } - break; - case 'none': - break; + case 'nano': { + if ('url' in inputSource) { + throw new Error('Unsupported : fetch the url I guess?'); + } + const nanoClient = new NanoTDFClient({ + authProvider: oidcClient, + kasEndpoint: c.kas, + dpopKeys: oidcClient.getSigningKey(), + }); + try { + const cipherText = + 'file' in inputSource + ? await inputSource.file.arrayBuffer() + : randomArrayBuffer(inputSource); + const plainText = await nanoClient.decrypt(cipherText); + switch (sinkType) { + case 'file': + saver(new Blob([plainText]), dfn); + break; + case 'fsapi': + if (!f) { + throw new Error(); + } + const writable = await f.createWritable(); + try { + await writable.write(plainText); + setDownloadState('Decrypt Complete'); + } finally { + await writable.close(); + } + break; + case 'none': + break; + } + } catch (e) { + console.error('Decrypt Failed', e); + setDownloadState(`Decrypt Failed: ${e}`); } - } catch (e) { - console.error('Decrypt Failed', e); - setDownloadState(`Decrypt Failed: ${e}`); + break; } - break; } } return false; }; - const handleScan = async () => { - const searchTerm = 'service workers'; - // Chars to show either side of the result in the match - const contextBefore = 30; - const contextAfter = 30; - const caseInsensitive = true; - - if (!inputSource) { - console.warn('PLEASE SELECT FILE ∨ URL'); - return false; - } - let source; - if ('file' in inputSource) { - source = inputSource.file.stream() as unknown as ReadableStream; - } else if ('type' in inputSource) { - throw new Error('Unimplemented'); - } else { - const sc = new AbortController(); - setStreamController(sc); - const fr = await fetch(inputSource.url, { cache: 'no-store', signal: sc.signal }); - console.log(`Received headers ${fr.headers}`); - if (!fr.ok) { - throw Error( - `Error on fetch [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); - } - if (!fr.body) { - throw Error( - `Failed to fetch input [${inputSource.url}]: ${fr.status} code received; [${fr.statusText}]` - ); - } - source = fr.body; - } - const reader = source.getReader(); - - const decoder = new TextDecoder(); - const toMatch = caseInsensitive ? searchTerm.toLowerCase() : searchTerm; - const bufferSize = Math.max(toMatch.length - 1, contextBefore); - - let bytesReceived = 0; - let buffer = ''; - let matchFoundAt = -1; - - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) { - console.log('Failed to find match'); - return; - } - bytesReceived += chunk.length; - console.log(`Received ${bytesReceived.toLocaleString()} bytes of data so far`); - buffer += decoder.decode(chunk, { stream: true }); - - // already found match & just context-gathering? - if (matchFoundAt === -1) { - matchFoundAt = (caseInsensitive ? buffer.toLowerCase() : buffer).indexOf(toMatch); - } - - if (matchFoundAt === -1) { - buffer = buffer.slice(-bufferSize); - } else if (buffer.slice(matchFoundAt + toMatch.length).length >= contextAfter) { - console.log("Here's the match:"); - console.log( - buffer.slice( - Math.max(0, matchFoundAt - contextBefore), - matchFoundAt + toMatch.length + contextAfter - ) - ); - console.log('Cancelling fetch'); - reader.cancel(); - return; - } else { - console.log('Found match, but need more context…'); - } - } - }; - const SessionInfo = authState.sessionState == 'start' ? ( -
diff --git a/web-app/src/config.ts b/web-app/src/config.ts index bfb01765..7dc2f709 100644 --- a/web-app/src/config.ts +++ b/web-app/src/config.ts @@ -14,10 +14,10 @@ function cfg(): TDFConfig { if (!VITE_TDF_CFG) { return { oidc: { - host: 'http://localhost:65432/auth/realms/opentdf', + host: 'http://localhost:65432/auth/realms/tdf', clientId: 'browsertest', }, - kas: 'http://localhost:65432/kas', + kas: 'http://localhost:65432/api/kas', reader: 'https://secure.virtru.com/start?htmlProtocol=1', }; } diff --git a/web-app/src/session.ts b/web-app/src/session.ts index f5aff0ff..0efe46e8 100644 --- a/web-app/src/session.ts +++ b/web-app/src/session.ts @@ -373,7 +373,7 @@ export class OidcClient implements AuthProvider { ); headers.DPoP = await dpopFn( signingKey, - 'http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token', + config.token_endpoint, 'POST' ); const response = await fetch(config.token_endpoint, {