From aa77296d00fc06ea30a3057e43be4d404ba0c056 Mon Sep 17 00:00:00 2001 From: Marco Crapts Date: Tue, 23 May 2023 00:44:51 +0200 Subject: [PATCH] passphrase prompt --- components.d.ts | 1 + src/App.vue | 46 ++++++++++++++++++++++-- src/components/Modal.vue | 13 +++++-- src/composables/useEncryption.ts | 60 +++++++++++++++++++++++++------- src/composables/useNotes.ts | 16 ++++----- src/types.d.ts | 1 + 6 files changed, 110 insertions(+), 27 deletions(-) diff --git a/components.d.ts b/components.d.ts index f7bba81..5cd708e 100644 --- a/components.d.ts +++ b/components.d.ts @@ -23,6 +23,7 @@ declare module '@vue/runtime-core' { SearchBar: typeof import('./src/components/Search/SearchBar.vue')['default'] SearchResult: typeof import('./src/components/Search/SearchResult.vue')['default'] SideBar: typeof import('./src/components/SideBar.vue')['default'] + SideBar2: typeof import('./src/components/SideBar2.vue')['default'] SideBarMenu: typeof import('./src/components/SideBar/SideBarMenu.vue')['default'] SideBarMenuItem: typeof import('./src/components/SideBar/SideBarMenuItem.vue')['default'] SkeletonNote: typeof import('./src/components/Skeleton/SkeletonNote.vue')['default'] diff --git a/src/App.vue b/src/App.vue index 13015a3..7ff053b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,12 @@ @@ -31,9 +59,10 @@ provide('loading', loading) @set-view-mode="(viewMode) => (activeViewMode = viewMode)" @collapse="(collapse) => (sideBarCollapsed = collapse)" class="mt-[50px] px-3 py-6" + :class="sideBarCollapsed && 'max-sm:hidden'" />
diff --git a/src/components/Modal.vue b/src/components/Modal.vue index 4aa9f05..f79d4ce 100644 --- a/src/components/Modal.vue +++ b/src/components/Modal.vue @@ -4,13 +4,20 @@ import { onClickOutside } from '@vueuse/core' const props = withDefaults( defineProps<{ open?: boolean + persistent?: boolean }>(), { - open: false + open: false, + persistent: false } ) -const show = ref(Boolean(props.open)) +const show = ref(false) +watch( + () => props.open, + () => (show.value = props.open), + { immediate: true } +) const modal = ref(null) const modalBox = ref(null) @@ -25,7 +32,7 @@ const close = (): Promise => { const slotProps = { open, close } -onClickOutside(modalBox, () => close()) +if (!props.persistent) onClickOutside(modalBox, () => close()) const onEnter = (el: Element, done: () => void): void => { setTimeout(() => { diff --git a/src/composables/useEncryption.ts b/src/composables/useEncryption.ts index 702d95c..81b4029 100644 --- a/src/composables/useEncryption.ts +++ b/src/composables/useEncryption.ts @@ -2,7 +2,7 @@ import { doc, getDoc } from 'firebase/firestore' import { user, db } from '@/composables/useFirebase' import { decrypt, calculateClientKey } from '@/utils/crypto' -function getClientKeysFromLocalStorage() { +function getClientKeysFromLocalStorage(): { [uid: string]: string } { try { return JSON.parse(localStorage.getItem('clientKeys') || '{}') } catch (e) { @@ -10,24 +10,58 @@ function getClientKeysFromLocalStorage() { } } -export const getClientKey = (): ClientKey | void => { +export const clientKey = ref() + +export const getClientKey = () => { if (!user.value) return const clientKeys = getClientKeysFromLocalStorage() - const clientKey = clientKeys[user.value?.uid] || calculateClientKey('test') - return clientKey + clientKey.value = clientKeys[user.value?.uid] } -export async function getEncryptionKey(): Promise { - if (!user.value) return - const clientKey = getClientKey() - if (!db.value || !clientKey) return - const data = (await getDoc(doc(db.value, 'encryptionKeys', user.value?.uid || ''))).data() - if (!data) return - const { key } = data - const encryptionKey: EncryptionKey = decrypt(key, clientKey) - return encryptionKey +export const setClientKey = (passphrase: string) => { + const calculatedClientKey = calculateClientKey(passphrase) + const verified = verifyClientKey(calculatedClientKey) + if (!user.value || !verified) return + const clientKeys = getClientKeysFromLocalStorage() + clientKeys[user.value.uid] = calculatedClientKey + localStorage.setItem('clientKeys', JSON.stringify(clientKeys)) + clientKey.value = calculatedClientKey + getEncryptionKey() + return true } +export const verifyClientKey = (clientKey: ClientKey) => { + try { + if (!encryptedEncryptionKey.value) throw new Error('Encryption key is null') + if (!clientKey) throw new Error('Client key is null') + decrypt(encryptedEncryptionKey.value, clientKey) + return true + } catch (e) { + console.log(e) + return false + } +} + +const encryptedEncryptionKey = ref() + +async function getEncryptedEncryptionKey(): Promise { + if (!user.value || !db.value) return + const data = (await getDoc(doc(db.value, 'encryptionKeys', user.value?.uid || ''))).data() + return data?.key +} + +export const encryptionKey = ref() + +export async function getEncryptionKey() { + encryptedEncryptionKey.value = (await getEncryptedEncryptionKey()) || undefined + if (!encryptedEncryptionKey.value || !clientKey.value) return + encryptionKey.value = decrypt(encryptedEncryptionKey.value, clientKey.value) +} + +export const passphraseRequired = computed(() => { + return Boolean(encryptedEncryptionKey.value && !clientKey.value) +}) + const decryptNote = (note: BaseNote, key: EncryptionKey) => { return { ...note, diff --git a/src/composables/useNotes.ts b/src/composables/useNotes.ts index efdaf8b..04b8e20 100644 --- a/src/composables/useNotes.ts +++ b/src/composables/useNotes.ts @@ -3,19 +3,19 @@ import { useTitle } from '@vueuse/core' import { doc, getDoc } from 'firebase/firestore' import { viewModes, activeViewMode } from '@/composables/useViewMode' import { initialized, user, db } from '@/composables/useFirebase' -import { decryptNotes, getEncryptionKey } from '@/composables/useEncryption' +import { decryptNotes, encryptionKey } from '@/composables/useEncryption' import { defaultNotes } from '@/utils/defaultNotes' import { mdToHtml } from '@/utils/markdown' import { getAllMatches } from '@/utils/helpers' -const notesSources = computed(() => ({ +export const notesSources = computed(() => ({ local: true, firebase: initialized.value && user.value })) type notesSourceValues = keyof typeof notesSources.value | null -const activeNotesSource = ref(null) +export const activeNotesSource = ref(null) watchEffect(() => { const getSource = (): notesSourceValues => { @@ -29,14 +29,14 @@ const baseNotes = ref<{ [noteId: string]: BaseNote }>({}) watch( baseNotes, () => { - console.log(`Sync base notes with ${activeNotesSource.value}`, baseNotes.value) - if (!activeNotesSource.value) return + if (!activeNotesSource.value || Object.keys(baseNotes.value).length === 0) return if (activeNotesSource.value === 'local') { console.log('sync with local') localStorage.setItem('notes', JSON.stringify(baseNotes.value)) } else if (activeNotesSource.value === 'firebase') { console.log('sync with firebase') } + console.log(`Sync base notes with ${activeNotesSource.value}`, baseNotes.value) }, { deep: true } ) @@ -209,7 +209,7 @@ const parseBaseNotes = (notes: BaseNotes): BaseNotes => { } watch( - activeNotesSource, + [activeNotesSource, encryptionKey], async () => { if (!activeNotesSource.value) return baseNotes.value = {} @@ -221,11 +221,11 @@ watch( console.log(error) } } else if (activeNotesSource.value === 'firebase' && db.value) { + if (encryptionKey.value === undefined) return const firebaseNotes = ( await getDoc(doc(db.value, 'pages', user.value?.uid || '')) ).data() as { [noteId: string]: any } - const encryptionKey = await getEncryptionKey() - notes = encryptionKey ? decryptNotes(firebaseNotes, encryptionKey) : firebaseNotes + notes = encryptionKey.value ? decryptNotes(firebaseNotes, encryptionKey.value) : firebaseNotes console.log('get notes from firebase', notes) } baseNotes.value = parseBaseNotes(notes) diff --git a/src/types.d.ts b/src/types.d.ts index 458eed4..d865b2f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -42,6 +42,7 @@ declare global { } type ClientKey = string + type EncryptedEncryptionKey = string type EncryptionKey = string } export {}