diff --git a/.github/workflows/be_on_push.yml b/.github/workflows/be_on_push.yml new file mode 100644 index 0000000..6ad505d --- /dev/null +++ b/.github/workflows/be_on_push.yml @@ -0,0 +1,48 @@ +name: On Push Backend + +on: + push: + branches: + - 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: + 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@v4.1.7 + + + - name: Setup Node ${{ env.NODE_VERSION }} Environment + uses: actions/setup-node@v4.0.3 + 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 + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + id: fa + with: + app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} 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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5058030 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/frontend/qrcode-vue3"] + path = packages/frontend/qrcode-vue3 + url = https://github.com/judithweng/qrcode-vue3.git diff --git a/.vscode/settings.json b/.vscode/settings.json index 5faf49e..ca58d81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ + + { "azureFunctions.deploySubpath": "packages/backend", "azureFunctions.postDeployTask": "install backend", diff --git a/package-lock.json b/package-lock.json index 7a3602e..9acc0c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,4 @@ + { "name": "gosqas-global-distributed-tracking", "version": "0.0.0-placeholder", diff --git a/package.json b/package.json index db8004b..cba874d 100644 --- a/package.json +++ b/package.json @@ -1,3 +1,4 @@ + { "name": "gosqas-global-distributed-tracking", "version": "0.0.0-placeholder", @@ -12,6 +13,5 @@ }, "workspaces": [ "packages/legacy" - ], - "packageManager": "pnpm@9.1.1+sha512.14e915759c11f77eac07faba4d019c193ec8637229e62ec99eefb7cf3c3b75c64447882b7c485142451ee3a6b408059cdfb7b7fa0341b975f12d0f7629c71195" + ] } diff --git a/packages/.DS_Store b/packages/.DS_Store index 186ff1f..bf82c2f 100644 Binary files a/packages/.DS_Store and b/packages/.DS_Store differ 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/host.json b/packages/backend/host.json index 9df9136..0993d47 100644 --- a/packages/backend/host.json +++ b/packages/backend/host.json @@ -1,3 +1,4 @@ + { "version": "2.0", "logging": { diff --git a/packages/backend/local.settings.json b/packages/backend/local.settings.json index c63557c..6fb8489 100644 --- a/packages/backend/local.settings.json +++ b/packages/backend/local.settings.json @@ -1,13 +1,14 @@ + { - "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/package-lock.json b/packages/backend/package-lock.json index e70f658..ddc92bd 100644 --- a/packages/backend/package-lock.json +++ b/packages/backend/package-lock.json @@ -1,3 +1,4 @@ + { "name": "gosqas-az-func", "version": "0.0.0-placeholder", diff --git a/packages/backend/package.json b/packages/backend/package.json index 3819547..becbcd3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,3 +1,4 @@ + { "name": "gosqas-az-func", "version": "0.0.0-placeholder", diff --git a/packages/backend/src/functions/httpTrigger.ts b/packages/backend/src/functions/httpTrigger.ts index 687835a..caa2672 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,24 @@ 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) + : { encryptedData: 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" @@ -83,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}`); @@ -91,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 @@ -136,26 +154,44 @@ 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; } - const { data, contentType } = await decryptBlob(blobClient, deviceKey); - return { - body: data, - headers: contentType - ? { "Content-Type": contentType } - : undefined - }; + return 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}"`); + headers.append("Attachment-Name", filename); + } + + 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 { @@ -172,13 +208,12 @@ async function postProvenance(request: HttpRequest, context: InvocationContext): // 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,9 +221,8 @@ 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); - return { - jsonBody: { record: recordID, attachments } }; + const recordID = await upload(containerClient, deviceKey, data, "prov", "application/json", timestamp, undefined); + return { jsonBody: { record: recordID, attachments } }; } } // blobNames look like: 'gosqas/63f4b781c0688d83d40908ff368fefa6a2fa4cd470216fd83b3d7d4c642578c0/prov/1a771caa4b15a45ae97b13d7a336e1e9c9ec1c91c70f1dc8f7749440c0af8114' @@ -252,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 0cdc017..06e0b10 100644 --- a/packages/backend/test/test.ts +++ b/packages/backend/test/test.ts @@ -20,17 +20,22 @@ 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 Blob[]) { +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)); 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", @@ -87,14 +92,21 @@ 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("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}); } } }) 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 +116,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/README.md b/packages/frontend/README.md index b0b7ee1..616ad3b 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -1,3 +1,4 @@ + # Nuxt 3 Minimal Starter Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. diff --git a/packages/frontend/assets/css/main.css b/packages/frontend/assets/css/main.css index 099cf64..39e4a7a 100644 --- a/packages/frontend/assets/css/main.css +++ b/packages/frontend/assets/css/main.css @@ -1,3 +1,19 @@ +/* +main.css -- styling page of the frontend +Copyright (C) 2024 GOSQAS Team +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . */ + /* Default styles */ body { font-family: Arial, sans-serif; diff --git a/packages/frontend/assets/styles/main.scss b/packages/frontend/assets/styles/main.scss index 531c4a8..21e6492 100644 --- a/packages/frontend/assets/styles/main.scss +++ b/packages/frontend/assets/styles/main.scss @@ -1,3 +1,18 @@ +// main.scss -- logo styling +// Copyright (C) 2024 GOSQAS Team +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + // custom color variables // @import "bootstrap/scss/_utilities"; @@ -7,7 +22,7 @@ $custom-theme-colors: ( "iris" : #4e3681, - "sky" :#ccecfd, + "sky" :#E6F6FF, "eggplant" : #322253, "frost" : #e6f6ff, "slate" :#1e2019, @@ -17,12 +32,116 @@ $custom-theme-colors: ( $font-family-base: 'Poppins', sans-serif; $font-weight-base: 500; - $theme-colors: map-merge($custom-theme-colors, $theme-colors); +$input-placeholder-color: #1e2019; +$input-font-weight: $font-weight-base; +$input-border-width: 0; + +$form-file-button-color: #ffffff; +$form-file-button-bg: #4e3681 ; +$form-file-button-hover-bg: #322253; // import bootstrap @import "bootstrap/scss/bootstrap"; + +//// Custom styles implementation: + +//// Mobile +// Apply custom font size to h1 elements +h1 { + font-size: 32px; + font-weight: 600; + line-height: 50px; + } + + + // Apply custom font size to h2 elements + h2 { + font-size: 28px; + font-weight: $font-weight-base; + line-height: 42px; + } + + + // Apply custom font size to h3 elements + h3 { + font-size: 24px; + font-weight: $font-weight-base; + line-height: 36px; + } + + + // Apply custom font size to h4 elements + h4 { + font-size: 22px; + font-weight: $font-weight-base; + line-height: 33px; + } + + + // Apply h4 style font to buttons and inputs + .form-control { + font-size: 22px; + font-weight: 400; + line-height: 33px; + } + .btn { + font-size: 22px; + font-weight: 350; + line-height: 33px; + } + + + //// Desktop + @media (min-width:768px) { + h1 { + font-size: 48px; + font-weight: 600; + line-height: 72px; + } + + + h3 { + font-size: 32px; + line-height: 50px; + } + + + h4 { + font-size: 24px; + line-height: 36px; + } + + + .form-control { + font-size: 24px; + font-weight: 400; + line-height: 36px; + } + .btn { + font-size: 24px; + font-weight: 350; + line-height: 36px; + } + // h4 style on nav-links + .nav-link { + font-size: 24px; + } + + + h5 { + font-size: 20px; + font-weight: $font-weight-base; + line-height: 30px; + } + * { + font-family: $font-family-base; + font-weight: $font-weight-base; + font-size: 18px; + } + } + \ No newline at end of file diff --git a/packages/frontend/components/Buttons/LargeToggle.vue b/packages/frontend/components/Buttons/LargeToggle.vue new file mode 100644 index 0000000..682c210 --- /dev/null +++ b/packages/frontend/components/Buttons/LargeToggle.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/packages/frontend/components/CreateContainer.vue b/packages/frontend/components/Forms/CreateContainer.vue similarity index 66% rename from packages/frontend/components/CreateContainer.vue rename to packages/frontend/components/Forms/CreateContainer.vue index 2238209..8b4b26a 100644 --- a/packages/frontend/components/CreateContainer.vue +++ b/packages/frontend/components/Forms/CreateContainer.vue @@ -1,39 +1,71 @@ + + \ No newline at end of file diff --git a/packages/frontend/components/gosqas_main_logo.svg b/packages/frontend/components/gosqas_main_logo.svg index f15a355..9e773b5 100644 --- a/packages/frontend/components/gosqas_main_logo.svg +++ b/packages/frontend/components/gosqas_main_logo.svg @@ -1 +1,3 @@ + + \ No newline at end of file diff --git a/packages/frontend/layouts/default.vue b/packages/frontend/layouts/default.vue index 9f3b093..2a01c27 100644 --- a/packages/frontend/layouts/default.vue +++ b/packages/frontend/layouts/default.vue @@ -1,9 +1,9 @@ \ No newline at end of file + + + + \ No newline at end of file diff --git a/packages/frontend/nuxt.config.ts b/packages/frontend/nuxt.config.ts index fe83b86..e841780 100644 --- a/packages/frontend/nuxt.config.ts +++ b/packages/frontend/nuxt.config.ts @@ -1,3 +1,17 @@ +// nuxt.config.ts -- default configuration f0r framework +// Copyright (C) 2024 GOSQAS Team +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . // https://nuxt.com/docs/api/configuration/nuxt-config const globalBaseUrl = 'https://gosqasbe.azurewebsites.net/api'; diff --git a/packages/frontend/package-lock.json b/packages/frontend/package-lock.json index 0d7de9a..9446068 100644 --- a/packages/frontend/package-lock.json +++ b/packages/frontend/package-lock.json @@ -1,3 +1,4 @@ + { "name": "nuxt-app", "lockfileVersion": 3, @@ -13,7 +14,7 @@ "bootstrap5-toggle": "^5.1.1", "mitt": "^3.0.1", "nuxt": "^3.11.2", - "qrcode-vue3": "^1.6.8", + "qrcode-generator": "^1.4.4", "vue": "^3.4.27", "vue-router": "^4.3.2" }, @@ -419,22 +420,21 @@ } }, "node_modules/@azure/identity": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.0.tgz", - "integrity": "sha512-ve3aYv79qXOJ8wRxQ5jO0eIz2DZ4o0TyME4m4vlGV5YyePddVZ+pFMzusAMODNAflYAAv1cBIhKnd4xytmXyig==", - "license": "MIT", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.3.0.tgz", + "integrity": "sha512-LHZ58/RsIpIWa4hrrE2YuJ/vzG1Jv9f774RfTTAVDZDriubvJ0/S5u4pnw4akJDlS0TiJb6VMphmVUFsWmgodQ==", "optional": true, "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.5.0", - "@azure/core-client": "^1.4.0", + "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.1.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^3.11.1", - "@azure/msal-node": "^2.6.6", + "@azure/msal-node": "^2.9.2", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", @@ -492,13 +492,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.1.tgz", - "integrity": "sha512-I9Pc78mXwj/K8ydSgTfZ5A20vQ/xvfgnnhSCkienZ29b59zFy/hb2Vxmc6Gvg5pNkimSqkPnAtGoBMxYOLBm1A==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", + "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", "devOptional": true, - "license": "MIT", "dependencies": { - "@azure/msal-common": "14.11.0", + "@azure/msal-common": "14.12.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -507,21 +506,19 @@ } }, "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "14.11.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.11.0.tgz", - "integrity": "sha512-B6+IKLFs7Lsr06vjX8dPN61ENpTgiFrHf+CVo1UasHcmk5uEOq5D4thrbjsauKX+xtFryYsCDtznVDmWS4/sCg==", + "version": "14.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.12.0.tgz", + "integrity": "sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/static-web-apps-cli": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@azure/static-web-apps-cli/-/static-web-apps-cli-1.1.8.tgz", - "integrity": "sha512-6/yCxXjAfvBwsT8eqgc5asQFbdKwQJMN8PJmSJKenbQTYgAarwBdNClEvm6sVAWKiixKmIOjvQvZP08UI9ofKw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@azure/static-web-apps-cli/-/static-web-apps-cli-1.1.10.tgz", + "integrity": "sha512-X8Pbw/OOurGbAh6gtXth1aWxMBIo0bStJDvQ2k8poqOw8a7JZnMYJM9VWeVqj5m9bc0/GI9nuSxip9lJdBfXXQ==", "dev": true, - "license": "MIT", "dependencies": { "@azure/arm-appservice": "^12.0.0", "@azure/arm-resources": "^5.0.1", @@ -5339,11 +5336,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -7312,9 +7309,9 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11326,14 +11323,6 @@ "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==" }, - "node_modules/qrcode-vue3": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/qrcode-vue3/-/qrcode-vue3-1.6.8.tgz", - "integrity": "sha512-LtMnwKWi58ZqHbXBcsTF/VxDYhI6RrBIrDQw8fbDVlO8p5tJBZa7TaIaVYLY937vKO2WCEBmOKksGlpm5ccEIg==", - "dependencies": { - "qrcode-generator": "^1.4.4" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14093,9 +14082,9 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index adcc7b1..aded63e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -1,3 +1,4 @@ + { "name": "nuxt-app", "private": true, @@ -22,7 +23,7 @@ "bootstrap5-toggle": "^5.1.1", "mitt": "^3.0.1", "nuxt": "^3.11.2", - "qrcode-vue3": "^1.6.8", + "qrcode-generator": "^1.4.4", "vue": "^3.4.27", "vue-router": "^4.3.2" } diff --git a/packages/frontend/pages/about.vue b/packages/frontend/pages/about.vue new file mode 100644 index 0000000..30bd688 --- /dev/null +++ b/packages/frontend/pages/about.vue @@ -0,0 +1,85 @@ + + \ No newline at end of file diff --git a/packages/frontend/pages/contact.vue b/packages/frontend/pages/contact.vue new file mode 100644 index 0000000..e6dfbc8 --- /dev/null +++ b/packages/frontend/pages/contact.vue @@ -0,0 +1,107 @@ + + \ No newline at end of file diff --git a/packages/frontend/pages/device/[deviceKey].vue b/packages/frontend/pages/device/[deviceKey].vue index c5c875d..4bae6b0 100644 --- a/packages/frontend/pages/device/[deviceKey].vue +++ b/packages/frontend/pages/device/[deviceKey].vue @@ -1,17 +1,31 @@ + @@ -54,7 +65,7 @@ import GenerateQRCode from '~/components/GenerateQRCode.vue'; import KeyList from '~/components/KeyList.vue'; import { getProvenance } from '~/services/azureFuncs'; -let deviceRecord; +let deviceRecord: any; // Here we are are going to want to read the device, // but not all the provenance. We will use this to load @@ -74,7 +85,14 @@ export default { isLoading: true, hasReportingKey: false, childKeys: [], + loadingKey: 0, }}, + methods: { + //This method helps rerendering the site + forceRerender() { + this.loadingKey += 1; + } + }, async mounted() { try { const route = useRoute(); diff --git a/packages/frontend/pages/index.vue b/packages/frontend/pages/index.vue index 924b4fc..4e2389b 100644 --- a/packages/frontend/pages/index.vue +++ b/packages/frontend/pages/index.vue @@ -1,67 +1,174 @@ + + + \ No newline at end of file diff --git a/packages/frontend/pages/provenance/[deviceKey].vue b/packages/frontend/pages/provenance/[deviceKey].vue index f9e28ce..7bcd66e 100644 --- a/packages/frontend/pages/provenance/[deviceKey].vue +++ b/packages/frontend/pages/provenance/[deviceKey].vue @@ -1,3 +1,18 @@ + + -
+
Child Keys: -
-
+
+
+
@@ -59,12 +77,11 @@