uppy-encrypt 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 0sum Co
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Uppy Encrypt
2
+
3
+ An [Uppy](https://uppy.io/) Plugin to encrypt files on the browser before it's uploaded. Uppy Encrypt also comes with the ability to decrypt browser-side.
4
+
5
+ Uppy Encrypt uses [libsodium.js](https://github.com/jedisct1/libsodium.js) for all the cryptographical magic.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm i uppy-encrypt
11
+ ```
12
+
13
+ ## Encryption Example
14
+ ```javascript
15
+ import { Uppy } from '@uppy/core';
16
+ import UppyEncryptPlugin from 'uppy-encrypt';
17
+
18
+ const uppy = new Uppy();
19
+ uppy.use(UppyEncryptPlugin);
20
+
21
+ // Optional: Set password manually, or disregard and a random password will be auto-generated
22
+ // uppy.setMeta({ password: '$upers3cret!' });
23
+
24
+ uppy.on('complete', async (result) => {
25
+ for (const file of result.successful) {
26
+ const salt = file.meta.encryption.salt; // Salt value used to increase security
27
+ const header = file.meta.encryption.header; // Header encryption data to kick off the decryption process
28
+ const hash = file.meta.encryption.hash; // Secure 1-way hash of the password
29
+ const meta = file.meta.encryption.meta; // Encrypted file meta data (file name, type)
30
+ // ^ These are all safe to store in a database
31
+ }
32
+ });
33
+ ```
34
+
35
+ ## Decryption Example
36
+ ```javascript
37
+ import { UppyDecrypt, uppyEncryptReady } from 'uppy-encrypt';
38
+
39
+ // Use the values generated from the encryption process
40
+ // Usually, these would be stored/retrieved from a database
41
+ const decrypt = async (hash, password, salt, header, meta, encryptedFileUrl) => {
42
+ // Ensure required libraries are loaded
43
+ await uppyEncryptReady();
44
+
45
+ // Verify provided password against the stored hash value
46
+ if (!UppyDecrypt.verifyPassword(hash, password)) {
47
+ // Invalid password
48
+ return;
49
+ }
50
+
51
+ // Decrypt Metadata
52
+ const decryptor = new UppyDecrypt(password, salt, header);
53
+ const decryptedMeta = decryptor.getDecryptedMetaData(meta.header, meta.data);
54
+
55
+ // Fetch & Decrypt the encrypted file
56
+ const file = await fetch(encryptedFileUrl);
57
+ const blob = await file.blob();
58
+ const decrypted = await decryptor.decryptFile(blob);
59
+
60
+ // Do something with the decrypted file, like download it
61
+ if (decrypted) {
62
+ const aElement = document.createElement('a');
63
+ aElement.setAttribute('download', decryptedMeta.name);
64
+ const href = URL.createObjectURL(decrypted);
65
+ aElement.href = href;
66
+ aElement.setAttribute('target', '_blank');
67
+ aElement.click();
68
+ URL.revokeObjectURL(href);
69
+ }
70
+ }
71
+ ```
@@ -0,0 +1,57 @@
1
+ import { Uppy, UppyFile, BasePlugin, DefaultPluginOptions } from '@uppy/core';
2
+
3
+ declare class UppyEncrypt {
4
+ private uppy;
5
+ private password;
6
+ private salt;
7
+ private key;
8
+ private state;
9
+ private header;
10
+ private file;
11
+ private stream;
12
+ private streamController;
13
+ private streamCanceled;
14
+ private index;
15
+ constructor(uppy: Uppy, file: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string);
16
+ static generatePassword(): string;
17
+ encryptFile(): Promise<boolean>;
18
+ getEncryptedFile(): Promise<Blob>;
19
+ getEncryptMetaData(): {
20
+ header: string;
21
+ data: string;
22
+ };
23
+ getPasswordHash(): string;
24
+ getHeader(): string;
25
+ getSalt(): string;
26
+ }
27
+
28
+ interface DecryptedMetaData {
29
+ name: string;
30
+ type?: string;
31
+ }
32
+ declare class UppyDecrypt {
33
+ private key;
34
+ private state;
35
+ private stream;
36
+ private streamController;
37
+ private contentType;
38
+ private index;
39
+ constructor(password: string, salt: string, header: string);
40
+ static verifyPassword(hash: string, password: string): boolean;
41
+ decryptFile(file: Blob): Promise<Blob>;
42
+ getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
43
+ }
44
+
45
+ interface UppyEncryptPluginOptions extends DefaultPluginOptions {
46
+ password: string | null;
47
+ }
48
+ declare const uppyEncryptReady: () => Promise<void>;
49
+ declare class UppyEncryptPlugin extends BasePlugin {
50
+ opts: UppyEncryptPluginOptions;
51
+ constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined);
52
+ encryptFiles(fileIds: string[]): Promise<void>;
53
+ install(): void;
54
+ uninstall(): void;
55
+ }
56
+
57
+ export { type DecryptedMetaData, UppyDecrypt, UppyEncrypt, UppyEncryptPlugin, uppyEncryptReady };
@@ -0,0 +1,57 @@
1
+ import { Uppy, UppyFile, BasePlugin, DefaultPluginOptions } from '@uppy/core';
2
+
3
+ declare class UppyEncrypt {
4
+ private uppy;
5
+ private password;
6
+ private salt;
7
+ private key;
8
+ private state;
9
+ private header;
10
+ private file;
11
+ private stream;
12
+ private streamController;
13
+ private streamCanceled;
14
+ private index;
15
+ constructor(uppy: Uppy, file: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string);
16
+ static generatePassword(): string;
17
+ encryptFile(): Promise<boolean>;
18
+ getEncryptedFile(): Promise<Blob>;
19
+ getEncryptMetaData(): {
20
+ header: string;
21
+ data: string;
22
+ };
23
+ getPasswordHash(): string;
24
+ getHeader(): string;
25
+ getSalt(): string;
26
+ }
27
+
28
+ interface DecryptedMetaData {
29
+ name: string;
30
+ type?: string;
31
+ }
32
+ declare class UppyDecrypt {
33
+ private key;
34
+ private state;
35
+ private stream;
36
+ private streamController;
37
+ private contentType;
38
+ private index;
39
+ constructor(password: string, salt: string, header: string);
40
+ static verifyPassword(hash: string, password: string): boolean;
41
+ decryptFile(file: Blob): Promise<Blob>;
42
+ getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
43
+ }
44
+
45
+ interface UppyEncryptPluginOptions extends DefaultPluginOptions {
46
+ password: string | null;
47
+ }
48
+ declare const uppyEncryptReady: () => Promise<void>;
49
+ declare class UppyEncryptPlugin extends BasePlugin {
50
+ opts: UppyEncryptPluginOptions;
51
+ constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined);
52
+ encryptFiles(fileIds: string[]): Promise<void>;
53
+ install(): void;
54
+ uninstall(): void;
55
+ }
56
+
57
+ export { type DecryptedMetaData, UppyDecrypt, UppyEncrypt, UppyEncryptPlugin, uppyEncryptReady };
package/dist/index.js ADDED
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ var __copyProps = (to, from, except, desc) => {
27
+ if (from && typeof from === "object" || typeof from === "function") {
28
+ for (let key of __getOwnPropNames(from))
29
+ if (!__hasOwnProp.call(to, key) && key !== except)
30
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
31
+ }
32
+ return to;
33
+ };
34
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
35
+ // If the importer is in node compatibility mode or this is not an ESM
36
+ // file that has been converted to a CommonJS file using a Babel-
37
+ // compatible transform (i.e. "__esModule" has not been set), then set
38
+ // "default" to the CommonJS "module.exports" for node compatibility.
39
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
40
+ mod
41
+ ));
42
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
43
+ var __async = (__this, __arguments, generator) => {
44
+ return new Promise((resolve, reject) => {
45
+ var fulfilled = (value) => {
46
+ try {
47
+ step(generator.next(value));
48
+ } catch (e) {
49
+ reject(e);
50
+ }
51
+ };
52
+ var rejected = (value) => {
53
+ try {
54
+ step(generator.throw(value));
55
+ } catch (e) {
56
+ reject(e);
57
+ }
58
+ };
59
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
60
+ step((generator = generator.apply(__this, __arguments)).next());
61
+ });
62
+ };
63
+
64
+ // src/index.ts
65
+ var src_exports = {};
66
+ __export(src_exports, {
67
+ UppyDecrypt: () => UppyDecrypt,
68
+ UppyEncrypt: () => UppyEncrypt,
69
+ UppyEncryptPlugin: () => UppyEncryptPlugin,
70
+ uppyEncryptReady: () => uppyEncryptReady
71
+ });
72
+ module.exports = __toCommonJS(src_exports);
73
+
74
+ // src/UppyEncrypt.ts
75
+ var import_libsodium_wrappers_sumo = __toESM(require("libsodium-wrappers-sumo"));
76
+
77
+ // src/constants.ts
78
+ var CHUNK_SIZE = 64 * 1024 * 1024;
79
+ var SIGNATURE = "uppyencrypt";
80
+
81
+ // src/UppyEncrypt.ts
82
+ var sodium;
83
+ (() => __async(void 0, null, function* () {
84
+ yield import_libsodium_wrappers_sumo.default.ready;
85
+ sodium = import_libsodium_wrappers_sumo.default;
86
+ }))();
87
+ var UppyEncrypt = class {
88
+ constructor(uppy, file, password) {
89
+ this.streamCanceled = false;
90
+ this.index = 0;
91
+ this.uppy = uppy;
92
+ this.file = file;
93
+ this.password = password;
94
+ uppy.on("cancel-all", () => {
95
+ this.streamCanceled = true;
96
+ });
97
+ this.streamController;
98
+ this.stream = new ReadableStream({
99
+ start: (controller) => {
100
+ this.streamController = controller;
101
+ }
102
+ });
103
+ this.salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
104
+ this.key = sodium.crypto_pwhash(
105
+ sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,
106
+ password,
107
+ this.salt,
108
+ sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
109
+ sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
110
+ sodium.crypto_pwhash_ALG_ARGON2ID13
111
+ );
112
+ const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
113
+ this.state = res.state;
114
+ this.header = res.header;
115
+ }
116
+ /**
117
+ * Helper function that generate a random password
118
+ */
119
+ static generatePassword() {
120
+ return sodium.to_base64(sodium.randombytes_buf(16), sodium.base64_variants.URLSAFE_NO_PADDING);
121
+ }
122
+ /**
123
+ * Encrypts the file
124
+ */
125
+ encryptFile() {
126
+ return __async(this, null, function* () {
127
+ if (!this.streamController) {
128
+ throw new Error("Encryption stream does not exist");
129
+ }
130
+ while (this.index < this.file.size) {
131
+ if (this.streamCanceled) {
132
+ yield this.stream.cancel();
133
+ return false;
134
+ }
135
+ if (this.index === 0) {
136
+ this.streamController.enqueue(new Uint8Array(new TextEncoder().encode(SIGNATURE)));
137
+ this.streamController.enqueue(this.salt);
138
+ this.streamController.enqueue(this.header);
139
+ }
140
+ const tag = this.index + CHUNK_SIZE < this.file.size ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
141
+ const chunk = yield this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();
142
+ const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);
143
+ this.streamController.enqueue(new Uint8Array(encryptedChunk));
144
+ this.uppy.emit("preprocess-progress", this.file, {
145
+ mode: "determinate",
146
+ message: `Encrypting ${this.file.name}...`,
147
+ value: this.index / this.file.size
148
+ });
149
+ this.index += CHUNK_SIZE;
150
+ }
151
+ this.uppy.emit("preprocess-progress", this.file, {
152
+ mode: "determinate",
153
+ message: `Encrypting ${this.file.name}...`,
154
+ value: 1
155
+ });
156
+ this.streamController.close();
157
+ return true;
158
+ });
159
+ }
160
+ /**
161
+ * Creates and returns a Blob of the encrypted file
162
+ */
163
+ getEncryptedFile() {
164
+ return __async(this, null, function* () {
165
+ const response = new Response(this.stream);
166
+ return response.blob();
167
+ });
168
+ }
169
+ /**
170
+ * Returns an encrypted representation of the file's metadata (name, content-type)
171
+ * header: base64-encoded header data
172
+ * meta: Encrypted JSON string of the file's metadata, base64-encoded
173
+ */
174
+ getEncryptMetaData() {
175
+ const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
176
+ const metaJson = JSON.stringify({ name: this.file.meta.name, type: this.file.meta.type || null });
177
+ const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(
178
+ res.state,
179
+ new Uint8Array(new TextEncoder().encode(metaJson)),
180
+ null,
181
+ sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
182
+ );
183
+ return {
184
+ header: sodium.to_base64(res.header, sodium.base64_variants.URLSAFE_NO_PADDING),
185
+ data: sodium.to_base64(encryptedChunk, sodium.base64_variants.URLSAFE_NO_PADDING)
186
+ };
187
+ }
188
+ /**
189
+ * Returns a hash of the password base64-encoded
190
+ * This data is safe to store in a database, etc
191
+ */
192
+ getPasswordHash() {
193
+ return sodium.crypto_pwhash_str(this.password, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);
194
+ }
195
+ /**
196
+ * Returns the header base64-encoded
197
+ * This data is safe to store in a database, etc
198
+ */
199
+ getHeader() {
200
+ return sodium.to_base64(this.header, sodium.base64_variants.URLSAFE_NO_PADDING);
201
+ }
202
+ /**
203
+ * Returns the salt base64-encoded
204
+ * This data is safe to store in a database, etc
205
+ */
206
+ getSalt() {
207
+ return sodium.to_base64(this.salt, sodium.base64_variants.URLSAFE_NO_PADDING);
208
+ }
209
+ };
210
+
211
+ // src/UppyDecrypt.ts
212
+ var import_libsodium_wrappers_sumo2 = __toESM(require("libsodium-wrappers-sumo"));
213
+ var sodium2;
214
+ (() => __async(void 0, null, function* () {
215
+ yield import_libsodium_wrappers_sumo2.default.ready;
216
+ sodium2 = import_libsodium_wrappers_sumo2.default;
217
+ }))();
218
+ var UppyDecrypt = class {
219
+ constructor(password, salt, header) {
220
+ this.index = 0;
221
+ const saltUint = sodium2.from_base64(salt, sodium2.base64_variants.URLSAFE_NO_PADDING);
222
+ const headerUint = sodium2.from_base64(header, sodium2.base64_variants.URLSAFE_NO_PADDING);
223
+ this.streamController;
224
+ this.stream = new ReadableStream({
225
+ start: (controller) => {
226
+ this.streamController = controller;
227
+ }
228
+ });
229
+ this.contentType = "";
230
+ this.key = sodium2.crypto_pwhash(
231
+ sodium2.crypto_secretstream_xchacha20poly1305_KEYBYTES,
232
+ password,
233
+ saltUint,
234
+ sodium2.crypto_pwhash_OPSLIMIT_INTERACTIVE,
235
+ sodium2.crypto_pwhash_MEMLIMIT_INTERACTIVE,
236
+ sodium2.crypto_pwhash_ALG_ARGON2ID13
237
+ );
238
+ this.state = sodium2.crypto_secretstream_xchacha20poly1305_init_pull(headerUint, this.key);
239
+ this.index = SIGNATURE.length + saltUint.length + headerUint.length;
240
+ }
241
+ /**
242
+ * Validates that the provided password is correct
243
+ * @param hash The hash value of the password created during UppyEncrypt
244
+ * @param password The user-provided password
245
+ * @returns {bool} true if correct password
246
+ */
247
+ static verifyPassword(hash, password) {
248
+ return sodium2.crypto_pwhash_str_verify(hash, password);
249
+ }
250
+ /**
251
+ * Decrypts the provided file
252
+ * @param file Blob of encryptyed file
253
+ * @returns Decrypted file as a blob
254
+ */
255
+ decryptFile(file) {
256
+ return __async(this, null, function* () {
257
+ if (!this.streamController) {
258
+ throw new Error("Encryption stream does not exist");
259
+ }
260
+ while (this.index < file.size) {
261
+ const chunk = yield file.slice(this.index, this.index + CHUNK_SIZE + sodium2.crypto_secretstream_xchacha20poly1305_ABYTES).arrayBuffer();
262
+ const decryptedChunk = sodium2.crypto_secretstream_xchacha20poly1305_pull(this.state, new Uint8Array(chunk));
263
+ this.streamController.enqueue(decryptedChunk.message);
264
+ this.index += CHUNK_SIZE + sodium2.crypto_secretstream_xchacha20poly1305_ABYTES;
265
+ }
266
+ this.streamController.close();
267
+ const response = new Response(this.stream, { headers: { "Content-Type": this.contentType } });
268
+ return response.blob();
269
+ });
270
+ }
271
+ /**
272
+ *
273
+ * @param header Header created during encryption of the meta data
274
+ * @param meta Encrypted meta data string
275
+ * @returns object of the decrypted meta data
276
+ */
277
+ getDecryptedMetaData(header, meta) {
278
+ const state = sodium2.crypto_secretstream_xchacha20poly1305_init_pull(sodium2.from_base64(header, sodium2.base64_variants.URLSAFE_NO_PADDING), this.key);
279
+ const decryptedChunk = sodium2.crypto_secretstream_xchacha20poly1305_pull(state, sodium2.from_base64(meta, sodium2.base64_variants.URLSAFE_NO_PADDING));
280
+ if (!decryptedChunk)
281
+ throw new Error("Unable to decrypt meta data");
282
+ const decryptedMeta = JSON.parse(new TextDecoder().decode(decryptedChunk.message));
283
+ if (decryptedMeta.type)
284
+ this.contentType = decryptedMeta.type;
285
+ return decryptedMeta;
286
+ }
287
+ };
288
+
289
+ // src/index.ts
290
+ var import_libsodium_wrappers_sumo3 = __toESM(require("libsodium-wrappers-sumo"));
291
+ var import_core = require("@uppy/core");
292
+ var sodiumIsReady = false;
293
+ var uppyEncryptReady = () => __async(void 0, null, function* () {
294
+ if (!sodiumIsReady) {
295
+ yield import_libsodium_wrappers_sumo3.default.ready;
296
+ sodiumIsReady = true;
297
+ }
298
+ });
299
+ var UppyEncryptPlugin = class extends import_core.BasePlugin {
300
+ constructor(uppy, opts) {
301
+ var _a;
302
+ super(uppy, opts);
303
+ this.id = (_a = opts == null ? void 0 : opts.id) != null ? _a : "UppyEncryptPlugin";
304
+ this.type = "modifier";
305
+ const defaultOptions = {
306
+ password: null
307
+ };
308
+ this.opts = __spreadValues(__spreadValues({}, defaultOptions), opts);
309
+ this.encryptFiles = this.encryptFiles.bind(this);
310
+ }
311
+ encryptFiles(fileIds) {
312
+ return __async(this, null, function* () {
313
+ this.opts.password = this.opts.password || UppyEncrypt.generatePassword();
314
+ this.uppy.setMeta({ password: this.opts.password });
315
+ for (const fileId of fileIds) {
316
+ const file = this.uppy.getFile(fileId);
317
+ const enc = new UppyEncrypt(this.uppy, file, this.opts.password);
318
+ if (yield enc.encryptFile()) {
319
+ this.uppy.emit("preprocess-complete", file);
320
+ let blob = yield enc.getEncryptedFile();
321
+ this.uppy.setFileState(fileId, {
322
+ type: "application/octet-stream",
323
+ data: blob,
324
+ size: blob.size
325
+ });
326
+ this.uppy.setFileMeta(fileId, {
327
+ name: `${file.name}.enc`,
328
+ type: "application/octet-stream",
329
+ encryption: {
330
+ salt: enc.getSalt(),
331
+ header: enc.getHeader(),
332
+ hash: enc.getPasswordHash(),
333
+ meta: enc.getEncryptMetaData()
334
+ }
335
+ });
336
+ }
337
+ }
338
+ });
339
+ }
340
+ install() {
341
+ this.uppy.addPreProcessor(this.encryptFiles);
342
+ }
343
+ uninstall() {
344
+ this.uppy.removePreProcessor(this.encryptFiles);
345
+ }
346
+ };
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ UppyDecrypt,
350
+ UppyEncrypt,
351
+ UppyEncryptPlugin,
352
+ uppyEncryptReady
353
+ });
354
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/UppyEncrypt.ts","../src/constants.ts","../src/UppyDecrypt.ts"],"sourcesContent":["import UppyEncrypt from './UppyEncrypt';\nimport UppyDecrypt, { type DecryptedMetaData } from './UppyDecrypt';\nimport _sodium from 'libsodium-wrappers-sumo';\nimport { BasePlugin, type DefaultPluginOptions, Uppy } from '@uppy/core';\n\ninterface UppyEncryptPluginOptions extends DefaultPluginOptions {\n password: string | null;\n}\n\n// Sodium is initialized automatically within UppyEncrypt / UppyDecrypt\n// Optionally call this to ensure initialization\nlet sodiumIsReady = false;\nexport const uppyEncryptReady = async () => {\n if (!sodiumIsReady) {\n await _sodium.ready;\n sodiumIsReady = true;\n }\n};\n\nexport class UppyEncryptPlugin extends BasePlugin {\n opts: UppyEncryptPluginOptions;\n\n constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined) {\n super(uppy, opts);\n this.id = opts?.id ?? 'UppyEncryptPlugin';\n this.type = 'modifier';\n\n const defaultOptions = {\n password: null,\n };\n this.opts = { ...defaultOptions, ...opts };\n\n this.encryptFiles = this.encryptFiles.bind(this);\n }\n\n async encryptFiles(fileIds: string[]) {\n // Generate a password here if none is already set\n this.opts.password = this.opts.password || UppyEncrypt.generatePassword();\n\n // Add password to meta data so it can be referenced externally\n this.uppy.setMeta({ password: this.opts.password });\n\n for (const fileId of fileIds) {\n const file = this.uppy.getFile(fileId);\n const enc = new UppyEncrypt(this.uppy, file, this.opts.password);\n if (await enc.encryptFile()) {\n this.uppy.emit('preprocess-complete', file);\n let blob = await enc.getEncryptedFile();\n this.uppy.setFileState(fileId, {\n type: 'application/octet-stream',\n data: blob,\n size: blob.size,\n });\n\n this.uppy.setFileMeta(fileId, {\n name: `${file.name}.enc`,\n type: 'application/octet-stream',\n encryption: {\n salt: enc.getSalt(),\n header: enc.getHeader(),\n hash: enc.getPasswordHash(),\n meta: enc.getEncryptMetaData(),\n },\n });\n }\n }\n }\n\n install() {\n this.uppy.addPreProcessor(this.encryptFiles);\n }\n\n uninstall() {\n this.uppy.removePreProcessor(this.encryptFiles);\n }\n}\n\nexport { UppyEncrypt, UppyDecrypt, DecryptedMetaData };\n","import type { Uppy, UppyFile } from '@uppy/core';\nimport _sodium from 'libsodium-wrappers-sumo';\nimport { CHUNK_SIZE, SIGNATURE } from './constants';\n\n// Init Sodium\nlet sodium: typeof _sodium;\n(async () => {\n await _sodium.ready;\n sodium = _sodium;\n})();\n\nexport default class UppyEncrypt {\n private uppy: Uppy;\n private password: string;\n private salt: Uint8Array;\n private key: Uint8Array;\n private state: _sodium.StateAddress;\n private header: Uint8Array;\n private file: UppyFile<Record<string, unknown>, Record<string, unknown>>;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private streamCanceled = false;\n\n private index = 0;\n\n constructor(uppy: Uppy, file: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string) {\n this.uppy = uppy;\n this.file = file;\n this.password = password;\n\n // Set Uppy event handlers that effect the encryption process\n uppy.on('cancel-all', () => {\n this.streamCanceled = true;\n });\n\n this.streamController;\n this.stream = new ReadableStream({\n start: (controller) => {\n this.streamController = controller;\n },\n });\n\n this.salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);\n this.key = sodium.crypto_pwhash(\n sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,\n password,\n this.salt,\n sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_ALG_ARGON2ID13\n );\n\n const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);\n this.state = res.state;\n this.header = res.header;\n }\n\n /**\n * Helper function that generate a random password\n */\n static generatePassword() {\n return sodium.to_base64(sodium.randombytes_buf(16), sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n\n /**\n * Encrypts the file\n */\n async encryptFile() {\n if (!this.streamController) {\n throw new Error('Encryption stream does not exist');\n }\n\n while (this.index < this.file.size) {\n if (this.streamCanceled) {\n await this.stream.cancel();\n return false;\n }\n\n // If first chunk\n if (this.index === 0) {\n this.streamController.enqueue(new Uint8Array(new TextEncoder().encode(SIGNATURE)));\n this.streamController.enqueue(this.salt);\n this.streamController.enqueue(this.header);\n }\n\n const tag =\n this.index + CHUNK_SIZE < this.file.size\n ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE\n : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;\n\n const chunk = await this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();\n const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);\n\n this.streamController.enqueue(new Uint8Array(encryptedChunk));\n\n this.uppy.emit('preprocess-progress', this.file, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: this.index / this.file.size,\n });\n\n this.index += CHUNK_SIZE;\n }\n\n this.uppy.emit('preprocess-progress', this.file, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: 1,\n });\n\n this.streamController.close();\n\n return true;\n }\n\n /**\n * Creates and returns a Blob of the encrypted file\n */\n async getEncryptedFile() {\n const response = new Response(this.stream);\n return response.blob();\n }\n\n /**\n * Returns an encrypted representation of the file's metadata (name, content-type)\n * header: base64-encoded header data\n * meta: Encrypted JSON string of the file's metadata, base64-encoded\n */\n getEncryptMetaData() {\n // Init fresh state\n const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);\n\n const metaJson = JSON.stringify({ name: this.file.meta.name, type: this.file.meta.type || null });\n const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(\n res.state,\n new Uint8Array(new TextEncoder().encode(metaJson)),\n null,\n sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL\n );\n\n return {\n header: sodium.to_base64(res.header, sodium.base64_variants.URLSAFE_NO_PADDING),\n data: sodium.to_base64(encryptedChunk, sodium.base64_variants.URLSAFE_NO_PADDING),\n };\n }\n\n /**\n * Returns a hash of the password base64-encoded\n * This data is safe to store in a database, etc\n */\n getPasswordHash() {\n return sodium.crypto_pwhash_str(this.password, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);\n }\n\n /**\n * Returns the header base64-encoded\n * This data is safe to store in a database, etc\n */\n getHeader() {\n return sodium.to_base64(this.header, sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n\n /**\n * Returns the salt base64-encoded\n * This data is safe to store in a database, etc\n */\n getSalt() {\n return sodium.to_base64(this.salt, sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n}\n","export const CHUNK_SIZE = 64 * 1024 * 1024;\nexport const SIGNATURE = 'uppyencrypt';\n","import _sodium from 'libsodium-wrappers-sumo';\nimport { CHUNK_SIZE, SIGNATURE } from './constants';\n\nexport interface DecryptedMetaData {\n name: string;\n type?: string;\n}\n\n// Init Sodium\nlet sodium: typeof _sodium;\n(async () => {\n await _sodium.ready;\n sodium = _sodium;\n})();\n\nexport default class UppyDecrypt {\n private key: Uint8Array;\n private state: _sodium.StateAddress;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private contentType: string;\n\n private index = 0;\n\n constructor(password: string, salt: string, header: string) {\n const saltUint = sodium.from_base64(salt, sodium.base64_variants.URLSAFE_NO_PADDING);\n const headerUint = sodium.from_base64(header, sodium.base64_variants.URLSAFE_NO_PADDING);\n\n this.streamController;\n this.stream = new ReadableStream({\n start: (controller) => {\n this.streamController = controller;\n },\n });\n this.contentType = ''; // Defined if/when meta-data is decrypted\n\n this.key = sodium.crypto_pwhash(\n sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,\n password,\n saltUint,\n sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_ALG_ARGON2ID13\n );\n\n this.state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(headerUint, this.key);\n\n this.index = SIGNATURE.length + saltUint.length + headerUint.length;\n }\n\n /**\n * Validates that the provided password is correct\n * @param hash The hash value of the password created during UppyEncrypt\n * @param password The user-provided password\n * @returns {bool} true if correct password\n */\n static verifyPassword(hash: string, password: string) {\n return sodium.crypto_pwhash_str_verify(hash, password);\n }\n\n /**\n * Decrypts the provided file\n * @param file Blob of encryptyed file\n * @returns Decrypted file as a blob\n */\n async decryptFile(file: Blob) {\n if (!this.streamController) {\n throw new Error('Encryption stream does not exist');\n }\n\n while (this.index < file.size) {\n const chunk = await file.slice(this.index, this.index + CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES).arrayBuffer();\n const decryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, new Uint8Array(chunk));\n\n this.streamController.enqueue(decryptedChunk.message);\n\n this.index += CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;\n }\n\n this.streamController.close();\n\n const response = new Response(this.stream, { headers: { 'Content-Type': this.contentType } });\n return response.blob();\n }\n\n /**\n *\n * @param header Header created during encryption of the meta data\n * @param meta Encrypted meta data string\n * @returns object of the decrypted meta data\n */\n getDecryptedMetaData(header: string, meta: string) {\n // Init fresh state\n const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(sodium.from_base64(header, sodium.base64_variants.URLSAFE_NO_PADDING), this.key);\n const decryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_pull(state, sodium.from_base64(meta, sodium.base64_variants.URLSAFE_NO_PADDING));\n\n if (!decryptedChunk) throw new Error('Unable to decrypt meta data');\n const decryptedMeta = JSON.parse(new TextDecoder().decode(decryptedChunk.message)) as DecryptedMetaData;\n if (decryptedMeta.type) this.contentType = decryptedMeta.type;\n return decryptedMeta;\n }\n}\n"],"mappings":"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","names":["_sodium","import_libsodium_wrappers_sumo","sodium","_sodium","import_libsodium_wrappers_sumo","_sodium"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,317 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ var __async = (__this, __arguments, generator) => {
18
+ return new Promise((resolve, reject) => {
19
+ var fulfilled = (value) => {
20
+ try {
21
+ step(generator.next(value));
22
+ } catch (e) {
23
+ reject(e);
24
+ }
25
+ };
26
+ var rejected = (value) => {
27
+ try {
28
+ step(generator.throw(value));
29
+ } catch (e) {
30
+ reject(e);
31
+ }
32
+ };
33
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
+ step((generator = generator.apply(__this, __arguments)).next());
35
+ });
36
+ };
37
+
38
+ // src/UppyEncrypt.ts
39
+ import _sodium from "libsodium-wrappers-sumo";
40
+
41
+ // src/constants.ts
42
+ var CHUNK_SIZE = 64 * 1024 * 1024;
43
+ var SIGNATURE = "uppyencrypt";
44
+
45
+ // src/UppyEncrypt.ts
46
+ var sodium;
47
+ (() => __async(void 0, null, function* () {
48
+ yield _sodium.ready;
49
+ sodium = _sodium;
50
+ }))();
51
+ var UppyEncrypt = class {
52
+ constructor(uppy, file, password) {
53
+ this.streamCanceled = false;
54
+ this.index = 0;
55
+ this.uppy = uppy;
56
+ this.file = file;
57
+ this.password = password;
58
+ uppy.on("cancel-all", () => {
59
+ this.streamCanceled = true;
60
+ });
61
+ this.streamController;
62
+ this.stream = new ReadableStream({
63
+ start: (controller) => {
64
+ this.streamController = controller;
65
+ }
66
+ });
67
+ this.salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
68
+ this.key = sodium.crypto_pwhash(
69
+ sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,
70
+ password,
71
+ this.salt,
72
+ sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
73
+ sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
74
+ sodium.crypto_pwhash_ALG_ARGON2ID13
75
+ );
76
+ const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
77
+ this.state = res.state;
78
+ this.header = res.header;
79
+ }
80
+ /**
81
+ * Helper function that generate a random password
82
+ */
83
+ static generatePassword() {
84
+ return sodium.to_base64(sodium.randombytes_buf(16), sodium.base64_variants.URLSAFE_NO_PADDING);
85
+ }
86
+ /**
87
+ * Encrypts the file
88
+ */
89
+ encryptFile() {
90
+ return __async(this, null, function* () {
91
+ if (!this.streamController) {
92
+ throw new Error("Encryption stream does not exist");
93
+ }
94
+ while (this.index < this.file.size) {
95
+ if (this.streamCanceled) {
96
+ yield this.stream.cancel();
97
+ return false;
98
+ }
99
+ if (this.index === 0) {
100
+ this.streamController.enqueue(new Uint8Array(new TextEncoder().encode(SIGNATURE)));
101
+ this.streamController.enqueue(this.salt);
102
+ this.streamController.enqueue(this.header);
103
+ }
104
+ const tag = this.index + CHUNK_SIZE < this.file.size ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
105
+ const chunk = yield this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();
106
+ const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);
107
+ this.streamController.enqueue(new Uint8Array(encryptedChunk));
108
+ this.uppy.emit("preprocess-progress", this.file, {
109
+ mode: "determinate",
110
+ message: `Encrypting ${this.file.name}...`,
111
+ value: this.index / this.file.size
112
+ });
113
+ this.index += CHUNK_SIZE;
114
+ }
115
+ this.uppy.emit("preprocess-progress", this.file, {
116
+ mode: "determinate",
117
+ message: `Encrypting ${this.file.name}...`,
118
+ value: 1
119
+ });
120
+ this.streamController.close();
121
+ return true;
122
+ });
123
+ }
124
+ /**
125
+ * Creates and returns a Blob of the encrypted file
126
+ */
127
+ getEncryptedFile() {
128
+ return __async(this, null, function* () {
129
+ const response = new Response(this.stream);
130
+ return response.blob();
131
+ });
132
+ }
133
+ /**
134
+ * Returns an encrypted representation of the file's metadata (name, content-type)
135
+ * header: base64-encoded header data
136
+ * meta: Encrypted JSON string of the file's metadata, base64-encoded
137
+ */
138
+ getEncryptMetaData() {
139
+ const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
140
+ const metaJson = JSON.stringify({ name: this.file.meta.name, type: this.file.meta.type || null });
141
+ const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(
142
+ res.state,
143
+ new Uint8Array(new TextEncoder().encode(metaJson)),
144
+ null,
145
+ sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
146
+ );
147
+ return {
148
+ header: sodium.to_base64(res.header, sodium.base64_variants.URLSAFE_NO_PADDING),
149
+ data: sodium.to_base64(encryptedChunk, sodium.base64_variants.URLSAFE_NO_PADDING)
150
+ };
151
+ }
152
+ /**
153
+ * Returns a hash of the password base64-encoded
154
+ * This data is safe to store in a database, etc
155
+ */
156
+ getPasswordHash() {
157
+ return sodium.crypto_pwhash_str(this.password, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);
158
+ }
159
+ /**
160
+ * Returns the header base64-encoded
161
+ * This data is safe to store in a database, etc
162
+ */
163
+ getHeader() {
164
+ return sodium.to_base64(this.header, sodium.base64_variants.URLSAFE_NO_PADDING);
165
+ }
166
+ /**
167
+ * Returns the salt base64-encoded
168
+ * This data is safe to store in a database, etc
169
+ */
170
+ getSalt() {
171
+ return sodium.to_base64(this.salt, sodium.base64_variants.URLSAFE_NO_PADDING);
172
+ }
173
+ };
174
+
175
+ // src/UppyDecrypt.ts
176
+ import _sodium2 from "libsodium-wrappers-sumo";
177
+ var sodium2;
178
+ (() => __async(void 0, null, function* () {
179
+ yield _sodium2.ready;
180
+ sodium2 = _sodium2;
181
+ }))();
182
+ var UppyDecrypt = class {
183
+ constructor(password, salt, header) {
184
+ this.index = 0;
185
+ const saltUint = sodium2.from_base64(salt, sodium2.base64_variants.URLSAFE_NO_PADDING);
186
+ const headerUint = sodium2.from_base64(header, sodium2.base64_variants.URLSAFE_NO_PADDING);
187
+ this.streamController;
188
+ this.stream = new ReadableStream({
189
+ start: (controller) => {
190
+ this.streamController = controller;
191
+ }
192
+ });
193
+ this.contentType = "";
194
+ this.key = sodium2.crypto_pwhash(
195
+ sodium2.crypto_secretstream_xchacha20poly1305_KEYBYTES,
196
+ password,
197
+ saltUint,
198
+ sodium2.crypto_pwhash_OPSLIMIT_INTERACTIVE,
199
+ sodium2.crypto_pwhash_MEMLIMIT_INTERACTIVE,
200
+ sodium2.crypto_pwhash_ALG_ARGON2ID13
201
+ );
202
+ this.state = sodium2.crypto_secretstream_xchacha20poly1305_init_pull(headerUint, this.key);
203
+ this.index = SIGNATURE.length + saltUint.length + headerUint.length;
204
+ }
205
+ /**
206
+ * Validates that the provided password is correct
207
+ * @param hash The hash value of the password created during UppyEncrypt
208
+ * @param password The user-provided password
209
+ * @returns {bool} true if correct password
210
+ */
211
+ static verifyPassword(hash, password) {
212
+ return sodium2.crypto_pwhash_str_verify(hash, password);
213
+ }
214
+ /**
215
+ * Decrypts the provided file
216
+ * @param file Blob of encryptyed file
217
+ * @returns Decrypted file as a blob
218
+ */
219
+ decryptFile(file) {
220
+ return __async(this, null, function* () {
221
+ if (!this.streamController) {
222
+ throw new Error("Encryption stream does not exist");
223
+ }
224
+ while (this.index < file.size) {
225
+ const chunk = yield file.slice(this.index, this.index + CHUNK_SIZE + sodium2.crypto_secretstream_xchacha20poly1305_ABYTES).arrayBuffer();
226
+ const decryptedChunk = sodium2.crypto_secretstream_xchacha20poly1305_pull(this.state, new Uint8Array(chunk));
227
+ this.streamController.enqueue(decryptedChunk.message);
228
+ this.index += CHUNK_SIZE + sodium2.crypto_secretstream_xchacha20poly1305_ABYTES;
229
+ }
230
+ this.streamController.close();
231
+ const response = new Response(this.stream, { headers: { "Content-Type": this.contentType } });
232
+ return response.blob();
233
+ });
234
+ }
235
+ /**
236
+ *
237
+ * @param header Header created during encryption of the meta data
238
+ * @param meta Encrypted meta data string
239
+ * @returns object of the decrypted meta data
240
+ */
241
+ getDecryptedMetaData(header, meta) {
242
+ const state = sodium2.crypto_secretstream_xchacha20poly1305_init_pull(sodium2.from_base64(header, sodium2.base64_variants.URLSAFE_NO_PADDING), this.key);
243
+ const decryptedChunk = sodium2.crypto_secretstream_xchacha20poly1305_pull(state, sodium2.from_base64(meta, sodium2.base64_variants.URLSAFE_NO_PADDING));
244
+ if (!decryptedChunk)
245
+ throw new Error("Unable to decrypt meta data");
246
+ const decryptedMeta = JSON.parse(new TextDecoder().decode(decryptedChunk.message));
247
+ if (decryptedMeta.type)
248
+ this.contentType = decryptedMeta.type;
249
+ return decryptedMeta;
250
+ }
251
+ };
252
+
253
+ // src/index.ts
254
+ import _sodium3 from "libsodium-wrappers-sumo";
255
+ import { BasePlugin } from "@uppy/core";
256
+ var sodiumIsReady = false;
257
+ var uppyEncryptReady = () => __async(void 0, null, function* () {
258
+ if (!sodiumIsReady) {
259
+ yield _sodium3.ready;
260
+ sodiumIsReady = true;
261
+ }
262
+ });
263
+ var UppyEncryptPlugin = class extends BasePlugin {
264
+ constructor(uppy, opts) {
265
+ var _a;
266
+ super(uppy, opts);
267
+ this.id = (_a = opts == null ? void 0 : opts.id) != null ? _a : "UppyEncryptPlugin";
268
+ this.type = "modifier";
269
+ const defaultOptions = {
270
+ password: null
271
+ };
272
+ this.opts = __spreadValues(__spreadValues({}, defaultOptions), opts);
273
+ this.encryptFiles = this.encryptFiles.bind(this);
274
+ }
275
+ encryptFiles(fileIds) {
276
+ return __async(this, null, function* () {
277
+ this.opts.password = this.opts.password || UppyEncrypt.generatePassword();
278
+ this.uppy.setMeta({ password: this.opts.password });
279
+ for (const fileId of fileIds) {
280
+ const file = this.uppy.getFile(fileId);
281
+ const enc = new UppyEncrypt(this.uppy, file, this.opts.password);
282
+ if (yield enc.encryptFile()) {
283
+ this.uppy.emit("preprocess-complete", file);
284
+ let blob = yield enc.getEncryptedFile();
285
+ this.uppy.setFileState(fileId, {
286
+ type: "application/octet-stream",
287
+ data: blob,
288
+ size: blob.size
289
+ });
290
+ this.uppy.setFileMeta(fileId, {
291
+ name: `${file.name}.enc`,
292
+ type: "application/octet-stream",
293
+ encryption: {
294
+ salt: enc.getSalt(),
295
+ header: enc.getHeader(),
296
+ hash: enc.getPasswordHash(),
297
+ meta: enc.getEncryptMetaData()
298
+ }
299
+ });
300
+ }
301
+ }
302
+ });
303
+ }
304
+ install() {
305
+ this.uppy.addPreProcessor(this.encryptFiles);
306
+ }
307
+ uninstall() {
308
+ this.uppy.removePreProcessor(this.encryptFiles);
309
+ }
310
+ };
311
+ export {
312
+ UppyDecrypt,
313
+ UppyEncrypt,
314
+ UppyEncryptPlugin,
315
+ uppyEncryptReady
316
+ };
317
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/UppyEncrypt.ts","../src/constants.ts","../src/UppyDecrypt.ts","../src/index.ts"],"sourcesContent":["import type { Uppy, UppyFile } from '@uppy/core';\nimport _sodium from 'libsodium-wrappers-sumo';\nimport { CHUNK_SIZE, SIGNATURE } from './constants';\n\n// Init Sodium\nlet sodium: typeof _sodium;\n(async () => {\n await _sodium.ready;\n sodium = _sodium;\n})();\n\nexport default class UppyEncrypt {\n private uppy: Uppy;\n private password: string;\n private salt: Uint8Array;\n private key: Uint8Array;\n private state: _sodium.StateAddress;\n private header: Uint8Array;\n private file: UppyFile<Record<string, unknown>, Record<string, unknown>>;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private streamCanceled = false;\n\n private index = 0;\n\n constructor(uppy: Uppy, file: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string) {\n this.uppy = uppy;\n this.file = file;\n this.password = password;\n\n // Set Uppy event handlers that effect the encryption process\n uppy.on('cancel-all', () => {\n this.streamCanceled = true;\n });\n\n this.streamController;\n this.stream = new ReadableStream({\n start: (controller) => {\n this.streamController = controller;\n },\n });\n\n this.salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);\n this.key = sodium.crypto_pwhash(\n sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,\n password,\n this.salt,\n sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_ALG_ARGON2ID13\n );\n\n const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);\n this.state = res.state;\n this.header = res.header;\n }\n\n /**\n * Helper function that generate a random password\n */\n static generatePassword() {\n return sodium.to_base64(sodium.randombytes_buf(16), sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n\n /**\n * Encrypts the file\n */\n async encryptFile() {\n if (!this.streamController) {\n throw new Error('Encryption stream does not exist');\n }\n\n while (this.index < this.file.size) {\n if (this.streamCanceled) {\n await this.stream.cancel();\n return false;\n }\n\n // If first chunk\n if (this.index === 0) {\n this.streamController.enqueue(new Uint8Array(new TextEncoder().encode(SIGNATURE)));\n this.streamController.enqueue(this.salt);\n this.streamController.enqueue(this.header);\n }\n\n const tag =\n this.index + CHUNK_SIZE < this.file.size\n ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE\n : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;\n\n const chunk = await this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();\n const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);\n\n this.streamController.enqueue(new Uint8Array(encryptedChunk));\n\n this.uppy.emit('preprocess-progress', this.file, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: this.index / this.file.size,\n });\n\n this.index += CHUNK_SIZE;\n }\n\n this.uppy.emit('preprocess-progress', this.file, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: 1,\n });\n\n this.streamController.close();\n\n return true;\n }\n\n /**\n * Creates and returns a Blob of the encrypted file\n */\n async getEncryptedFile() {\n const response = new Response(this.stream);\n return response.blob();\n }\n\n /**\n * Returns an encrypted representation of the file's metadata (name, content-type)\n * header: base64-encoded header data\n * meta: Encrypted JSON string of the file's metadata, base64-encoded\n */\n getEncryptMetaData() {\n // Init fresh state\n const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);\n\n const metaJson = JSON.stringify({ name: this.file.meta.name, type: this.file.meta.type || null });\n const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(\n res.state,\n new Uint8Array(new TextEncoder().encode(metaJson)),\n null,\n sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL\n );\n\n return {\n header: sodium.to_base64(res.header, sodium.base64_variants.URLSAFE_NO_PADDING),\n data: sodium.to_base64(encryptedChunk, sodium.base64_variants.URLSAFE_NO_PADDING),\n };\n }\n\n /**\n * Returns a hash of the password base64-encoded\n * This data is safe to store in a database, etc\n */\n getPasswordHash() {\n return sodium.crypto_pwhash_str(this.password, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE);\n }\n\n /**\n * Returns the header base64-encoded\n * This data is safe to store in a database, etc\n */\n getHeader() {\n return sodium.to_base64(this.header, sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n\n /**\n * Returns the salt base64-encoded\n * This data is safe to store in a database, etc\n */\n getSalt() {\n return sodium.to_base64(this.salt, sodium.base64_variants.URLSAFE_NO_PADDING);\n }\n}\n","export const CHUNK_SIZE = 64 * 1024 * 1024;\nexport const SIGNATURE = 'uppyencrypt';\n","import _sodium from 'libsodium-wrappers-sumo';\nimport { CHUNK_SIZE, SIGNATURE } from './constants';\n\nexport interface DecryptedMetaData {\n name: string;\n type?: string;\n}\n\n// Init Sodium\nlet sodium: typeof _sodium;\n(async () => {\n await _sodium.ready;\n sodium = _sodium;\n})();\n\nexport default class UppyDecrypt {\n private key: Uint8Array;\n private state: _sodium.StateAddress;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private contentType: string;\n\n private index = 0;\n\n constructor(password: string, salt: string, header: string) {\n const saltUint = sodium.from_base64(salt, sodium.base64_variants.URLSAFE_NO_PADDING);\n const headerUint = sodium.from_base64(header, sodium.base64_variants.URLSAFE_NO_PADDING);\n\n this.streamController;\n this.stream = new ReadableStream({\n start: (controller) => {\n this.streamController = controller;\n },\n });\n this.contentType = ''; // Defined if/when meta-data is decrypted\n\n this.key = sodium.crypto_pwhash(\n sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,\n password,\n saltUint,\n sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,\n sodium.crypto_pwhash_ALG_ARGON2ID13\n );\n\n this.state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(headerUint, this.key);\n\n this.index = SIGNATURE.length + saltUint.length + headerUint.length;\n }\n\n /**\n * Validates that the provided password is correct\n * @param hash The hash value of the password created during UppyEncrypt\n * @param password The user-provided password\n * @returns {bool} true if correct password\n */\n static verifyPassword(hash: string, password: string) {\n return sodium.crypto_pwhash_str_verify(hash, password);\n }\n\n /**\n * Decrypts the provided file\n * @param file Blob of encryptyed file\n * @returns Decrypted file as a blob\n */\n async decryptFile(file: Blob) {\n if (!this.streamController) {\n throw new Error('Encryption stream does not exist');\n }\n\n while (this.index < file.size) {\n const chunk = await file.slice(this.index, this.index + CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES).arrayBuffer();\n const decryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, new Uint8Array(chunk));\n\n this.streamController.enqueue(decryptedChunk.message);\n\n this.index += CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;\n }\n\n this.streamController.close();\n\n const response = new Response(this.stream, { headers: { 'Content-Type': this.contentType } });\n return response.blob();\n }\n\n /**\n *\n * @param header Header created during encryption of the meta data\n * @param meta Encrypted meta data string\n * @returns object of the decrypted meta data\n */\n getDecryptedMetaData(header: string, meta: string) {\n // Init fresh state\n const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(sodium.from_base64(header, sodium.base64_variants.URLSAFE_NO_PADDING), this.key);\n const decryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_pull(state, sodium.from_base64(meta, sodium.base64_variants.URLSAFE_NO_PADDING));\n\n if (!decryptedChunk) throw new Error('Unable to decrypt meta data');\n const decryptedMeta = JSON.parse(new TextDecoder().decode(decryptedChunk.message)) as DecryptedMetaData;\n if (decryptedMeta.type) this.contentType = decryptedMeta.type;\n return decryptedMeta;\n }\n}\n","import UppyEncrypt from './UppyEncrypt';\nimport UppyDecrypt, { type DecryptedMetaData } from './UppyDecrypt';\nimport _sodium from 'libsodium-wrappers-sumo';\nimport { BasePlugin, type DefaultPluginOptions, Uppy } from '@uppy/core';\n\ninterface UppyEncryptPluginOptions extends DefaultPluginOptions {\n password: string | null;\n}\n\n// Sodium is initialized automatically within UppyEncrypt / UppyDecrypt\n// Optionally call this to ensure initialization\nlet sodiumIsReady = false;\nexport const uppyEncryptReady = async () => {\n if (!sodiumIsReady) {\n await _sodium.ready;\n sodiumIsReady = true;\n }\n};\n\nexport class UppyEncryptPlugin extends BasePlugin {\n opts: UppyEncryptPluginOptions;\n\n constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined) {\n super(uppy, opts);\n this.id = opts?.id ?? 'UppyEncryptPlugin';\n this.type = 'modifier';\n\n const defaultOptions = {\n password: null,\n };\n this.opts = { ...defaultOptions, ...opts };\n\n this.encryptFiles = this.encryptFiles.bind(this);\n }\n\n async encryptFiles(fileIds: string[]) {\n // Generate a password here if none is already set\n this.opts.password = this.opts.password || UppyEncrypt.generatePassword();\n\n // Add password to meta data so it can be referenced externally\n this.uppy.setMeta({ password: this.opts.password });\n\n for (const fileId of fileIds) {\n const file = this.uppy.getFile(fileId);\n const enc = new UppyEncrypt(this.uppy, file, this.opts.password);\n if (await enc.encryptFile()) {\n this.uppy.emit('preprocess-complete', file);\n let blob = await enc.getEncryptedFile();\n this.uppy.setFileState(fileId, {\n type: 'application/octet-stream',\n data: blob,\n size: blob.size,\n });\n\n this.uppy.setFileMeta(fileId, {\n name: `${file.name}.enc`,\n type: 'application/octet-stream',\n encryption: {\n salt: enc.getSalt(),\n header: enc.getHeader(),\n hash: enc.getPasswordHash(),\n meta: enc.getEncryptMetaData(),\n },\n });\n }\n }\n }\n\n install() {\n this.uppy.addPreProcessor(this.encryptFiles);\n }\n\n uninstall() {\n this.uppy.removePreProcessor(this.encryptFiles);\n }\n}\n\nexport { UppyEncrypt, UppyDecrypt, DecryptedMetaData };\n"],"mappings":"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","names":["_sodium","sodium","_sodium","_sodium","_sodium"]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "uppy-encrypt",
3
+ "version": "1.0.0",
4
+ "description": "Uppy plugin to encrypt and decrypt files in the browser before upload using libsodium-wrappers",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "test": "echo \"Error: no test specified\" && exit 1"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/0sumcode/uppy-encrypt.git"
18
+ },
19
+ "keywords": [
20
+ "uppy",
21
+ "encrypt",
22
+ "decrypt",
23
+ "libsodium"
24
+ ],
25
+ "author": "Dan Stevens",
26
+ "license": "MIT",
27
+ "bugs": {
28
+ "url": "https://github.com/0sumcode/uppy-encrypt/issues"
29
+ },
30
+ "homepage": "https://github.com/0sumcode/uppy-encrypt#readme",
31
+ "devDependencies": {
32
+ "ts-node": "^10.9.1",
33
+ "tsup": "^8.0.1",
34
+ "typescript": "^5.3.2"
35
+ },
36
+ "dependencies": {
37
+ "@types/libsodium-wrappers-sumo": "^0.7.8",
38
+ "@uppy/core": "^3.7.1",
39
+ "libsodium-wrappers-sumo": "^0.7.13"
40
+ }
41
+ }