+
End-to-end encryption
-
Enable end-to-end encryption
+
+
+
+
+ Enable end-to-end encryption
+
+
+
+
+
+ Disable end-to-end encryption
+
+
+
+
+
+ Enter your passphrase to
+ {{ encryptionEnabled ? 'disable' : 'enable' }}
+ encryption
+
+
+
+
+ {{ toggleEncryptionError }}
+
+
+ Close
+
+ {{ encryptionEnabled ? 'Disable' : 'Enable' }} encryption
+
+
+
+
+
diff --git a/src/components/ui/UIAlert.vue b/src/components/ui/UIAlert.vue
index 7a8f7d3..c24d5f9 100644
--- a/src/components/ui/UIAlert.vue
+++ b/src/components/ui/UIAlert.vue
@@ -1,9 +1,11 @@
diff --git a/src/components/ui/UIModal.vue b/src/components/ui/UIModal.vue
index 1d4ff18..fc17302 100644
--- a/src/components/ui/UIModal.vue
+++ b/src/components/ui/UIModal.vue
@@ -6,6 +6,7 @@ const props = withDefaults(
open?: boolean
persistent?: boolean
size?: 'sm' | 'md' | 'lg'
+ icon?: string
}>(),
{
open: false,
diff --git a/src/composables/useEncryption.ts b/src/composables/useEncryption.ts
index 88d9389..d3c66de 100644
--- a/src/composables/useEncryption.ts
+++ b/src/composables/useEncryption.ts
@@ -1,6 +1,8 @@
-import { doc, getDoc } from 'firebase/firestore'
+import { doc, getDoc, setDoc } from 'firebase/firestore'
import { user, db } from '@/composables/useFirebase'
-import { decrypt, encrypt, calculateClientKey } from '@/utils/crypto'
+import { decrypt, encrypt, calculateClientKey, generateEncryptionKey } from '@/utils/crypto'
+import { preferredNotesSource } from '@/composables/useSettings'
+import { activeNotesSource, syncNotesToFirebase, baseNotes } from '@/composables/useNotes'
function getClientKeysFromLocalStorage(): { [uid: string]: string } {
try {
@@ -42,20 +44,38 @@ export const verifyClientKey = (clientKey: ClientKey) => {
}
}
-const encryptedEncryptionKey = ref
()
+const removeClientKey = () => {
+ if (!user.value) return
+ const clientKeys = structuredClone(getClientKeysFromLocalStorage())
+ delete clientKeys[user.value?.uid]
+ localStorage.setItem('clientKeys', JSON.stringify(clientKeys))
+}
+
+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()
+ const data = (await getDoc(doc(db.value, 'encryptionKeys', user.value.uid))).data()
return data?.key
}
+async function setEncryptedEncryptionKey(
+ encryptedEncryptionKey: EncryptedEncryptionKey | null
+): Promise {
+ if (!user.value || !db.value) return
+ const docRef = doc(db.value, 'encryptionKeys', user.value.uid)
+ await setDoc(docRef, { key: encryptedEncryptionKey })
+}
+
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)
+ if (!encryptedEncryptionKey.value || !clientKey.value) {
+ encryptionKey.value = null
+ } else {
+ encryptionKey.value = decrypt(encryptedEncryptionKey.value, clientKey.value)
+ }
}
export const passphraseRequired = computed(() => {
@@ -63,10 +83,15 @@ export const passphraseRequired = computed(() => {
})
const decryptNote = (note: BaseNote, key: EncryptionKey) => {
- return {
- ...note,
- title: decrypt(note.title, key),
- content: decrypt(note.content, key)
+ try {
+ return {
+ ...note,
+ title: decrypt(note.title, key),
+ content: decrypt(note.content, key)
+ }
+ } catch (error: any) {
+ console.error(error)
+ return note
}
}
@@ -91,3 +116,34 @@ export const encryptNotes = (notes: BaseNotes, encryptionKey: EncryptionKey) =>
)
return encryptedNotes
}
+
+export const verifyPassphrase = (passphrase: string) => {
+ const calculatedClientKey = calculateClientKey(passphrase)
+ return calculatedClientKey === clientKey.value
+}
+
+export const disableEncryption = async (passphrase: string) => {
+ if (!encryptionKey.value) return "Encryption key doesn't exist."
+ if (!verifyPassphrase(passphrase)) return 'Passphrase is incorrect.'
+ preferredNotesSource.value = 'firebase'
+ if (activeNotesSource.value !== 'firebase') throw Error('Something went wrong.')
+ await setEncryptedEncryptionKey(null)
+ encryptedEncryptionKey.value = null
+ encryptionKey.value = undefined
+ removeClientKey()
+ await syncNotesToFirebase(baseNotes.value)
+ getEncryptionKey()
+}
+
+export const enableEncryption = async (passphrase: string) => {
+ preferredNotesSource.value = 'firebase'
+ if (activeNotesSource.value !== 'firebase') throw Error('Something went wrong.')
+ const candidateEncryptionKey = generateEncryptionKey()
+ const candidateClientKey = calculateClientKey(passphrase)
+ const candidateEncryptedEncryptionKey = encrypt(candidateEncryptionKey, candidateClientKey)
+ await setEncryptedEncryptionKey(candidateEncryptedEncryptionKey)
+ encryptedEncryptionKey.value = candidateEncryptedEncryptionKey
+ encryptionKey.value = candidateEncryptionKey
+ setClientKey(passphrase)
+ syncNotesToFirebase(baseNotes.value)
+}
diff --git a/src/composables/useNotes.ts b/src/composables/useNotes.ts
index a3847b7..b188ce0 100644
--- a/src/composables/useNotes.ts
+++ b/src/composables/useNotes.ts
@@ -39,36 +39,42 @@ watchEffect(() => {
activeNotesSource.value = getSource()
})
-const baseNotes = ref({})
+export const baseNotes = ref({})
+
+const syncNotesLocal = (notes: BaseNotes) => {
+ localStorage.setItem('notes', JSON.stringify(notes))
+}
+
+export const syncNotesToFirebase = async (newNotes: BaseNotes, oldNotes?: BaseNotes) => {
+ if (!db.value) throw Error("Database undefined, can't sync to Firebase")
+ if (!user.value) throw Error("User undefined, can't sync to Firebase")
+ const notes = encryptionKey.value
+ ? encryptNotes(baseNotes.value, encryptionKey.value)
+ : baseNotes.value
+ try {
+ const docRef = doc(db.value, 'pages', user.value.uid)
+ if (oldNotes) {
+ const notesToDelete = Object.keys(oldNotes).filter((x) => !Object.keys(newNotes).includes(x))
+ await Promise.all(
+ notesToDelete.map((noteId: string) => {
+ return updateDoc(docRef, { [noteId]: deleteField() })
+ })
+ )
+ }
+ await updateDoc(docRef, notes)
+ } catch (error: any) {
+ console.error(error)
+ }
+}
+
watch(
baseNotes,
async (newBaseNotes, oldBaseNotes) => {
if (!activeNotesSource.value || Object.keys(baseNotes.value).length === 0) return
- console.log()
-
if (activeNotesSource.value === 'local') {
- localStorage.setItem('notes', JSON.stringify(baseNotes.value))
+ syncNotesLocal(baseNotes.value)
} else if (activeNotesSource.value === 'firebase') {
- if (!db.value) throw Error("Database undefined, can't sync to Firebase")
- if (!user.value) throw Error("User undefined, can't sync to Firebase")
- const notes = encryptionKey.value
- ? encryptNotes(baseNotes.value, encryptionKey.value)
- : baseNotes.value
-
- const notesToDelete = Object.keys(oldBaseNotes).filter(
- (x) => !Object.keys(newBaseNotes).includes(x)
- )
- try {
- const docRef = doc(db.value, 'pages', user.value.uid)
- await Promise.all(
- notesToDelete.map((noteId: string) => {
- return updateDoc(docRef, { [noteId]: deleteField() })
- })
- )
- await updateDoc(docRef, notes)
- } catch (error: any) {
- console.error(error)
- }
+ syncNotesToFirebase(newBaseNotes, oldBaseNotes)
}
},
{ deep: true }
diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts
index 6cd3d05..371b12e 100644
--- a/src/utils/crypto.ts
+++ b/src/utils/crypto.ts
@@ -17,3 +17,7 @@ export const decrypt = (encryptedMessage: string, key: string): string => {
export const encrypt = (unencryptedMessage: string, key: string): string => {
return CryptoJS.AES.encrypt(encryptionPrefix + unencryptedMessage, key).toString()
}
+
+export const generateEncryptionKey = () => {
+ return CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex)
+}