enable/disable e2e encryption

This commit is contained in:
2023-05-28 21:45:47 +02:00
parent 77f5bafa2f
commit b4eab2d8e8
9 changed files with 372 additions and 59 deletions

167
package-lock.json generated
View File

@@ -29,9 +29,11 @@
"daisyui": "^2.51.6", "daisyui": "^2.51.6",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"dompurify": "^3.0.2", "dompurify": "^3.0.2",
"file-saver": "^2.0.5",
"firebase": "^9.22.0", "firebase": "^9.22.0",
"firebaseui": "^6.0.2", "firebaseui": "^6.0.2",
"hamburgers": "^1.2.1", "hamburgers": "^1.2.1",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"marked": "^4.3.0", "marked": "^4.3.0",
"shortid": "^2.2.16", "shortid": "^2.2.16",
@@ -45,6 +47,7 @@
"@tsconfig/node18": "^2.0.1", "@tsconfig/node18": "^2.0.1",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/dompurify": "^3.0.2", "@types/dompurify": "^3.0.2",
"@types/file-saver": "^2.0.5",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "^20.2.1", "@types/node": "^20.2.1",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
@@ -2069,6 +2072,12 @@
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true "dev": true
}, },
"node_modules/@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
"node_modules/@types/glob": { "node_modules/@types/glob": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
@@ -4224,8 +4233,7 @@
"node_modules/core-util-is": { "node_modules/core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
"dev": true
}, },
"node_modules/cors": { "node_modules/cors": {
"version": "2.8.5", "version": "2.8.5",
@@ -5664,6 +5672,11 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/file-uri-to-path": { "node_modules/file-uri-to-path": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz",
@@ -7028,6 +7041,11 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
@@ -7578,8 +7596,7 @@
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
"dev": true
}, },
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
@@ -7839,6 +7856,44 @@
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/jszip/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/jszip/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/jwa": { "node_modules/jwa": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
@@ -7954,6 +8009,14 @@
"libsodium": "^0.7.11" "libsodium": "^0.7.11"
} }
}, },
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -9358,6 +9421,11 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -9825,8 +9893,7 @@
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"dev": true
}, },
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
@@ -10733,6 +10800,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -13980,6 +14052,12 @@
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true "dev": true
}, },
"@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
"@types/glob": { "@types/glob": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
@@ -15606,8 +15684,7 @@
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
"dev": true
}, },
"cors": { "cors": {
"version": "2.8.5", "version": "2.8.5",
@@ -16740,6 +16817,11 @@
"flat-cache": "^3.0.4" "flat-cache": "^3.0.4"
} }
}, },
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"file-uri-to-path": { "file-uri-to-path": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz",
@@ -17806,6 +17888,11 @@
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"dev": true "dev": true
}, },
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"immutable": { "immutable": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
@@ -18196,8 +18283,7 @@
"isarray": { "isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
"dev": true
}, },
"isexe": { "isexe": {
"version": "2.0.0", "version": "2.0.0",
@@ -18427,6 +18513,46 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
},
"dependencies": {
"readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"jwa": { "jwa": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
@@ -18535,6 +18661,14 @@
"libsodium": "^0.7.11" "libsodium": "^0.7.11"
} }
}, },
"lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"requires": {
"immediate": "~3.0.5"
}
},
"lilconfig": { "lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -19606,6 +19740,11 @@
"netmask": "^2.0.2" "netmask": "^2.0.2"
} }
}, },
"pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"parent-module": { "parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -19861,8 +20000,7 @@
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"dev": true
}, },
"progress": { "progress": {
"version": "2.0.3", "version": "2.0.3",
@@ -20570,6 +20708,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"setprototypeof": { "setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",

View File

@@ -36,9 +36,11 @@
"daisyui": "^2.51.6", "daisyui": "^2.51.6",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"dompurify": "^3.0.2", "dompurify": "^3.0.2",
"file-saver": "^2.0.5",
"firebase": "^9.22.0", "firebase": "^9.22.0",
"firebaseui": "^6.0.2", "firebaseui": "^6.0.2",
"hamburgers": "^1.2.1", "hamburgers": "^1.2.1",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"marked": "^4.3.0", "marked": "^4.3.0",
"shortid": "^2.2.16", "shortid": "^2.2.16",
@@ -52,6 +54,7 @@
"@tsconfig/node18": "^2.0.1", "@tsconfig/node18": "^2.0.1",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/dompurify": "^3.0.2", "@types/dompurify": "^3.0.2",
"@types/file-saver": "^2.0.5",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "^20.2.1", "@types/node": "^20.2.1",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",

View File

@@ -17,7 +17,7 @@ const emit = defineEmits<{
<template #activator="{ open }"> <template #activator="{ open }">
<UIButton size="sm" @click="open"><i class="fas fa-fw fa-trash" /></UIButton> <UIButton size="sm" @click="open"><i class="fas fa-fw fa-trash" /></UIButton>
</template> </template>
<template #title>Delete note</template> <template #title><i class="fas fa-fw fa-trash mr-2" />Delete note</template>
<template #default>Are you sure you want to delete this note?</template> <template #default>Are you sure you want to delete this note?</template>
<template #actions="{ close }"> <template #actions="{ close }">
<UIButton size="sm" color="primary" @click="emit('delete', close)">Delete notes</UIButton> <UIButton size="sm" color="primary" @click="emit('delete', close)">Delete notes</UIButton>
@@ -28,7 +28,7 @@ const emit = defineEmits<{
<template #activator="{ open }"> <template #activator="{ open }">
<UIButton size="sm" @click="open"><i class="fas fa-fw fa-sitemap" /></UIButton> <UIButton size="sm" @click="open"><i class="fas fa-fw fa-sitemap" /></UIButton>
</template> </template>
<template #title>Set root note</template> <template #title><i class="fas fa-fw fa-sitemap mr-2" />Set root note</template>
<template #default>Are you sure you want to set this note as root note?</template> <template #default>Are you sure you want to set this note as root note?</template>
<template #actions="{ close }"> <template #actions="{ close }">
<UIButton size="sm" @click="close">Cancel</UIButton> <UIButton size="sm" @click="close">Cancel</UIButton>

View File

@@ -1,6 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { user } from '@/composables/useFirebase' import { user } from '@/composables/useFirebase'
import { encryptionKey, enableEncryption, disableEncryption } from '@/composables/useEncryption'
import { notes } from '@/composables/useNotes'
import { format } from 'date-fns' import { format } from 'date-fns'
import JSZip from 'jszip'
import FileSaver from 'file-saver'
const verificationEmailSent = ref(false) const verificationEmailSent = ref(false)
const sendVerificationMail = () => { const sendVerificationMail = () => {
@@ -8,18 +12,49 @@ const sendVerificationMail = () => {
user.value.sendEmailVerification() user.value.sendEmailVerification()
verificationEmailSent.value = true verificationEmailSent.value = true
} }
const exportNotes = async () => {
const zip = new JSZip()
notes.value.forEach((note) => {
zip.file(`${note.title}-${note.id}.md`, note.content)
})
const blob = await zip.generateAsync({ type: 'blob' })
const currentDate = format(new Date(), 'yyyyMMdd')
FileSaver.saveAs(blob, `contexted-${user.value?.email}-${currentDate}.zip`)
}
const showEncryptionDialog = ref(false)
watch(showEncryptionDialog, () => {
passphrase.value = ''
})
const passphrase = ref('')
const toggleEncryptionError = ref('')
const encryptionEnabled = computed(() => Boolean(encryptionKey.value))
const toggleEncryption = async () => {
const result = encryptionEnabled.value
? await disableEncryption(passphrase.value)
: await enableEncryption(passphrase.value)
if (typeof result === 'string') {
toggleEncryptionError.value = result
} else {
toggleEncryptionError.value = ''
showEncryptionDialog.value = false
}
}
</script> </script>
<template> <template>
<UIModal size="lg"> <UIModal size="lg">
<template #activator="{ open }"> <template #activator="{ open }">
<UIDropdownItem @click="open"> <UIDropdownItem @click="open">
<i class="fa-fw fa-solid fa-sliders" /> <i class="fa-fw fa-solid fa-sliders" />
Settings Account settings
</UIDropdownItem> </UIDropdownItem>
</template> </template>
<template #title> <template #title>
<i class="fa-fw fa-solid fa-sliders mr-2" /> <i class="fa-fw fa-solid fa-sliders mr-2" />
Settings Account settings
</template> </template>
<template #default> <template #default>
<div class="space-y-2"> <div class="space-y-2">
@@ -62,17 +97,75 @@ const sendVerificationMail = () => {
<UICard> <UICard>
<template #title>Notes</template> <template #title>Notes</template>
<template #default> <template #default>
<div class="w-full flex-row sm:flex items-center"> <div class="items-top w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">Export notes</div> <div class="font-bold sm:w-4/12">Export notes</div>
<UIButton size="sm" color="secondary"><i class="fa-fw fa-solid fa-file-export mr-2"></i>Export notes</UIButton> <UIButton size="sm" @click="exportNotes">
<i class="fa-fw fa-solid fa-file-export mr-2"></i>
Export notes
</UIButton>
</div> </div>
<div class="w-full flex-row sm:flex items-center"> <div class="items-top w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">Delete account</div> <div class="font-bold sm:w-4/12">Delete account</div>
<UIButton size="sm" color="error"><i class="fa-fw fa-solid fa-trash mr-2"></i>Delete account</UIButton> <UIButton size="sm" color="error">
<i class="fa-fw fa-solid fa-trash mr-2"></i>
Delete account
</UIButton>
</div> </div>
<div class="w-full flex-row sm:flex items-center"> <div class="items-top w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">End-to-end encryption</div> <div class="font-bold sm:w-4/12">End-to-end encryption</div>
<UIButton size="sm" color="secondary"><i class="fa-fw fa-solid fa-key mr-2"></i>Enable end-to-end encryption</UIButton> <div>
<template v-if="!encryptionEnabled">
<UIButton
size="sm"
@click="showEncryptionDialog = true"
v-if="showEncryptionDialog === false"
>
<i class="fa-fw fa-solid fa-key mr-2"></i>
Enable end-to-end encryption
</UIButton>
</template>
<template v-else>
<UIButton
size="sm"
@click="showEncryptionDialog = true"
v-if="showEncryptionDialog === false"
>
<i class="fa-fw fa-solid fa-key mr-2"></i>
Disable end-to-end encryption
</UIButton>
</template>
<UIAlert color="info" density="compact" v-if="showEncryptionDialog">
<div class="space-y-2">
<div>
Enter your passphrase to
{{ encryptionEnabled ? 'disable' : 'enable' }}
encryption
</div>
<UIInputText
size="sm"
type="password"
:color="toggleEncryptionError ? 'error' : 'regular'"
v-model="passphrase"
class="w-full"
/>
<UIAlert density="compact" color="error" v-if="toggleEncryptionError">
<i class="fa-solid fa-triangle-exclamation"></i>
{{ toggleEncryptionError }}
</UIAlert>
<div class="flex justify-end space-x-2">
<UIButton size="sm" @click="showEncryptionDialog = false">Close</UIButton>
<UIButton
:disabled="passphrase.length === 0"
size="sm"
color="primary"
@click="toggleEncryption"
>
{{ encryptionEnabled ? 'Disable' : 'Enable' }} encryption
</UIButton>
</div>
</div>
</UIAlert>
</div>
</div> </div>
</template> </template>
</UICard> </UICard>

View File

@@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
color?: 'info' | 'success' | 'warning' | 'error' color?: 'info' | 'success' | 'warning' | 'error'
density?: 'regular' | 'compact'
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
color: 'info' color: 'info',
density: 'regular'
}) })
const styleClass = computed(() => { const styleClass = computed(() => {
@@ -13,12 +15,17 @@ const styleClass = computed(() => {
'warning': 'dui-alert-warning', 'warning': 'dui-alert-warning',
'error': 'dui-alert-error' 'error': 'dui-alert-error'
} }
const densityVariants = {
'regular': 'py-4 px-4',
'compact': 'py-2 px-4'
}
const colorClass = colorVariants[props.color] const colorClass = colorVariants[props.color]
return [colorClass] const densityClass = densityVariants[props.density]
return [colorClass, densityClass]
}) })
</script> </script>
<template> <template>
<div class="dui-alert shadow-lg items-start" :class="styleClass"> <div class="dui-alert shadow-lg items-start" :class="styleClass">
<div class="flex items-center"><slot></slot></div> <div class="flex items-center w-full"><slot></slot></div>
</div> </div>
</template> </template>

View File

@@ -6,6 +6,7 @@ const props = withDefaults(
open?: boolean open?: boolean
persistent?: boolean persistent?: boolean
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
icon?: string
}>(), }>(),
{ {
open: false, open: false,

View File

@@ -1,6 +1,8 @@
import { doc, getDoc } from 'firebase/firestore' import { doc, getDoc, setDoc } from 'firebase/firestore'
import { user, db } from '@/composables/useFirebase' 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 } { function getClientKeysFromLocalStorage(): { [uid: string]: string } {
try { try {
@@ -42,20 +44,38 @@ export const verifyClientKey = (clientKey: ClientKey) => {
} }
} }
const encryptedEncryptionKey = ref<EncryptedEncryptionKey>() const removeClientKey = () => {
if (!user.value) return
const clientKeys = structuredClone(getClientKeysFromLocalStorage())
delete clientKeys[user.value?.uid]
localStorage.setItem('clientKeys', JSON.stringify(clientKeys))
}
const encryptedEncryptionKey = ref<EncryptedEncryptionKey | null>()
async function getEncryptedEncryptionKey(): Promise<EncryptedEncryptionKey | void> { async function getEncryptedEncryptionKey(): Promise<EncryptedEncryptionKey | void> {
if (!user.value || !db.value) return 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 return data?.key
} }
async function setEncryptedEncryptionKey(
encryptedEncryptionKey: EncryptedEncryptionKey | null
): Promise<void> {
if (!user.value || !db.value) return
const docRef = doc(db.value, 'encryptionKeys', user.value.uid)
await setDoc(docRef, { key: encryptedEncryptionKey })
}
export const encryptionKey = ref<EncryptionKey | null>() export const encryptionKey = ref<EncryptionKey | null>()
export async function getEncryptionKey() { export async function getEncryptionKey() {
encryptedEncryptionKey.value = (await getEncryptedEncryptionKey()) || undefined encryptedEncryptionKey.value = (await getEncryptedEncryptionKey()) || undefined
if (!encryptedEncryptionKey.value || !clientKey.value) return if (!encryptedEncryptionKey.value || !clientKey.value) {
encryptionKey.value = null
} else {
encryptionKey.value = decrypt(encryptedEncryptionKey.value, clientKey.value) encryptionKey.value = decrypt(encryptedEncryptionKey.value, clientKey.value)
}
} }
export const passphraseRequired = computed(() => { export const passphraseRequired = computed(() => {
@@ -63,11 +83,16 @@ export const passphraseRequired = computed(() => {
}) })
const decryptNote = (note: BaseNote, key: EncryptionKey) => { const decryptNote = (note: BaseNote, key: EncryptionKey) => {
try {
return { return {
...note, ...note,
title: decrypt(note.title, key), title: decrypt(note.title, key),
content: decrypt(note.content, key) content: decrypt(note.content, key)
} }
} catch (error: any) {
console.error(error)
return note
}
} }
export const decryptNotes = (notes: BaseNotes, encryptionKey: EncryptionKey) => { export const decryptNotes = (notes: BaseNotes, encryptionKey: EncryptionKey) => {
@@ -91,3 +116,34 @@ export const encryptNotes = (notes: BaseNotes, encryptionKey: EncryptionKey) =>
) )
return encryptedNotes 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)
}

View File

@@ -39,36 +39,42 @@ watchEffect(() => {
activeNotesSource.value = getSource() activeNotesSource.value = getSource()
}) })
const baseNotes = ref<BaseNotes>({}) export const baseNotes = ref<BaseNotes>({})
watch(
baseNotes,
async (newBaseNotes, oldBaseNotes) => {
if (!activeNotesSource.value || Object.keys(baseNotes.value).length === 0) return
console.log()
if (activeNotesSource.value === 'local') { const syncNotesLocal = (notes: BaseNotes) => {
localStorage.setItem('notes', JSON.stringify(baseNotes.value)) localStorage.setItem('notes', JSON.stringify(notes))
} else if (activeNotesSource.value === 'firebase') { }
export const syncNotesToFirebase = async (newNotes: BaseNotes, oldNotes?: BaseNotes) => {
if (!db.value) throw Error("Database undefined, can't sync to 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") if (!user.value) throw Error("User undefined, can't sync to Firebase")
const notes = encryptionKey.value const notes = encryptionKey.value
? encryptNotes(baseNotes.value, encryptionKey.value) ? encryptNotes(baseNotes.value, encryptionKey.value)
: baseNotes.value : baseNotes.value
const notesToDelete = Object.keys(oldBaseNotes).filter(
(x) => !Object.keys(newBaseNotes).includes(x)
)
try { try {
const docRef = doc(db.value, 'pages', user.value.uid) 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( await Promise.all(
notesToDelete.map((noteId: string) => { notesToDelete.map((noteId: string) => {
return updateDoc(docRef, { [noteId]: deleteField() }) return updateDoc(docRef, { [noteId]: deleteField() })
}) })
) )
}
await updateDoc(docRef, notes) await updateDoc(docRef, notes)
} catch (error: any) { } catch (error: any) {
console.error(error) console.error(error)
} }
}
watch(
baseNotes,
async (newBaseNotes, oldBaseNotes) => {
if (!activeNotesSource.value || Object.keys(baseNotes.value).length === 0) return
if (activeNotesSource.value === 'local') {
syncNotesLocal(baseNotes.value)
} else if (activeNotesSource.value === 'firebase') {
syncNotesToFirebase(newBaseNotes, oldBaseNotes)
} }
}, },
{ deep: true } { deep: true }

View File

@@ -17,3 +17,7 @@ export const decrypt = (encryptedMessage: string, key: string): string => {
export const encrypt = (unencryptedMessage: string, key: string): string => { export const encrypt = (unencryptedMessage: string, key: string): string => {
return CryptoJS.AES.encrypt(encryptionPrefix + unencryptedMessage, key).toString() return CryptoJS.AES.encrypt(encryptionPrefix + unencryptedMessage, key).toString()
} }
export const generateEncryptionKey = () => {
return CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex)
}