From f795e2dd42a3693d5c3b2c11ee1cbc0061c2c2a4 Mon Sep 17 00:00:00 2001 From: Sampsa Penna Date: Wed, 18 Oct 2023 12:50:10 +0300 Subject: [PATCH 1/3] split signed fetch requests to a convenience function --- swift_browser_ui_frontend/src/common/api.js | 35 +++++++++++++++++++ swift_browser_ui_frontend/src/entries/main.js | 17 ++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/swift_browser_ui_frontend/src/common/api.js b/swift_browser_ui_frontend/src/common/api.js index f5381d1d3..6765e6989 100644 --- a/swift_browser_ui_frontend/src/common/api.js +++ b/swift_browser_ui_frontend/src/common/api.js @@ -542,3 +542,38 @@ export async function getUploadCryptedEndpoint( return ret.json(); } + + +// Convenience function for performing a signed fetch +export async function signedFetch( + method, + base, + path, + body, + params, + lifetime = 60, +) { + let signatureUrl = new URL(`/sign/${lifetime}`, document.location.origin); + signatureUrl.searchParams.append("path", path); + let signed = await GET(signatureUrl); + signed = await signed.json(); + + let fetchUrl = new URL(base.concat(path)); + fetchUrl.searchParams.append("valid", signed.valid); + fetchUrl.searchParams.append("signature", signed.signature); + if (params !== undefined) { + for (const param in params) { + fetchUrl.searchParams.append(param, params[param]); + } + } + + let resp = await fetch( + fetchUrl, + { + method, + body, + }, + ); + + return resp; +} diff --git a/swift_browser_ui_frontend/src/entries/main.js b/swift_browser_ui_frontend/src/entries/main.js index 082a61d9b..7ffcd955a 100644 --- a/swift_browser_ui_frontend/src/entries/main.js +++ b/swift_browser_ui_frontend/src/entries/main.js @@ -20,7 +20,7 @@ import { vControl } from "@/common/csc-ui-vue-directive"; // Project JS functions import { i18n } from "@/common/i18n"; -import { GET, getUser } from "@/common/api"; +import { getUser, signedFetch } from "@/common/api"; import { getProjects } from "@/common/api"; // Import SharingView and Request API @@ -312,17 +312,12 @@ const app = createApp({ "setUploadEndpoint", discovery.upload_endpoint, ); - let keyPath = `/cryptic/${this.active.name}/keys`; - let signatureUrl = new URL(`/sign/${60}`, document.location.origin); - signatureUrl.searchParams.append("path", keyPath); - let signed = await GET(signatureUrl); - signed = await signed.json(); - let keyURL = new URL( - discovery.upload_endpoint.concat(keyPath), + + let key = await signedFetch( + "GET", + discovery.upload_endpoint, + `/cryptic/${this.active.name}/keys`, ); - keyURL.searchParams.append("valid", signed.valid); - keyURL.searchParams.append("signature", signed.signature); - let key = await GET(keyURL); key = await key.text(); key = `-----BEGIN CRYPT4GH PUBLIC KEY-----\n${key}\n-----END CRYPT4GH PUBLIC KEY-----\n`; this.$store.commit("appendPubKey", key); From 00b0d5eb497da6de2f623349f27da065aa2b69a0 Mon Sep 17 00:00:00 2001 From: Sampsa Penna Date: Wed, 18 Oct 2023 12:51:27 +0300 Subject: [PATCH 2/3] fix shared upload regressions --- .../src/components/UploadModal.vue | 99 ++++++++++++------- .../wasm/js/crypt-post-upworker.js | 4 +- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/swift_browser_ui_frontend/src/components/UploadModal.vue b/swift_browser_ui_frontend/src/components/UploadModal.vue index 764a73fb7..8a101aa16 100644 --- a/swift_browser_ui_frontend/src/components/UploadModal.vue +++ b/swift_browser_ui_frontend/src/components/UploadModal.vue @@ -250,7 +250,7 @@ import { keyboardNavigationInsideModal, } from "@/common/keyboardNavigation"; import CUploadButton from "@/components/CUploadButton.vue"; -import { swiftDeleteObjects, getObjects } from "@/common/api"; +import { swiftDeleteObjects, getObjects, signedFetch } from "@/common/api"; import { debounce, delay } from "lodash"; import { mdiDelete } from "@mdi/js"; @@ -817,10 +817,32 @@ export default { this.beginEncryptedUpload(); } }, - beginEncryptedUpload() { - if (this.pubkey.length > 0) { + async aBeginEncryptedUpload() { + // We need the proper IDs for the other project for Vault access + let owner = ""; + let ownerName = ""; + if (this.pubkey.length > 0 && !(this.$route.params.owner)) { this.recvkeys = this.recvkeys.concat(this.pubkey); + } else if (this.$route.params.owner) { + let ids = await this.$store.state.client.projectCheckIDs( + this.$route.params.owner, + ); + owner = ids.id; + ownerName = ids.name; + } + + // Also need to get the other project's key from Vault + if (this.$route.params.owner) { + let sharedKey = await signedFetch( + "GET", + this.$store.state.uploadEndpoint, + `/cryptic/${ownerName}/keys`, + ); + sharedKey = await sharedKey.text(); + sharedKey = `-----BEGIN CRYPT4GH PUBLIC KEY-----\n${sharedKey}\n-----END CRYPT4GH PUBLIC KEY-----\n`; + this.recvkeys = this.recvkeys.concat([sharedKey]); } + // Clean up old stale upload if exists this.$store.commit("abortCurrentUpload"); this.$store.commit("eraseCurrentUpload"); @@ -835,44 +857,47 @@ export default { this.currentFolder ? this.currentFolder : this.inputFolder, this.$store.state.dropFiles.map(item => item), this.recvkeys.map(item => item), - this.$route.params.owner ? this.$route.params.owner : "", - this.$route.params.owner ? this.$route.params.owner : "", + owner, + ownerName, ); - - delay(() => { - if (this.abortReason !== undefined) { - if (this.abortReason - .match("Could not create or access the container.")) { - this.uploadError = this.currentFolder ? - this.$t("message.upload.accessFail") - : this.$t("message.error.createFail") - .concat(" ", this.$t("message.error.inUseOtherPrj")); + }, + beginEncryptedUpload() { + this.aBeginEncryptedUpload().then(() => { + delay(() => { + if (this.abortReason !== undefined) { + if (this.abortReason + .match("Could not create or access the container.")) { + this.uploadError = this.currentFolder ? + this.$t("message.upload.accessFail") + : this.$t("message.error.createFail") + .concat(" ", this.$t("message.error.inUseOtherPrj")); + } + else if (this.abortReason.match("cancel")) { + this.uploadError = this.$t("message.upload.cancelled"); + } + this.$store.commit("setUploadAbortReason", undefined); } - else if (this.abortReason.match("cancel")) { - this.uploadError = this.$t("message.upload.cancelled"); + else if (this.$store.state.encryptedFile == "" + && this.dropFiles.length) { + //upload didn't start + this.uploadError = this.$t("message.upload.error"); + this.$store.commit("stopUploading", true); + this.$store.commit("toggleUploadNotification", false); } - this.$store.commit("setUploadAbortReason", undefined); - } - else if (this.$store.state.encryptedFile == "" - && this.dropFiles.length) { - //upload didn't start - this.uploadError = this.$t("message.upload.error"); - this.$store.commit("stopUploading", true); - this.$store.commit("toggleUploadNotification", false); - } - if (this.uploadError) { - document.querySelector("#container-error-toasts").addToast( - { - type: "error", - duration: 6000, - progress: false, - message: this.uploadError, - }, - ); - } - }, 1000); - this.toggleUploadModal(); + if (this.uploadError) { + document.querySelector("#container-error-toasts").addToast( + { + type: "error", + duration: 6000, + progress: false, + message: this.uploadError, + }, + ); + } + }, 1000); + this.toggleUploadModal(); + }); }, handleKeyDown: function (e) { const focusableList = this.$refs.uploadContainer.querySelectorAll( diff --git a/swift_browser_ui_frontend/wasm/js/crypt-post-upworker.js b/swift_browser_ui_frontend/wasm/js/crypt-post-upworker.js index 30a885675..a67c7fba9 100644 --- a/swift_browser_ui_frontend/wasm/js/crypt-post-upworker.js +++ b/swift_browser_ui_frontend/wasm/js/crypt-post-upworker.js @@ -468,10 +468,10 @@ self.addEventListener("message", (e) => { } if (e.data.owner !== "") { - uploads[container].owner = e.data.owner; + uploads[e.data.container].owner = e.data.owner; } if (e.data.ownerName !== "") { - uploads[container].ownerName = e.data.ownerName; + uploads[e.data.container].ownerName = e.data.ownerName; } // Ensure the websocket has stayed open From 246c95151f98b0487efeb630c071de444e51e79b Mon Sep 17 00:00:00 2001 From: Sampsa Penna Date: Thu, 19 Oct 2023 14:50:19 +0300 Subject: [PATCH 3/3] fix shared download regressions --- CHANGELOG.md | 1 + .../src/common/socket.js | 77 ++++++++++++------- .../src/components/CObjectTable.vue | 2 + .../src/components/ContainerTable.vue | 8 +- .../wasm/js/crypt-post-downworker.js | 8 ++ 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e4c17c4..5bb1c1a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -197,6 +197,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - (GL #1137) Fix upload repeatability and worker lifecycle issues leading to upload unavailability - (GL #1127) Fix `ServiceWorker` issues affecting Firefox downloads - (GL #1091) Fix subfolder download button downloading only one file +- (GL #1129) Fix shared folder uploads and downloads in the new upload implementation (follow-up) ### Removed diff --git a/swift_browser_ui_frontend/src/common/socket.js b/swift_browser_ui_frontend/src/common/socket.js index d4dba22a0..c4c3b7866 100644 --- a/swift_browser_ui_frontend/src/common/socket.js +++ b/swift_browser_ui_frontend/src/common/socket.js @@ -1,6 +1,6 @@ // Functions for handling interfacing between workers and upload API socket -import { DELETE, GET, PUT, getUploadEndpoint, getUploadSocket } from "./api"; +import { getUploadEndpoint, getUploadSocket, signedFetch } from "./api"; import { DEV } from "./conv"; import { getDB } from "./db"; @@ -89,6 +89,8 @@ export default class UploadSocket { e.data.container, e.data.files, e.data.pubkey, + e.data.owner, + e.data.ownerName, ).then(() => { if (DEV) { console.log( @@ -146,7 +148,7 @@ export default class UploadSocket { } // Get headers for download - async getHeaders(container, fileList, pubkey) { + async getHeaders(container, fileList, pubkey, owner, ownerName) { let headers = {}; // If no files are specified, get all files in the container @@ -173,38 +175,34 @@ export default class UploadSocket { container, ); - let signatureUrl = new URL(`/sign/${60}`, document.location.origin); - signatureUrl.searchParams.append("path", whitelistPath); - let signed = await GET(signatureUrl); - signed = await signed.json(); - let whitelistUrl = new URL( - this.$store.state.uploadEndpoint.concat(whitelistPath), + await signedFetch( + "PUT", + this.$store.state.uploadEndpoint, + whitelistPath, + pubkey, + { + flavor: "crypt4gh", + session: upInfo.id, + }, ); - whitelistUrl.searchParams.append("valid", signed.valid); - whitelistUrl.searchParams.append("signature", signed.signature); - whitelistUrl.searchParams.append("flavor", "crypt4gh"); - whitelistUrl.searchParams.append("session", upInfo.id); - await PUT(whitelistUrl, pubkey); for (const file of files) { // Get the file header - let headerPath = `/header/${this.active.name}/${container}/${file}`; - signatureUrl = new URL(`/sign/${60}`, document.location.origin); - signatureUrl.searchParams.append("path", headerPath); - signed = await GET(signatureUrl); - signed = await signed.json(); - let headerUrl = new URL( - this.$store.state.uploadEndpoint.concat(headerPath), + let header = await signedFetch( + "GET", + this.$store.state.uploadEndpoint, + `/header/${this.active.name}/${container}/${file}`, + undefined, + { + session: upInfo.id, + owner: ownerName, + }, ); - headerUrl.searchParams.append("valid", signed.valid); - headerUrl.searchParams.append("signature", signed.signature); - headerUrl.searchParams.append("session", upInfo.id); - let resp = await GET(headerUrl); - let header = await resp.text(); + header = await header.text(); - // Prepare and sign the file URL + // Prepare the file URL let fileUrl = new URL( - `/download/${this.active.id}/${container}/${file}`, + `/download/${owner ? owner : this.active.id}/${container}/${file}`, document.location.origin, ); fileUrl.searchParams.append("project", this.active.id); @@ -215,7 +213,15 @@ export default class UploadSocket { }; } - await DELETE(whitelistUrl); + await signedFetch( + "DELETE", + this.$store.state.uploadEndpoint, + whitelistPath, + undefined, + { + session: upInfo.id, + }, + ); if (!this.useServiceWorker) { this.downWorker.postMessage({ @@ -290,7 +296,14 @@ export default class UploadSocket { async addDownload( container, objects, + owner = "", ) { + let ownerName = ""; + if (owner) { + let ids = await this.$store.state.client.projectCheckIDs(owner); + ownerName = ids.name; + } + let fileHandle = undefined; if (objects.length == 1) { // Download directly into the file if available. @@ -315,6 +328,8 @@ export default class UploadSocket { container: container, file: objects[0], handle: fileHandle, + owner: owner, + ownerName: ownerName, }); } else { if (DEV) { @@ -325,6 +340,8 @@ export default class UploadSocket { command: "downloadFile", container: container, file: objects[0], + owner: owner, + ownerName: ownerName, }); }); } @@ -348,6 +365,8 @@ export default class UploadSocket { container: container, files: objects.length < 1 ? [] : objects, handle: fileHandle, + owner: owner, + ownerName: ownerName, }); } else { navigator.serviceWorker.ready.then(reg => { @@ -355,6 +374,8 @@ export default class UploadSocket { command: "downloadFiles", container: container, files: objects.length < 1 ? [] : objects, + owner: owner, + ownerName: ownerName, }); }); } diff --git a/swift_browser_ui_frontend/src/components/CObjectTable.vue b/swift_browser_ui_frontend/src/components/CObjectTable.vue index 5d7940476..d4dad73ed 100644 --- a/swift_browser_ui_frontend/src/components/CObjectTable.vue +++ b/swift_browser_ui_frontend/src/components/CObjectTable.vue @@ -417,6 +417,7 @@ export default { this.$store.state.socket.addDownload( this.$route.params.container, subfolderFiles, + this.$route.params.owner ? this.$route.params.owner : "", ).then(() => { if (DEV) console.log(`Started downloading subfolder ${object.name}`); }); @@ -424,6 +425,7 @@ export default { this.$store.state.socket.addDownload( this.$route.params.container, [object.name], + this.$route.params.owner ? this.$route.params.owner : "", ).then(() => { if (DEV) console.log(`Started downloading object ${object.name}`); }); diff --git a/swift_browser_ui_frontend/src/components/ContainerTable.vue b/swift_browser_ui_frontend/src/components/ContainerTable.vue index 1335efa02..440ff1412 100644 --- a/swift_browser_ui_frontend/src/components/ContainerTable.vue +++ b/swift_browser_ui_frontend/src/components/ContainerTable.vue @@ -274,7 +274,10 @@ export default { size: "small", title: this.$t("message.download"), onClick: () => { - this.beginDownload(item.name); + this.beginDownload( + item.name, + item.owner ? item.owner : "", + ); }, target: "_blank", path: mdiTrayArrowDown, @@ -485,10 +488,11 @@ export default { this.paginationOptions.itemCount - 1, }); }, - beginDownload(container) { + beginDownload(container, owner) { this.$store.state.socket.addDownload( container, [], + owner, ).then(() => { if (DEV) console.log(`Started downloading all objects from container ${container}`); }); diff --git a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js index 2f382886c..557d81cef 100644 --- a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js +++ b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js @@ -431,6 +431,8 @@ self.addEventListener("message", (e) => { e.data.file, ], pubkey: downloads[e.data.container].pubkey, + owner: e.data.owner, + ownerName: e.data.ownerName, }); } else { createDownloadSession(e.data.container, e.data.handle, false); @@ -441,6 +443,8 @@ self.addEventListener("message", (e) => { e.data.file, ], pubkey: downloads[e.data.container].pubkey, + owner: e.data.owner, + ownerName: e.data.ownerName, }); } break; @@ -452,6 +456,8 @@ self.addEventListener("message", (e) => { container: e.data.container, files: e.data.files, pubkey: downloads[e.data.container].pubkey, + owner: e.data.owner, + ownerName: e.data.ownerName, }); } else { createDownloadSession(e.data.container, e.data.handle, true); @@ -460,6 +466,8 @@ self.addEventListener("message", (e) => { container: e.data.container, files: e.data.files, pubkey: downloads[e.data.container].pubkey, + owner: e.data.owner, + ownerName: e.data.ownerName, }); } break;