From 112aa99f1ed9d390e6d6b37ca8ab3223e683b3e1 Mon Sep 17 00:00:00 2001 From: nobswebdev Date: Mon, 26 Oct 2026 19:13:15 +0100 Subject: [PATCH] add ViewerView decrypt from URL fragment via view store --- frontend/src/stores/view.ts | 48 +++++++++++++++++ .../upload/GetZkUploadCiphertextResult.ts | 8 +++ frontend/src/views/ViewerView.vue | 54 +++++++++++++++++-- 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 frontend/src/stores/view.ts create mode 100644 frontend/src/types/upload/GetZkUploadCiphertextResult.ts diff --git a/frontend/src/stores/view.ts b/frontend/src/stores/view.ts new file mode 100644 index 0000000..476d07c --- /dev/null +++ b/frontend/src/stores/view.ts @@ -0,0 +1,48 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { api } from '@/plugins/axios'; +import type { GetZkUploadCiphertextResult } from '@/types/upload/GetZkUploadCiphertextResult'; +import { base64ToBytes } from '@/utils/encoding/base64ToBytes'; +import { toArrayBuffer } from '@/utils/toArrayBuffer'; +import { decryptZkUpload } from '@/utils/upload/decryptZkUpload'; + +export const useViewStore = defineStore('view', () => { + const imageObjectUrl = ref(null); + + const clearImage = (): void => { + if (imageObjectUrl.value) { + URL.revokeObjectURL(imageObjectUrl.value); + imageObjectUrl.value = null; + } + }; + + const fetchZkCiphertext = async (uploadId: string): Promise => { + const { data } = await api.get(`/uploads/${uploadId}`); + + return data; + }; + + const loadZk = async (payload: GetZkUploadCiphertextResult, fragmentKey: string): Promise => { + clearImage(); + + const iv = base64ToBytes(payload.iv); + const ciphertext = base64ToBytes(payload.ciphertext); + + const plaintext = await decryptZkUpload(ciphertext, iv, fragmentKey); + + const buffer = toArrayBuffer(plaintext); + + if (!buffer) { + throw new Error('Could not build image blob'); + } + + imageObjectUrl.value = URL.createObjectURL(new Blob([buffer])); + }; + + return { + imageObjectUrl, + fetchZkCiphertext, + loadZk, + clearImage + }; +}); diff --git a/frontend/src/types/upload/GetZkUploadCiphertextResult.ts b/frontend/src/types/upload/GetZkUploadCiphertextResult.ts new file mode 100644 index 0000000..e26d889 --- /dev/null +++ b/frontend/src/types/upload/GetZkUploadCiphertextResult.ts @@ -0,0 +1,8 @@ +export type GetZkUploadCiphertextResult = { + iv: string; + ciphertext: string; + passwordProtected: boolean; + wrappedDek?: string; + wrapIv?: string; + salt?: string; +}; diff --git a/frontend/src/views/ViewerView.vue b/frontend/src/views/ViewerView.vue index 0d1b36c..440248f 100644 --- a/frontend/src/views/ViewerView.vue +++ b/frontend/src/views/ViewerView.vue @@ -1,6 +1,54 @@ + +