From 19714471514a71ac72d8bf37485f661719dfd2d2 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Mon, 3 Jun 2024 17:44:43 -0700 Subject: [PATCH 01/16] WIP --- packages/backend/.vscode/extensions.json | 5 +++ packages/backend/.vscode/launch.json | 12 ++++++ packages/backend/.vscode/settings.json | 9 +++++ packages/backend/.vscode/tasks.json | 38 +++++++++++++++++++ packages/backend/local.settings.json | 24 ++++++------ packages/backend/src/functions/httpTrigger.ts | 18 +++++---- packages/backend/test/test.ts | 8 ++-- packages/frontend/services/azureFuncs.ts | 4 +- 8 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 packages/backend/.vscode/extensions.json create mode 100644 packages/backend/.vscode/launch.json create mode 100644 packages/backend/.vscode/settings.json create mode 100644 packages/backend/.vscode/tasks.json diff --git a/packages/backend/.vscode/extensions.json b/packages/backend/.vscode/extensions.json new file mode 100644 index 0000000..036c408 --- /dev/null +++ b/packages/backend/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/packages/backend/.vscode/launch.json b/packages/backend/.vscode/launch.json new file mode 100644 index 0000000..1c6e50d --- /dev/null +++ b/packages/backend/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Node Functions", + "type": "node", + "request": "attach", + "port": 9229, + "preLaunchTask": "func: host start" + } + ] +} \ No newline at end of file diff --git a/packages/backend/.vscode/settings.json b/packages/backend/.vscode/settings.json new file mode 100644 index 0000000..012588f --- /dev/null +++ b/packages/backend/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "azureFunctions.deploySubpath": ".", + "azureFunctions.postDeployTask": "npm install (functions)", + "azureFunctions.projectLanguage": "TypeScript", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.projectLanguageModel": 4, + "azureFunctions.preDeployTask": "npm prune (functions)" +} \ No newline at end of file diff --git a/packages/backend/.vscode/tasks.json b/packages/backend/.vscode/tasks.json new file mode 100644 index 0000000..8ae4910 --- /dev/null +++ b/packages/backend/.vscode/tasks.json @@ -0,0 +1,38 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "func", + "label": "func: host start", + "command": "host start", + "problemMatcher": "$func-node-watch", + "isBackground": true, + "dependsOn": "npm build (functions)" + }, + { + "type": "shell", + "label": "npm build (functions)", + "command": "npm run build", + "dependsOn": "npm clean (functions)", + "problemMatcher": "$tsc" + }, + { + "type": "shell", + "label": "npm install (functions)", + "command": "npm install" + }, + { + "type": "shell", + "label": "npm prune (functions)", + "command": "npm prune --production", + "dependsOn": "npm build (functions)", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "npm clean (functions)", + "command": "npm run clean", + "dependsOn": "npm install (functions)" + } + ] +} \ No newline at end of file diff --git a/packages/backend/local.settings.json b/packages/backend/local.settings.json index c63557c..a69df1b 100644 --- a/packages/backend/local.settings.json +++ b/packages/backend/local.settings.json @@ -1,13 +1,13 @@ { - "IsEncrypted": false, - "Values": { - "FUNCTIONS_WORKER_RUNTIME": "node", - "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", - "AzureWebJobsStorage": "", - "AZURE_STORAGE_ACCOUNT_NAME": "", - "AZURE_STORAGE_ACCOUNT_KEY": "" - }, - "Host": { - "CORS": "*" - } -} + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "node", + "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "AZURE_STORAGE_ACCOUNT_NAME": "", + "AZURE_STORAGE_ACCOUNT_KEY": "" + }, + "Host": { + "CORS": "*" + } +} \ No newline at end of file diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index 687835a..bc44d36 100644 --- a/packages/backend/src/functions/httpTrigger.ts +++ b/packages/backend/src/functions/httpTrigger.ts @@ -49,9 +49,9 @@ async function calculateDeviceID(key: string | Uint8Array): Promise { return toHex(hash); } -async function encrypt(key: Uint8Array, data: BufferSource): Promise<{ salt: Uint8Array; encryptedData: Uint8Array; }> { +async function encrypt(key: Uint8Array, data: BufferSource, salt?: Uint8Array): Promise<{ salt: Uint8Array; encryptedData: Uint8Array; }> { const $key = await crypto.subtle.importKey("raw", key.buffer, "AES-CBC", false, ['encrypt']); - const salt = crypto.getRandomValues(new Uint8Array(16)); + salt ??= crypto.getRandomValues(new Uint8Array(16)); const encryptedData = await crypto.subtle.encrypt({ name: "AES-CBC", iv: salt }, $key, data); return { salt, encryptedData: new Uint8Array(encryptedData) }; } @@ -62,19 +62,23 @@ async function decrypt(key: Uint8Array, salt: Uint8Array, encryptedData: Uint8Ar return new Uint8Array(result); } -async function upload(client: ContainerClient, deviceKey: Uint8Array, data: BufferSource, type: 'attach' | 'prov', contentType: string, timestamp: number): Promise { +async function upload(client: ContainerClient, deviceKey: Uint8Array, data: BufferSource, type: 'attach' | 'prov', contentType: string, timestamp: number, fileName: string | undefined): Promise { const dataHash = toHex(await sha256(data)); const deviceID = await calculateDeviceID(deviceKey); const { salt, encryptedData } = await encrypt(deviceKey, data); const blobID = toHex(await sha256(encryptedData)); const blobName = `${client.containerName}/${deviceID}/${type}/${blobID}`; + const { encryptedData: encryptedName } = fileName ? await encrypt(deviceKey, new TextEncoder().encode(fileName), salt) : undefined; + + await client.uploadBlockBlob(blobName, encryptedData.buffer, encryptedData.length, { metadata: { gdtcontenttype: contentType, gdthash: dataHash, gdtsalt: toHex(salt), gdttimestamp: `${timestamp}`, + // gdtname: encryptedName ? toHex(encryptedName) }, blobHTTPHeaders: { blobContentType: "application/octet-stream" @@ -166,19 +170,19 @@ async function postProvenance(request: HttpRequest, context: InvocationContext): await containerClient.createIfNotExists(); const formData = await request.formData(); + const values = Array.from(formData.values()); const provenanceRecord = formData.get("provenanceRecord"); if (typeof provenanceRecord !== 'string') { return { status: 404 }; } const record = JSON5.parse(provenanceRecord); // https://stackoverflow.com/questions/9756120/how-do-i-get-a-utc-timestamp-in-javascript#comment73511758_9756120 const timestamp = new Date().getTime(); - const attachments = new Array(); { - for (const attach of formData.getAll("attachment")) { + for (const attach of formData.values()) { if (typeof attach === 'string') continue; const data = await attach.arrayBuffer() - const attachmentID = await upload(containerClient, deviceKey, data, "attach", attach.type, timestamp); + const attachmentID = await upload(containerClient, deviceKey, data, "attach", attach.type, timestamp, attach.name); attachments.push(attachmentID); } } @@ -186,7 +190,7 @@ async function postProvenance(request: HttpRequest, context: InvocationContext): { const provRecord: ProvenanceRecord = { record, attachments }; const data = new TextEncoder().encode(JSON.stringify(provRecord)); - const recordID = await upload(containerClient, deviceKey, data, "prov", "application/json", timestamp); + const recordID = await upload(containerClient, deviceKey, data, "prov", "application/json", timestamp, undefined); return { jsonBody: { record: recordID, attachments } }; } diff --git a/packages/backend/test/test.ts b/packages/backend/test/test.ts index 0cdc017..25729c2 100644 --- a/packages/backend/test/test.ts +++ b/packages/backend/test/test.ts @@ -26,11 +26,11 @@ async function getAttachment(baseUrl: string, deviceKey: string, attachmentID: s return await response.blob(); } -async function putProvRecord(baseUrl: string, deviceKey: string, record: any, attachments: readonly Blob[]) { +async function putProvRecord(baseUrl: string, deviceKey: string, record: any, attachments: readonly File[]) { const formData = new FormData(); formData.append("provenanceRecord", JSON.stringify(record)); for (const blob of attachments) { - formData.append("attachment", blob); + formData.append(blob.name, blob as Blob); } const response = await fetch(`${baseUrl}/provenance/${deviceKey}`, { method: "POST", @@ -94,7 +94,7 @@ program program.parse(process.argv); -async function getTestImages(): Promise { +async function getTestImages(): Promise { const images = new Array(); for (const fileName of await readdir(__dirname)) { const ext = extname(fileName); @@ -104,5 +104,5 @@ async function getTestImages(): Promise { const file = new File([buffer], fileName, { type }); images.push(file) } - return images as readonly Blob[]; + return images; } \ No newline at end of file diff --git a/packages/frontend/services/azureFuncs.ts b/packages/frontend/services/azureFuncs.ts index 282cd6e..86bdd8a 100644 --- a/packages/frontend/services/azureFuncs.ts +++ b/packages/frontend/services/azureFuncs.ts @@ -29,12 +29,12 @@ export async function getAttachment(baseUrl: string, deviceKey: string, attachme } } -export async function postProvenance(deviceKey: string, record: any, attachments: readonly Blob[]) { +export async function postProvenance(deviceKey: string, record: any, attachments: readonly File[]) { const baseUrl = useRuntimeConfig().public.baseUrl; const formData = new FormData(); formData.append("provenanceRecord", JSON.stringify(record)); for (const blob of attachments) { - formData.append("attachment", blob); + formData.append(blob.name, blob); } const response = await fetch(`${baseUrl}/provenance/${deviceKey}`, { method: "POST", From a2ebb322da4fd13219d73896ac85551b39663820 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Mon, 17 Jun 2024 17:20:05 -0700 Subject: [PATCH 02/16] remove pnpm package manager --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index db8004b..088e490 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,5 @@ }, "workspaces": [ "packages/legacy" - ], - "packageManager": "pnpm@9.1.1+sha512.14e915759c11f77eac07faba4d019c193ec8637229e62ec99eefb7cf3c3b75c64447882b7c485142451ee3a6b408059cdfb7b7fa0341b975f12d0f7629c71195" + ] } From b3019bf3514cfa9df6f0170d88630600215b4a4f Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Sun, 23 Jun 2024 10:35:59 -0700 Subject: [PATCH 03/16] fix http trigger --- packages/backend/src/functions/httpTrigger.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index bc44d36..70fae26 100644 --- a/packages/backend/src/functions/httpTrigger.ts +++ b/packages/backend/src/functions/httpTrigger.ts @@ -69,16 +69,17 @@ async function upload(client: ContainerClient, deviceKey: Uint8Array, data: Buff const blobID = toHex(await sha256(encryptedData)); const blobName = `${client.containerName}/${deviceID}/${type}/${blobID}`; - const { encryptedData: encryptedName } = fileName ? await encrypt(deviceKey, new TextEncoder().encode(fileName), salt) : undefined; + const { encryptedData: encryptedName } = fileName + ? await encrypt(deviceKey, new TextEncoder().encode(fileName), salt) + : { encryptedData: undefined }; - await client.uploadBlockBlob(blobName, encryptedData.buffer, encryptedData.length, { metadata: { gdtcontenttype: contentType, gdthash: dataHash, gdtsalt: toHex(salt), gdttimestamp: `${timestamp}`, - // gdtname: encryptedName ? toHex(encryptedName) + gdtname: encryptedName ? toHex(encryptedName) : "" }, blobHTTPHeaders: { blobContentType: "application/octet-stream" @@ -170,7 +171,6 @@ async function postProvenance(request: HttpRequest, context: InvocationContext): await containerClient.createIfNotExists(); const formData = await request.formData(); - const values = Array.from(formData.values()); const provenanceRecord = formData.get("provenanceRecord"); if (typeof provenanceRecord !== 'string') { return { status: 404 }; } const record = JSON5.parse(provenanceRecord); @@ -191,8 +191,7 @@ async function postProvenance(request: HttpRequest, context: InvocationContext): const provRecord: ProvenanceRecord = { record, attachments }; const data = new TextEncoder().encode(JSON.stringify(provRecord)); const recordID = await upload(containerClient, deviceKey, data, "prov", "application/json", timestamp, undefined); - return { - jsonBody: { record: recordID, attachments } }; + return { jsonBody: { record: recordID, attachments } }; } } // blobNames look like: 'gosqas/63f4b781c0688d83d40908ff368fefa6a2fa4cd470216fd83b3d7d4c642578c0/prov/1a771caa4b15a45ae97b13d7a336e1e9c9ec1c91c70f1dc8f7749440c0af8114' From db9b52c92ec233f5c34c580e3e0e7c16856a7a37 Mon Sep 17 00:00:00 2001 From: Coco Chen Date: Sun, 23 Jun 2024 17:23:07 -0400 Subject: [PATCH 04/16] update the file --- packages/frontend/components/Provenance/Feed.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/components/Provenance/Feed.vue b/packages/frontend/components/Provenance/Feed.vue index c7745e5..c17d312 100644 --- a/packages/frontend/components/Provenance/Feed.vue +++ b/packages/frontend/components/Provenance/Feed.vue @@ -21,6 +21,9 @@
{{ new Date(report.timestamp) }}
From 4f5771126fddbe34d228ce1f919eb0324ab188a8 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Mon, 24 Jun 2024 19:08:27 -0700 Subject: [PATCH 05/16] include filename in decryptBlob --- packages/backend/src/functions/httpTrigger.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index 70fae26..ccb2589 100644 --- a/packages/backend/src/functions/httpTrigger.ts +++ b/packages/backend/src/functions/httpTrigger.ts @@ -88,7 +88,14 @@ async function upload(client: ContainerClient, deviceKey: Uint8Array, data: Buff return blobID; } -async function decryptBlob(client: BlockBlobClient, deviceKey: Uint8Array) { +interface DecryptedBlob { + data: Uint8Array; + contentType: string; + timestamp: number; + filename?: string; +} + +async function decryptBlob(client: BlockBlobClient, deviceKey: Uint8Array): Promise { const props = await client.getProperties(); const salt = props.metadata?.["gdtsalt"]; if (!salt) throw new Error(`Missing Salt ${client.name}`); @@ -96,15 +103,21 @@ async function decryptBlob(client: BlockBlobClient, deviceKey: Uint8Array) { if (isNaN(timestamp) || !isFinite(timestamp)) throw new Error(`Invalid Timestamp ${client.name}`); const buffer = await client.downloadToBuffer(); - const data = await decrypt(deviceKey, fromHex(salt), buffer); + const saltBuffer = fromHex(salt); + const data = await decrypt(deviceKey, saltBuffer, buffer); const hash = props.metadata?.["gdthash"]; if (hash) { if (!areEqual(fromHex(hash), await sha256(data))) { throw new Error(`Invalid Hash ${client.name}`); } } + const contentType = props.metadata?.["gdtcontenttype"]; - return { data, contentType, timestamp }; + const encryptedName = props.metadata?.["gdtname"] ?? ""; + const encodedName = encryptedName.length > 0 ? await decrypt(deviceKey, saltBuffer, fromHex(encryptedName)) : undefined; + const filename = encodedName ? new TextDecoder().decode(encodedName) : undefined; + + return { data, contentType, timestamp, filename }; function areEqual(first: Uint8Array, second: Uint8Array) { return first.length === second.length @@ -154,13 +167,12 @@ async function getAttachment(request: HttpRequest, context: InvocationContext): const exists = await blobClient.exists(); if (!exists) { return { status: 404 }; } - const { data, contentType } = await decryptBlob(blobClient, deviceKey); - return { - body: data, - headers: contentType - ? { "Content-Type": contentType } - : undefined - }; + const { data, contentType, filename } = await decryptBlob(blobClient, deviceKey); + const headers = new Headers(); + if (contentType) { headers.append("Content-Type", contentType); } + if (filename) { headers.append("Content-Disposition", `attachment; filename="${filename}"`)} + + return { body: data, headers }; }; async function postProvenance(request: HttpRequest, context: InvocationContext): Promise { From f55bc7d0cebb997813752e5d110db2a5a10ea400 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Mon, 24 Jun 2024 19:11:37 -0700 Subject: [PATCH 06/16] Attachment-Name header --- packages/backend/src/functions/httpTrigger.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index ccb2589..741265d 100644 --- a/packages/backend/src/functions/httpTrigger.ts +++ b/packages/backend/src/functions/httpTrigger.ts @@ -170,7 +170,10 @@ async function getAttachment(request: HttpRequest, context: InvocationContext): const { data, contentType, filename } = await decryptBlob(blobClient, deviceKey); const headers = new Headers(); if (contentType) { headers.append("Content-Type", contentType); } - if (filename) { headers.append("Content-Disposition", `attachment; filename="${filename}"`)} + if (filename) { + headers.append("Content-Disposition", `attachment; filename="${filename}"`); + headers.append("Attachment-Name", filename); + } return { body: data, headers }; }; From f727d5f573b148c176e382a0a4ffdd9331bfc606 Mon Sep 17 00:00:00 2001 From: Coco Chen Date: Wed, 26 Jun 2024 17:36:21 -0400 Subject: [PATCH 07/16] update the download --- .../frontend/components/Provenance/Feed.vue | 19 +++++++++++---- packages/frontend/services/azureFuncs.ts | 24 ++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/frontend/components/Provenance/Feed.vue b/packages/frontend/components/Provenance/Feed.vue index c17d312..5f8d92d 100644 --- a/packages/frontend/components/Provenance/Feed.vue +++ b/packages/frontend/components/Provenance/Feed.vue @@ -18,10 +18,10 @@
{{ report.record.description }}
-
+ @@ -74,8 +74,17 @@ export default { const baseUrl = useRuntimeConfig().public.baseUrl; const attachmentPromises = report.attachments.map(attachmentID => getAttachment(baseUrl,this.deviceKey, attachmentID)); const attachments = await Promise.all(attachmentPromises); - const urls = attachments.map(attachment => URL.createObjectURL(attachment)); + + // Create object URLs for attachments and include filenames + const urls = attachments.map(attachment => ({ + url: URL.createObjectURL(attachment.blob), + fileName: attachment.fileName + })); + this.attachmentURLs[index.toString()] = urls; + console.log(`Attachment URLs for report ${index}:`, this.attachmentURLs[index.toString()]); // Debugging line + + } } catch (error) { console.error('Error occurred during getAttachment request:', error); @@ -112,4 +121,4 @@ export default { border-radius: 5px; font-size: 14px; } - + \ No newline at end of file diff --git a/packages/frontend/services/azureFuncs.ts b/packages/frontend/services/azureFuncs.ts index 86bdd8a..34d3243 100644 --- a/packages/frontend/services/azureFuncs.ts +++ b/packages/frontend/services/azureFuncs.ts @@ -19,16 +19,22 @@ export async function getProvenance(deviceKey: string) { export async function getAttachment(baseUrl: string, deviceKey: string, attachmentID: string) { // const baseUrl = useRuntimeConfig().public.baseUrl; try { - const response = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}`, { - method: "GET", - }); - return await response.blob(); - } catch (error) { - console.error('Error occurred during getAttachment request:', error); - throw error; // re-throw the error if you want to handle it further up the call stack - } + const response = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}`, { + method: "GET", + }); + const fileName = response.headers.get('Attachment-Name') || "not-getting-attachment-name"; + + const blob = await response.blob(); + return { blob, fileName }; +} catch (error) { + console.error('Error occurred during getAttachment request:', error); + throw error; // re-throw the error if you want to handle it further up the call stack + } + + } + export async function postProvenance(deviceKey: string, record: any, attachments: readonly File[]) { const baseUrl = useRuntimeConfig().public.baseUrl; const formData = new FormData(); @@ -49,4 +55,4 @@ export async function getStatistics() { method: "GET", }); return await response.json() as { record: string, timestamp: number }[]; -} +} \ No newline at end of file From 4e5ddb64f2515b2f79d13176d3a213d6762253d8 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Wed, 26 Jun 2024 14:48:12 -0700 Subject: [PATCH 08/16] update get attachement test --- packages/backend/test/test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/backend/test/test.ts b/packages/backend/test/test.ts index 25729c2..fe37a3e 100644 --- a/packages/backend/test/test.ts +++ b/packages/backend/test/test.ts @@ -20,10 +20,9 @@ async function getProvRecords(baseUrl: string, deviceKey: string) { } async function getAttachment(baseUrl: string, deviceKey: string, attachmentID: string) { - const response = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}`, { + return await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}`, { method: "GET", }); - return await response.blob(); } async function putProvRecord(baseUrl: string, deviceKey: string, record: any, attachments: readonly File[]) { @@ -87,7 +86,10 @@ program const attachment = json[0]?.attachments?.[0]; if (attachment) { console.log(`Downloading ${attachment}`); - await getAttachment(baseUrl, testDeviceKey, attachment!); + const resp = await getAttachment(baseUrl, testDeviceKey, attachment!); + console.log(resp.headers); + const name = resp.headers.get("Attachment-Name") + console.log({name}) } } }) From a5307ae587a09ec38f084a2f0b6e3be61bbda8c3 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Mon, 1 Jul 2024 18:33:28 -0700 Subject: [PATCH 09/16] add getAttachmentName backend function --- packages/backend/src/functions/httpTrigger.ts | 34 +++++++++++++++---- packages/backend/test/test.ts | 18 +++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index 741265d..caa2672 100644 --- a/packages/backend/src/functions/httpTrigger.ts +++ b/packages/backend/src/functions/httpTrigger.ts @@ -154,21 +154,29 @@ async function getProvenance(request: HttpRequest, context: InvocationContext): return { jsonBody: records }; } -async function getAttachment(request: HttpRequest, context: InvocationContext): Promise { +async function getDecryptedBlob(request: HttpRequest, context: InvocationContext): Promise { const deviceKey = decodeKey(request.params.deviceKey); const deviceID = await calculateDeviceID(deviceKey); const attachmentID = request.params.attachmentID; - context.log(`getAttachment`, { accountName, deviceKey: request.params.deviceKey, deviceID, attachmentID }); + context.log(`getDecryptedBlob`, { accountName, deviceKey: request.params.deviceKey, deviceID, attachmentID }); const containerExists = await containerClient.exists(); - if (!containerExists) { return { status: 404 }; } + if (!containerExists) { return undefined; } const blobClient = containerClient.getBlockBlobClient(`gosqas/${deviceID}/attach/${attachmentID}`); const exists = await blobClient.exists(); - if (!exists) { return { status: 404 }; } + if (!exists) { return undefined; } + + return await decryptBlob(blobClient, deviceKey); +} - const { data, contentType, filename } = await decryptBlob(blobClient, deviceKey); +async function getAttachment(request: HttpRequest, context: InvocationContext): Promise { + const decryptedBlob = await getDecryptedBlob(request, context); + if (!decryptedBlob) { return { status: 404 } } + + const { data, contentType, filename } = decryptedBlob; const headers = new Headers(); + headers.append("Access-Control-Allow-Headers", "Attachment-Name"); if (contentType) { headers.append("Content-Type", contentType); } if (filename) { headers.append("Content-Disposition", `attachment; filename="${filename}"`); @@ -178,6 +186,14 @@ async function getAttachment(request: HttpRequest, context: InvocationContext): return { body: data, headers }; }; +async function getAttachmentName(request: HttpRequest, context: InvocationContext): Promise { + const decryptedBlob = await getDecryptedBlob(request, context); + if (!decryptedBlob) { return { status: 404 } } + + const { filename } = decryptedBlob; + return { body: filename }; +}; + async function postProvenance(request: HttpRequest, context: InvocationContext): Promise { const deviceKey = decodeKey(request.params.deviceKey); const deviceID = await calculateDeviceID(deviceKey); @@ -270,7 +286,13 @@ app.post("postProvenance", { app.get("getAttachment", { authLevel: 'anonymous', route: 'attachment/{deviceKey}/{attachmentID}', - handler: getAttachment + handler: getAttachment, +}) + +app.get("getAttachmentName", { + authLevel: 'anonymous', + route: 'attachment/{deviceKey}/{attachmentID}/name', + handler: getAttachmentName, }) app.get("getStatistics", { diff --git a/packages/backend/test/test.ts b/packages/backend/test/test.ts index fe37a3e..06e0b10 100644 --- a/packages/backend/test/test.ts +++ b/packages/backend/test/test.ts @@ -25,6 +25,12 @@ async function getAttachment(baseUrl: string, deviceKey: string, attachmentID: s }); } +async function getAttachmentName(baseUrl: string, deviceKey: string, attachmentID: string) { + return await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}/name`, { + method: "GET", + }); +} + async function putProvRecord(baseUrl: string, deviceKey: string, record: any, attachments: readonly File[]) { const formData = new FormData(); formData.append("provenanceRecord", JSON.stringify(record)); @@ -86,10 +92,14 @@ program const attachment = json[0]?.attachments?.[0]; if (attachment) { console.log(`Downloading ${attachment}`); - const resp = await getAttachment(baseUrl, testDeviceKey, attachment!); - console.log(resp.headers); - const name = resp.headers.get("Attachment-Name") - console.log({name}) + const resp = await getAttachment(baseUrl, testDeviceKey, attachment); + console.log("Headers"); + for (const [key, value] of resp.headers) { + console.log(` ${key}: ${value}`); + } + const resp2 = await getAttachmentName(baseUrl, testDeviceKey, attachment); + const name = await resp2.text(); + console.log({name}); } } }) From cc8e1e57655c5176308e0289d0989e892be86b4c Mon Sep 17 00:00:00 2001 From: Coco Chen Date: Sun, 7 Jul 2024 16:00:34 -0400 Subject: [PATCH 10/16] update the frontend --- packages/frontend/services/azureFuncs.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/frontend/services/azureFuncs.ts b/packages/frontend/services/azureFuncs.ts index 34d3243..0c484de 100644 --- a/packages/frontend/services/azureFuncs.ts +++ b/packages/frontend/services/azureFuncs.ts @@ -22,10 +22,16 @@ export async function getAttachment(baseUrl: string, deviceKey: string, attachme const response = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}`, { method: "GET", }); - const fileName = response.headers.get('Attachment-Name') || "not-getting-attachment-name"; const blob = await response.blob(); - return { blob, fileName }; + + // Fetch the attachment name + const nameResponse = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}/name`, { + method: "GET", + }); + const fileName = await nameResponse.text(); + + return { blob, fileName }; } catch (error) { console.error('Error occurred during getAttachment request:', error); throw error; // re-throw the error if you want to handle it further up the call stack From b2e541f808affdf493eb43cc6cece35d097eb6e2 Mon Sep 17 00:00:00 2001 From: Coco Chen Date: Wed, 10 Jul 2024 15:23:12 -0400 Subject: [PATCH 11/16] Update retrieve the attachment name from the header --- packages/frontend/services/azureFuncs.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/frontend/services/azureFuncs.ts b/packages/frontend/services/azureFuncs.ts index 0c484de..0843cc0 100644 --- a/packages/frontend/services/azureFuncs.ts +++ b/packages/frontend/services/azureFuncs.ts @@ -25,12 +25,16 @@ export async function getAttachment(baseUrl: string, deviceKey: string, attachme const blob = await response.blob(); - // Fetch the attachment name - const nameResponse = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}/name`, { + // Check for the attachment name + let fileName = response.headers.get('Attachment-Name'); + // If the header is not present, fetch the attachment name + if(!fileName) { + // Fetch the attachment name + const nameResponse = await fetch(`${baseUrl}/attachment/${deviceKey}/${attachmentID}/name`, { method: "GET", }); - const fileName = await nameResponse.text(); - + fileName = await nameResponse.text(); + } return { blob, fileName }; } catch (error) { console.error('Error occurred during getAttachment request:', error); From 4d536014eae5f18407638feb0e488268e5443d86 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Thu, 18 Jul 2024 13:38:08 -0700 Subject: [PATCH 12/16] change front end on push GH action file name --- .github/workflows/{on_push.yml => fe_on_push.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{on_push.yml => fe_on_push.yml} (100%) diff --git a/.github/workflows/on_push.yml b/.github/workflows/fe_on_push.yml similarity index 100% rename from .github/workflows/on_push.yml rename to .github/workflows/fe_on_push.yml From d85da90db17cd348fe7ccd4f9293888b3aca536c Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Thu, 18 Jul 2024 13:38:22 -0700 Subject: [PATCH 13/16] add Backend push github action --- .github/workflows/be_on_push.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/be_on_push.yml diff --git a/.github/workflows/be_on_push.yml b/.github/workflows/be_on_push.yml new file mode 100644 index 0000000..c635f20 --- /dev/null +++ b/.github/workflows/be_on_push.yml @@ -0,0 +1,43 @@ +name: On Push Backend + +on: + push: + branches: + - main + paths: + - packages/backend/** + +# Docs: https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-github-actions?tabs=linux%2Cjavascript&pivots=method-manual + +env: + AZURE_FUNCTIONAPP_NAME: 'gosqasbe' # set this to your function app name on Azure + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'packages/backend' # set this to the path to your function app project, defaults to the repository root + NODE_VERSION: '18.x' # set this to the node version to use (e.g. '8.x', '10.x', '12.x') + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v3 + + - name: Setup Node ${{ env.NODE_VERSION }} Environment + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 'Resolve Project Dependencies Using Npm' + shell: bash + run: | + pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' + npm install + npm run build --if-present + popd + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} From 45f53643b4b37be0ce37afa2f63d9e149ce23813 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Thu, 18 Jul 2024 13:39:53 -0700 Subject: [PATCH 14/16] add be build test on pr --- .github/workflows/be_on_push.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/be_on_push.yml b/.github/workflows/be_on_push.yml index c635f20..d8c44b4 100644 --- a/.github/workflows/be_on_push.yml +++ b/.github/workflows/be_on_push.yml @@ -6,7 +6,10 @@ on: - main paths: - packages/backend/** - + pull_request: + branches: + - main + # Docs: https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-github-actions?tabs=linux%2Cjavascript&pivots=method-manual env: @@ -36,6 +39,7 @@ jobs: - name: 'Run Azure Functions Action' uses: Azure/functions-action@v1 + if: github.ref == 'refs/heads/main' && github.event_name == 'push' id: fa with: app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} From 1582f5e59fc59e5241f2963e7ceeafea9e569d90 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Thu, 18 Jul 2024 13:41:12 -0700 Subject: [PATCH 15/16] update actions/checkout --- .github/workflows/be_on_push.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/be_on_push.yml b/.github/workflows/be_on_push.yml index d8c44b4..038925a 100644 --- a/.github/workflows/be_on_push.yml +++ b/.github/workflows/be_on_push.yml @@ -22,7 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout GitHub Action' - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.7 + - name: Setup Node ${{ env.NODE_VERSION }} Environment uses: actions/setup-node@v3 From 65077853059f4745c96190afefe50020367bbac7 Mon Sep 17 00:00:00 2001 From: Harry Pierson Date: Thu, 18 Jul 2024 13:42:26 -0700 Subject: [PATCH 16/16] update actions/setup-node --- .github/workflows/be_on_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/be_on_push.yml b/.github/workflows/be_on_push.yml index 038925a..6ad505d 100644 --- a/.github/workflows/be_on_push.yml +++ b/.github/workflows/be_on_push.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Node ${{ env.NODE_VERSION }} Environment - uses: actions/setup-node@v3 + uses: actions/setup-node@v4.0.3 with: node-version: ${{ env.NODE_VERSION }}