@fiduswriter/editor 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collab/chat.js +1 -1
- package/dist/collab/chat.js.map +1 -1
- package/dist/collab/doc.js +8 -8
- package/dist/collab/doc.js.map +1 -1
- package/dist/dialogs/figure.js +1 -1
- package/dist/dialogs/figure.js.map +1 -1
- package/dist/document_template/exporter.js +1 -1
- package/dist/document_template/exporter.js.map +1 -1
- package/dist/document_template/index.js +2 -2
- package/dist/document_template/index.js.map +1 -1
- package/dist/e2ee/snapshot-manager.js +5 -5
- package/dist/e2ee/snapshot-manager.js.map +1 -1
- package/dist/exporter/native/copy.js +117 -4
- package/dist/exporter/native/copy.js.map +1 -1
- package/dist/footnotes/editor.js +1 -1
- package/dist/footnotes/editor.js.map +1 -1
- package/dist/images/edit_dialog/edit_dialog/index.js +1 -1
- package/dist/images/edit_dialog/edit_dialog/index.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/menus/headerbar/model.js +4 -4
- package/dist/menus/headerbar/model.js.map +1 -1
- package/package.json +3 -3
- package/src/collab/chat.js +1 -1
- package/src/collab/doc.js +8 -8
- package/src/dialogs/figure.js +1 -1
- package/src/document_template/exporter.js +1 -1
- package/src/document_template/index.js +2 -2
- package/src/e2ee/snapshot-manager.js +5 -5
- package/src/exporter/native/copy.js +144 -7
- package/src/footnotes/editor.js +1 -1
- package/src/images/edit_dialog/edit_dialog/index.js +1 -1
- package/src/index.js +7 -7
- package/src/menus/headerbar/model.js +4 -4
- package/src/e2ee/encryptor.js +0 -228
- package/src/e2ee/key-manager.js +0 -202
- package/src/e2ee/passphrase-crypto.js +0 -606
- package/src/e2ee/passphrase-dialog.js +0 -655
- package/src/e2ee/passphrase-manager.js +0 -502
- package/src/e2ee/password-dialog.js +0 -652
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiduswriter/editor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Fidus Writer browser editor",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@fiduswriter/common": "^0.1.3",
|
|
35
|
-
"@fiduswriter/document": "^0.2.
|
|
35
|
+
"@fiduswriter/document": "^0.2.19",
|
|
36
36
|
"bibliojson": "^4.0.0",
|
|
37
37
|
"citeproc-plus": "^1.0.2",
|
|
38
38
|
"cropperjs": "^1.6.2",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"fast-deep-equal": "^3.1.3",
|
|
43
43
|
"fast-xml-parser": "^4.5.0",
|
|
44
44
|
"fix-utf8": "^2.0.1",
|
|
45
|
-
"fwtoolkit": "^0.1.
|
|
45
|
+
"fwtoolkit": "^0.1.9",
|
|
46
46
|
"jszip": "^3.10.1",
|
|
47
47
|
"mathml-to-latex": "^1.4.3",
|
|
48
48
|
"mathml2omml": "^0.5.0",
|
package/src/collab/chat.js
CHANGED
package/src/collab/doc.js
CHANGED
|
@@ -3,11 +3,11 @@ import {Dialog, activateWait, deactivateWait, makeWorker} from "fwtoolkit"
|
|
|
3
3
|
import {receiveTransaction, sendableSteps} from "prosemirror-collab"
|
|
4
4
|
import {EditorState} from "prosemirror-state"
|
|
5
5
|
import {Step} from "prosemirror-transform"
|
|
6
|
-
import {E2EEEncryptor} from "
|
|
7
|
-
import {E2EEKeyManager} from "
|
|
8
|
-
import {enterPassphraseDialog} from "
|
|
9
|
-
import {PassphraseManager} from "
|
|
10
|
-
import {enterPasswordDialog} from "
|
|
6
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
7
|
+
import {E2EEKeyManager} from "fwtoolkit/e2ee/key-manager"
|
|
8
|
+
import {enterPassphraseDialog} from "fwtoolkit/e2ee/passphrase-dialog"
|
|
9
|
+
import {PassphraseManager} from "fwtoolkit/e2ee/passphrase-manager"
|
|
10
|
+
import {enterPasswordDialog} from "fwtoolkit/e2ee/password-dialog"
|
|
11
11
|
import {
|
|
12
12
|
getSelectionUpdate,
|
|
13
13
|
removeCollaboratorSelection,
|
|
@@ -312,7 +312,7 @@ export class ModCollabDoc {
|
|
|
312
312
|
} else if (result?.action === "recover") {
|
|
313
313
|
// Recovery flow
|
|
314
314
|
const {recoverWithKeyDialog} = await import(
|
|
315
|
-
"
|
|
315
|
+
"fwtoolkit/e2ee/passphrase-dialog"
|
|
316
316
|
)
|
|
317
317
|
const recoverResult = await new Promise(resolve => {
|
|
318
318
|
recoverWithKeyDialog(resolve)
|
|
@@ -325,7 +325,7 @@ export class ModCollabDoc {
|
|
|
325
325
|
recoverResult.newPassphrase
|
|
326
326
|
)
|
|
327
327
|
const {showRecoveryKeyDialog} = await import(
|
|
328
|
-
"
|
|
328
|
+
"fwtoolkit/e2ee/passphrase-dialog"
|
|
329
329
|
)
|
|
330
330
|
await new Promise(resolve => {
|
|
331
331
|
showRecoveryKeyDialog(
|
|
@@ -685,7 +685,7 @@ export class ModCollabDoc {
|
|
|
685
685
|
let decryptedImages = doc.images
|
|
686
686
|
if (doc.images) {
|
|
687
687
|
decryptedImages = {}
|
|
688
|
-
const {E2EEEncryptor} = await import("
|
|
688
|
+
const {E2EEEncryptor} = await import("fwtoolkit/e2ee/encryptor")
|
|
689
689
|
for (const [id, image] of Object.entries(doc.images)) {
|
|
690
690
|
decryptedImages[id] = image
|
|
691
691
|
if (typeof image.copyright === "string") {
|
package/src/dialogs/figure.js
CHANGED
|
@@ -3,7 +3,7 @@ import deepEqual from "fast-deep-equal"
|
|
|
3
3
|
import {randomFigureId} from "@fiduswriter/document/schema/common/index"
|
|
4
4
|
import {ContentMenu, Dialog, addAlert, dropdownSelect} from "fwtoolkit"
|
|
5
5
|
import {ImageSelectionDialog} from "../images/selection_dialog/index.js"
|
|
6
|
-
import {E2EEEncryptor} from "
|
|
6
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
7
7
|
import {configureFigureTemplate} from "./templates.js"
|
|
8
8
|
|
|
9
9
|
export class FigureDialog {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import download from "downloadjs"
|
|
2
2
|
|
|
3
3
|
import {createSlug} from "@fiduswriter/document/exporter/tools/file"
|
|
4
|
-
import {ZipFileCreator} from "
|
|
4
|
+
import {ZipFileCreator} from "fwtoolkit/file/zip"
|
|
5
5
|
import {postJson} from "fwtoolkit"
|
|
6
6
|
|
|
7
7
|
export class DocumentTemplateExporter {
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
shortFileTitle
|
|
8
8
|
} from "fwtoolkit"
|
|
9
9
|
import {SaveCopy} from "../exporter/native/index.js"
|
|
10
|
-
import {E2EEKeyManager} from "
|
|
10
|
+
import {E2EEKeyManager} from "fwtoolkit/e2ee/key-manager"
|
|
11
11
|
import {
|
|
12
12
|
createPasswordDialog,
|
|
13
13
|
enterPasswordDialog
|
|
14
|
-
} from "
|
|
14
|
+
} from "fwtoolkit/e2ee/password-dialog"
|
|
15
15
|
|
|
16
16
|
export {serializeHelp} from "./schema.js"
|
|
17
17
|
|
|
@@ -68,7 +68,7 @@ export class E2EESnapshotManager {
|
|
|
68
68
|
const bibliography = this.editor.docInfo.bibliography || {}
|
|
69
69
|
const {title} = this.editor.getDoc()
|
|
70
70
|
|
|
71
|
-
const {E2EEEncryptor} = await import("
|
|
71
|
+
const {E2EEEncryptor} = await import("fwtoolkit/e2ee/encryptor")
|
|
72
72
|
|
|
73
73
|
const encryptedContent = await E2EEEncryptor.encryptObject(
|
|
74
74
|
content,
|
|
@@ -129,7 +129,7 @@ export class E2EESnapshotManager {
|
|
|
129
129
|
const {title} = this.editor.getDoc()
|
|
130
130
|
|
|
131
131
|
// Dynamically import to avoid circular dependencies
|
|
132
|
-
const {E2EEEncryptor} = await import("
|
|
132
|
+
const {E2EEEncryptor} = await import("fwtoolkit/e2ee/encryptor")
|
|
133
133
|
|
|
134
134
|
// Encrypt all document data
|
|
135
135
|
const encryptedContent = await E2EEEncryptor.encryptObject(
|
|
@@ -183,7 +183,7 @@ export class E2EESnapshotManager {
|
|
|
183
183
|
this.clearKey()
|
|
184
184
|
// Clear cached password and key from sessionStorage so the user
|
|
185
185
|
// is prompted for the new password on next access.
|
|
186
|
-
const {E2EEKeyManager} = await import("
|
|
186
|
+
const {E2EEKeyManager} = await import("fwtoolkit/e2ee/key-manager")
|
|
187
187
|
E2EEKeyManager.clearKeyFromSession(this.editor.docInfo.id)
|
|
188
188
|
E2EEKeyManager.clearPasswordFromSession(this.editor.docInfo.id)
|
|
189
189
|
// Notify the user that the password has changed.
|
|
@@ -213,7 +213,7 @@ export class E2EESnapshotManager {
|
|
|
213
213
|
return
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
const {E2EEEncryptor} = await import("
|
|
216
|
+
const {E2EEEncryptor} = await import("fwtoolkit/e2ee/encryptor")
|
|
217
217
|
|
|
218
218
|
const encryptedContent = await E2EEEncryptor.encryptObject(
|
|
219
219
|
content,
|
|
@@ -269,7 +269,7 @@ export class E2EESnapshotManager {
|
|
|
269
269
|
// Extract the current title from the editor
|
|
270
270
|
const {title} = this.editor.getDoc()
|
|
271
271
|
|
|
272
|
-
const {E2EEEncryptor} = await import("
|
|
272
|
+
const {E2EEEncryptor} = await import("fwtoolkit/e2ee/encryptor")
|
|
273
273
|
|
|
274
274
|
// Encrypt with the new key
|
|
275
275
|
const encryptedContent = await E2EEEncryptor.encryptObject(
|
|
@@ -1,9 +1,144 @@
|
|
|
1
1
|
import {SaveCopy as GenericSaveCopy} from "@fiduswriter/document/exporter/native"
|
|
2
|
-
import {addProgress, gettext, shortFileTitle} from "fwtoolkit"
|
|
3
|
-
import {E2EEEncryptor} from "
|
|
4
|
-
import {E2EEKeyManager} from "
|
|
2
|
+
import {addAlert, addProgress, gettext, postJson, shortFileTitle} from "fwtoolkit"
|
|
3
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
4
|
+
import {E2EEKeyManager} from "fwtoolkit/e2ee/key-manager"
|
|
5
5
|
import {NativeImporter} from "@fiduswriter/document/importer/native"
|
|
6
6
|
|
|
7
|
+
async function maybeDecryptImage(imageEntry, sourceKey) {
|
|
8
|
+
if (!sourceKey || !imageEntry.file) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
if (imageEntry.file_type !== "application/octet-stream") {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
const fileBuffer = await imageEntry.file.arrayBuffer()
|
|
15
|
+
const bytes = new Uint8Array(fileBuffer)
|
|
16
|
+
let binary = ""
|
|
17
|
+
const chunkSize = 65536
|
|
18
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
19
|
+
const chunk = bytes.subarray(i, i + chunkSize)
|
|
20
|
+
binary += String.fromCharCode.apply(null, chunk)
|
|
21
|
+
}
|
|
22
|
+
const base64 = btoa(binary)
|
|
23
|
+
const decrypted = await E2EEEncryptor.decryptBufferToBase64(base64, sourceKey)
|
|
24
|
+
const mime = imageEntry.original_file_type || "image/png"
|
|
25
|
+
const byteCharacters = atob(decrypted)
|
|
26
|
+
const byteNumbers = new Array(byteCharacters.length)
|
|
27
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
28
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
|
29
|
+
}
|
|
30
|
+
const byteArray = new Uint8Array(byteNumbers)
|
|
31
|
+
imageEntry.file = new Blob([byteArray], {type: mime})
|
|
32
|
+
imageEntry.file_type = mime
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createNativeImporterBackend(user, e2eeOptions) {
|
|
36
|
+
return {
|
|
37
|
+
createDoc: (template, importId, path, e2ee, files) => {
|
|
38
|
+
const jsonData = {
|
|
39
|
+
template: template.content,
|
|
40
|
+
export_templates: template.exportTemplates,
|
|
41
|
+
document_styles: template.documentStyles,
|
|
42
|
+
import_id: importId
|
|
43
|
+
? importId
|
|
44
|
+
: template.content.attrs.import_id,
|
|
45
|
+
template_title: template.content.attrs.template,
|
|
46
|
+
path
|
|
47
|
+
}
|
|
48
|
+
if (e2ee?.enabled) {
|
|
49
|
+
jsonData.e2ee = true
|
|
50
|
+
if (e2ee.salt) {
|
|
51
|
+
jsonData.e2ee_salt = e2ee.salt
|
|
52
|
+
}
|
|
53
|
+
if (e2ee.iterations) {
|
|
54
|
+
jsonData.e2ee_iterations = e2ee.iterations
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return postJson("/api/document/import/create/", jsonData, files)
|
|
58
|
+
.then(({json}) => ({
|
|
59
|
+
id: json.id,
|
|
60
|
+
path: json.path,
|
|
61
|
+
e2ee: json.e2ee,
|
|
62
|
+
template: json.template
|
|
63
|
+
}))
|
|
64
|
+
.catch(error => {
|
|
65
|
+
addAlert("error", gettext("Could not create document"))
|
|
66
|
+
throw error
|
|
67
|
+
})
|
|
68
|
+
},
|
|
69
|
+
saveImages: async (images, docId, e2ee) => {
|
|
70
|
+
const isE2EE = e2ee?.enabled
|
|
71
|
+
const endpoint = isE2EE
|
|
72
|
+
? "/api/document/e2ee_image/"
|
|
73
|
+
: "/api/document/import/image/"
|
|
74
|
+
const imageTranslationTable = {}
|
|
75
|
+
await Promise.all(
|
|
76
|
+
Object.values(images.db).map(async imageEntry => {
|
|
77
|
+
await maybeDecryptImage(imageEntry, e2ee?.sourceKey)
|
|
78
|
+
const encryptedFile =
|
|
79
|
+
e2ee?.enabled && e2ee.key
|
|
80
|
+
? await E2EEEncryptor.encryptImage(
|
|
81
|
+
imageEntry.file,
|
|
82
|
+
e2ee.key
|
|
83
|
+
)
|
|
84
|
+
: imageEntry.file
|
|
85
|
+
const jsonData = {
|
|
86
|
+
doc_id: docId,
|
|
87
|
+
title: imageEntry.title,
|
|
88
|
+
copyright: imageEntry.copyright,
|
|
89
|
+
checksum: imageEntry.checksum
|
|
90
|
+
}
|
|
91
|
+
const files = {
|
|
92
|
+
image: {
|
|
93
|
+
file: encryptedFile,
|
|
94
|
+
filename: imageEntry.image.split("/").pop()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const {json} = await postJson(endpoint, jsonData, files)
|
|
98
|
+
imageTranslationTable[imageEntry.id] = json.id
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
return imageTranslationTable
|
|
102
|
+
},
|
|
103
|
+
saveDocument: async (saveData, e2ee) => {
|
|
104
|
+
if (e2ee?.enabled && e2ee.key) {
|
|
105
|
+
saveData.content = await E2EEEncryptor.encryptObject(
|
|
106
|
+
saveData.content,
|
|
107
|
+
e2ee.key
|
|
108
|
+
)
|
|
109
|
+
saveData.comments = await E2EEEncryptor.encryptObject(
|
|
110
|
+
saveData.comments || {},
|
|
111
|
+
e2ee.key
|
|
112
|
+
)
|
|
113
|
+
saveData.bibliography = await E2EEEncryptor.encryptObject(
|
|
114
|
+
saveData.bibliography,
|
|
115
|
+
e2ee.key
|
|
116
|
+
)
|
|
117
|
+
saveData.title = await E2EEEncryptor.encrypt(
|
|
118
|
+
saveData.title,
|
|
119
|
+
e2ee.key
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
return postJson("/api/document/import/", saveData)
|
|
123
|
+
.then(({json}) => ({added: json.added, updated: json.updated}))
|
|
124
|
+
.catch(error => {
|
|
125
|
+
addAlert(
|
|
126
|
+
"error",
|
|
127
|
+
`${gettext("Could not save ")} ${shortFileTitle(
|
|
128
|
+
saveData.title,
|
|
129
|
+
""
|
|
130
|
+
)}`
|
|
131
|
+
)
|
|
132
|
+
throw error
|
|
133
|
+
})
|
|
134
|
+
},
|
|
135
|
+
encryptImage: E2EEEncryptor.encryptImage.bind(E2EEEncryptor),
|
|
136
|
+
encryptObject: E2EEEncryptor.encryptObject.bind(E2EEEncryptor),
|
|
137
|
+
encrypt: E2EEEncryptor.encrypt.bind(E2EEEncryptor),
|
|
138
|
+
storeKeyInSession: E2EEKeyManager.storeKeyInSession.bind(E2EEKeyManager)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
7
142
|
export class SaveCopy extends GenericSaveCopy {
|
|
8
143
|
constructor(
|
|
9
144
|
doc,
|
|
@@ -37,10 +172,12 @@ export class SaveCopy extends GenericSaveCopy {
|
|
|
37
172
|
imageDB,
|
|
38
173
|
httpIncludes,
|
|
39
174
|
options.user,
|
|
40
|
-
options.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
175
|
+
createNativeImporterBackend(options.user, options.e2eeOptions),
|
|
176
|
+
{
|
|
177
|
+
importId: options.importId,
|
|
178
|
+
requestedPath: options.requestedPath,
|
|
179
|
+
e2eeOptions: options.e2eeOptions
|
|
180
|
+
}
|
|
44
181
|
).init()
|
|
45
182
|
|
|
46
183
|
super(doc, bibDB, imageDB, newUser, {
|
package/src/footnotes/editor.js
CHANGED
|
@@ -11,7 +11,7 @@ import {EditorView} from "prosemirror-view"
|
|
|
11
11
|
|
|
12
12
|
import {fnSchema} from "@fiduswriter/document/schema/footnotes"
|
|
13
13
|
import {fnNodeToPmNode} from "@fiduswriter/document/schema/footnotes_convert"
|
|
14
|
-
import {E2EEEncryptor} from "
|
|
14
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
15
15
|
import {
|
|
16
16
|
citationRenderPlugin,
|
|
17
17
|
clipboardPlugin,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {CheckableList, ContentMenu, Dialog, addAlert} from "fwtoolkit"
|
|
2
|
-
import {E2EEEncryptor} from "
|
|
2
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
3
3
|
import {imageEditModel} from "./model.js"
|
|
4
4
|
import {imageEditTemplate} from "./templates.js"
|
|
5
5
|
export class ImageEditDialog {
|
package/src/index.js
CHANGED
|
@@ -20,18 +20,18 @@ import {EditorState, TextSelection} from "prosemirror-state"
|
|
|
20
20
|
import {tableEditing} from "prosemirror-tables"
|
|
21
21
|
import {EditorView} from "prosemirror-view"
|
|
22
22
|
import {FeedbackTab} from "@fiduswriter/common"
|
|
23
|
-
import {E2EEEncryptor} from "
|
|
24
|
-
import {E2EEKeyManager} from "
|
|
23
|
+
import {E2EEEncryptor} from "fwtoolkit/e2ee/encryptor"
|
|
24
|
+
import {E2EEKeyManager} from "fwtoolkit/e2ee/key-manager"
|
|
25
25
|
import {
|
|
26
26
|
enterPassphraseDialog,
|
|
27
27
|
setupPassphraseDialog,
|
|
28
28
|
showRecoveryKeyDialog
|
|
29
|
-
} from "
|
|
30
|
-
import {PassphraseManager} from "
|
|
29
|
+
} from "fwtoolkit/e2ee/passphrase-dialog"
|
|
30
|
+
import {PassphraseManager} from "fwtoolkit/e2ee/passphrase-manager"
|
|
31
31
|
import {
|
|
32
32
|
changePasswordDialog,
|
|
33
33
|
createPasswordDialog
|
|
34
|
-
} from "
|
|
34
|
+
} from "fwtoolkit/e2ee/password-dialog"
|
|
35
35
|
import {E2EESnapshotManager} from "./e2ee/snapshot-manager.js"
|
|
36
36
|
|
|
37
37
|
import {getSettings} from "@fiduswriter/document/schema/convert"
|
|
@@ -794,7 +794,7 @@ export class Editor {
|
|
|
794
794
|
} else if (result.action === "recover") {
|
|
795
795
|
// Recovery flow
|
|
796
796
|
const {recoverWithKeyDialog} = await import(
|
|
797
|
-
"
|
|
797
|
+
"fwtoolkit/e2ee/passphrase-dialog"
|
|
798
798
|
)
|
|
799
799
|
const recoverResult = await new Promise(resolve => {
|
|
800
800
|
recoverWithKeyDialog(resolve)
|
|
@@ -807,7 +807,7 @@ export class Editor {
|
|
|
807
807
|
recoverResult.newPassphrase
|
|
808
808
|
)
|
|
809
809
|
const {showRecoveryKeyDialog} = await import(
|
|
810
|
-
"
|
|
810
|
+
"fwtoolkit/e2ee/passphrase-dialog"
|
|
811
811
|
)
|
|
812
812
|
await new Promise(resolve => {
|
|
813
813
|
showRecoveryKeyDialog(newRecoveryKey, resolve)
|
|
@@ -10,9 +10,9 @@ import {DocumentAccessRightsDialog} from "../../documents/access_rights/index.js
|
|
|
10
10
|
import {SaveCopy, SaveRevision} from "../../exporter/native/index.js"
|
|
11
11
|
import {ExportFidusFile} from "../../exporter/native/file.js"
|
|
12
12
|
import {LanguageDialog, RevisionDialog} from "../../dialogs/index.js"
|
|
13
|
-
import {E2EEKeyManager} from "
|
|
14
|
-
import {PassphraseManager} from "
|
|
15
|
-
import {changePasswordDialog} from "
|
|
13
|
+
import {E2EEKeyManager} from "fwtoolkit/e2ee/key-manager"
|
|
14
|
+
import {PassphraseManager} from "fwtoolkit/e2ee/passphrase-manager"
|
|
15
|
+
import {changePasswordDialog} from "fwtoolkit/e2ee/password-dialog"
|
|
16
16
|
import {
|
|
17
17
|
KeyBindingsDialog,
|
|
18
18
|
SearchReplaceDialog,
|
|
@@ -435,7 +435,7 @@ export const headerbarModel = () => ({
|
|
|
435
435
|
editor.e2ee.encryptionIterations
|
|
436
436
|
)
|
|
437
437
|
const {E2EEEncryptor} = await import(
|
|
438
|
-
"
|
|
438
|
+
"fwtoolkit/e2ee/encryptor"
|
|
439
439
|
)
|
|
440
440
|
const testValue = "test"
|
|
441
441
|
const encryptedTest =
|
package/src/e2ee/encryptor.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E2EE Encryptor - Handles encryption and decryption of document content
|
|
3
|
-
* for end-to-end encrypted documents.
|
|
4
|
-
*
|
|
5
|
-
* Uses AES-GCM (256-bit) for all encryption operations. Each encryption
|
|
6
|
-
* operation generates a random 12-byte IV (initialization vector) to
|
|
7
|
-
* ensure that encrypting the same plaintext twice produces different
|
|
8
|
-
* ciphertext.
|
|
9
|
-
*
|
|
10
|
-
* Encrypted data format (Appendix A of the E2EE plan):
|
|
11
|
-
* [IV (12 bytes)][Ciphertext (variable length)][Auth Tag (16 bytes, implicit in AES-GCM)]
|
|
12
|
-
*
|
|
13
|
-
* When stored as a string (e.g., in JSON fields), the entire structure
|
|
14
|
-
* is Base64-encoded.
|
|
15
|
-
*/
|
|
16
|
-
export class E2EEEncryptor {
|
|
17
|
-
/**
|
|
18
|
-
* Encrypt a string with AES-GCM.
|
|
19
|
-
*
|
|
20
|
-
* @param {string} plaintext - The plaintext string to encrypt
|
|
21
|
-
* @param {CryptoKey} key - An AES-GCM key (from E2EEKeyManager.deriveKey)
|
|
22
|
-
* @returns {Promise<string>} Base64-encoded string (iv + ciphertext + auth tag)
|
|
23
|
-
*/
|
|
24
|
-
static async encrypt(plaintext, key) {
|
|
25
|
-
const iv = crypto.getRandomValues(new Uint8Array(12))
|
|
26
|
-
const encoded = new TextEncoder().encode(plaintext)
|
|
27
|
-
const ciphertext = await crypto.subtle.encrypt(
|
|
28
|
-
{name: "AES-GCM", iv: iv},
|
|
29
|
-
key,
|
|
30
|
-
encoded
|
|
31
|
-
)
|
|
32
|
-
// Prepend IV to ciphertext for storage
|
|
33
|
-
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
|
|
34
|
-
combined.set(iv, 0)
|
|
35
|
-
combined.set(new Uint8Array(ciphertext), iv.length)
|
|
36
|
-
return E2EEEncryptor._uint8ArrayToBase64(combined)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Decrypt a Base64-encoded AES-GCM ciphertext.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} ciphertextBase64 - Base64-encoded (iv + ciphertext + auth tag)
|
|
43
|
-
* @param {CryptoKey} key - An AES-GCM key (from E2EEKeyManager.deriveKey)
|
|
44
|
-
* @returns {Promise<string>} The decrypted plaintext string
|
|
45
|
-
*/
|
|
46
|
-
static async decrypt(ciphertextBase64, key) {
|
|
47
|
-
const combined = E2EEEncryptor._base64ToUint8Array(ciphertextBase64)
|
|
48
|
-
const iv = combined.slice(0, 12)
|
|
49
|
-
const ciphertext = combined.slice(12)
|
|
50
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
51
|
-
{name: "AES-GCM", iv: iv},
|
|
52
|
-
key,
|
|
53
|
-
ciphertext
|
|
54
|
-
)
|
|
55
|
-
return new TextDecoder().decode(decrypted)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Encrypt a JSON-serializable object.
|
|
60
|
-
*
|
|
61
|
-
* Serializes the object to JSON, then encrypts the JSON string.
|
|
62
|
-
*
|
|
63
|
-
* @param {Object} obj - A JSON-serializable object
|
|
64
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
65
|
-
* @returns {Promise<string>} Base64-encoded encrypted data
|
|
66
|
-
*/
|
|
67
|
-
static encryptObject(obj, key) {
|
|
68
|
-
return E2EEEncryptor.encrypt(JSON.stringify(obj), key)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Decrypt to a JSON-serializable object.
|
|
73
|
-
*
|
|
74
|
-
* Decrypts the Base64-encoded ciphertext, then parses the result as JSON.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} ciphertextBase64 - Base64-encoded encrypted data
|
|
77
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
78
|
-
* @returns {Promise<Object>} The decrypted and parsed object
|
|
79
|
-
*/
|
|
80
|
-
static async decryptObject(ciphertextBase64, key) {
|
|
81
|
-
const plaintext = await E2EEEncryptor.decrypt(ciphertextBase64, key)
|
|
82
|
-
return JSON.parse(plaintext)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Encrypt an ArrayBuffer (for images and other binary data).
|
|
87
|
-
*
|
|
88
|
-
* @param {ArrayBuffer} buffer - The binary data to encrypt
|
|
89
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
90
|
-
* @returns {Promise<string>} Base64-encoded encrypted data (iv + ciphertext)
|
|
91
|
-
*/
|
|
92
|
-
static async encryptBuffer(buffer, key) {
|
|
93
|
-
const iv = crypto.getRandomValues(new Uint8Array(12))
|
|
94
|
-
const ciphertext = await crypto.subtle.encrypt(
|
|
95
|
-
{name: "AES-GCM", iv: iv},
|
|
96
|
-
key,
|
|
97
|
-
buffer
|
|
98
|
-
)
|
|
99
|
-
// Prepend IV to ciphertext
|
|
100
|
-
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
|
|
101
|
-
combined.set(iv, 0)
|
|
102
|
-
combined.set(new Uint8Array(ciphertext), iv.length)
|
|
103
|
-
return E2EEEncryptor._uint8ArrayToBase64(combined)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Decrypt to an ArrayBuffer.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} ciphertextBase64 - Base64-encoded encrypted data
|
|
110
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
111
|
-
* @returns {Promise<ArrayBuffer>} The decrypted binary data
|
|
112
|
-
*/
|
|
113
|
-
static decryptBuffer(ciphertextBase64, key) {
|
|
114
|
-
const combined = E2EEEncryptor._base64ToUint8Array(ciphertextBase64)
|
|
115
|
-
const iv = combined.slice(0, 12)
|
|
116
|
-
const ciphertext = combined.slice(12)
|
|
117
|
-
return crypto.subtle.decrypt({name: "AES-GCM", iv: iv}, key, ciphertext)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Encrypt an image File/Blob for upload.
|
|
122
|
-
*
|
|
123
|
-
* Reads the file as an ArrayBuffer, encrypts it, and returns
|
|
124
|
-
* a Blob with application/octet-stream type (since the encrypted
|
|
125
|
-
* data is opaque binary).
|
|
126
|
-
*
|
|
127
|
-
* @param {File|Blob} file - The image file to encrypt
|
|
128
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
129
|
-
* @returns {Promise<Blob>} An encrypted Blob with type application/octet-stream
|
|
130
|
-
*/
|
|
131
|
-
static async encryptImage(file, key) {
|
|
132
|
-
const buffer = await file.arrayBuffer()
|
|
133
|
-
const iv = crypto.getRandomValues(new Uint8Array(12))
|
|
134
|
-
const ciphertext = await crypto.subtle.encrypt(
|
|
135
|
-
{name: "AES-GCM", iv: iv},
|
|
136
|
-
key,
|
|
137
|
-
buffer
|
|
138
|
-
)
|
|
139
|
-
// Prepend IV to ciphertext
|
|
140
|
-
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
|
|
141
|
-
combined.set(iv, 0)
|
|
142
|
-
combined.set(new Uint8Array(ciphertext), iv.length)
|
|
143
|
-
return new Blob([combined], {type: "application/octet-stream"})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Decrypt an encrypted image back to an ArrayBuffer.
|
|
148
|
-
*
|
|
149
|
-
* @param {string} ciphertextBase64 - Base64-encoded encrypted image data
|
|
150
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
151
|
-
* @returns {Promise<ArrayBuffer>} The decrypted image data
|
|
152
|
-
*/
|
|
153
|
-
static decryptImage(ciphertextBase64, key) {
|
|
154
|
-
return E2EEEncryptor.decryptBuffer(ciphertextBase64, key)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Decrypt a Base64-encoded ciphertext and return as a Base64 string.
|
|
159
|
-
*
|
|
160
|
-
* Useful for decrypting encrypted images that need to be stored
|
|
161
|
-
* as Base64 data URLs or re-exported.
|
|
162
|
-
*
|
|
163
|
-
* @param {string} ciphertextBase64 - Base64-encoded encrypted data
|
|
164
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
165
|
-
* @returns {Promise<string>} Base64-encoded decrypted data
|
|
166
|
-
*/
|
|
167
|
-
static async decryptBufferToBase64(ciphertextBase64, key) {
|
|
168
|
-
const buffer = await E2EEEncryptor.decryptBuffer(ciphertextBase64, key)
|
|
169
|
-
const bytes = new Uint8Array(buffer)
|
|
170
|
-
let binary = ""
|
|
171
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
172
|
-
binary += String.fromCharCode(bytes[i])
|
|
173
|
-
}
|
|
174
|
-
return btoa(binary)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Decrypt an encrypted image and return a blob URL for display.
|
|
179
|
-
*
|
|
180
|
-
* Fetches the encrypted image file from the given URL, decrypts it,
|
|
181
|
-
* and creates a temporary object URL that can be used as an img src.
|
|
182
|
-
*
|
|
183
|
-
* @param {string} imageUrl - The URL of the encrypted image file
|
|
184
|
-
* @param {CryptoKey} key - An AES-GCM key
|
|
185
|
-
* @param {string} mimeType - The MIME type of the decrypted image
|
|
186
|
-
* @returns {Promise<string>} A blob URL for the decrypted image
|
|
187
|
-
*/
|
|
188
|
-
static async decryptImageToUrl(imageUrl, key, mimeType = "image/png") {
|
|
189
|
-
const response = await fetch(imageUrl)
|
|
190
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
191
|
-
const bytes = new Uint8Array(arrayBuffer)
|
|
192
|
-
let binary = ""
|
|
193
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
194
|
-
binary += String.fromCharCode(bytes[i])
|
|
195
|
-
}
|
|
196
|
-
const base64 = btoa(binary)
|
|
197
|
-
const decrypted = await E2EEEncryptor.decryptBuffer(base64, key)
|
|
198
|
-
const blob = new Blob([decrypted], {type: mimeType})
|
|
199
|
-
return URL.createObjectURL(blob)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// --- Private helper methods ---
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Convert a Uint8Array to a Base64-encoded string.
|
|
206
|
-
* @private
|
|
207
|
-
*/
|
|
208
|
-
static _uint8ArrayToBase64(bytes) {
|
|
209
|
-
let binary = ""
|
|
210
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
211
|
-
binary += String.fromCharCode(bytes[i])
|
|
212
|
-
}
|
|
213
|
-
return btoa(binary)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Convert a Base64-encoded string to a Uint8Array.
|
|
218
|
-
* @private
|
|
219
|
-
*/
|
|
220
|
-
static _base64ToUint8Array(base64) {
|
|
221
|
-
const binary = atob(base64)
|
|
222
|
-
const bytes = new Uint8Array(binary.length)
|
|
223
|
-
for (let i = 0; i < binary.length; i++) {
|
|
224
|
-
bytes[i] = binary.charCodeAt(i)
|
|
225
|
-
}
|
|
226
|
-
return bytes
|
|
227
|
-
}
|
|
228
|
-
}
|