uppy-encrypt 1.0.2 → 2.0.1
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/README.md +17 -3
- package/dist/index.cjs +7 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
# Uppy Encrypt
|
|
2
2
|
|
|
3
|
-
An [Uppy](https://uppy.io/) Plugin to encrypt files
|
|
3
|
+
An [Uppy](https://uppy.io/) Plugin to encrypt files via the browser before they're uploaded. Decryption is handled browser-side as well.
|
|
4
|
+
|
|
5
|
+
Oh, also, it's fast AF. 🚀
|
|
4
6
|
|
|
5
7
|
Uppy Encrypt uses [libsodium.js](https://github.com/jedisct1/libsodium.js) for all the cryptographical magic.
|
|
6
8
|
|
|
9
|
+
A live implementation of Uppy Encrypt can be seen on [0up.io](https://0up.io) [[Source Code]](https://github.com/0sumcode/0up)
|
|
10
|
+
|
|
11
|
+
## Compatibility
|
|
12
|
+
|
|
13
|
+
`uppy-encrypt` supports both `@uppy/core` 3.x and 4.x. `@uppy/core` is a peer
|
|
14
|
+
dependency, so install it alongside `uppy-encrypt`:
|
|
15
|
+
|
|
16
|
+
| uppy-encrypt | @uppy/core |
|
|
17
|
+
| ------------ | -------------- |
|
|
18
|
+
| `^2.0.0` | `^3.7.1 \|\| ^4.0.0` |
|
|
19
|
+
| `^1.0.0` | `^3.7.1` |
|
|
20
|
+
|
|
7
21
|
## Installation
|
|
8
22
|
|
|
9
23
|
```bash
|
|
10
|
-
npm i uppy-encrypt
|
|
24
|
+
npm i uppy-encrypt @uppy/core
|
|
11
25
|
```
|
|
12
26
|
|
|
13
27
|
## Encryption Example
|
|
14
28
|
```javascript
|
|
15
29
|
import { Uppy } from '@uppy/core';
|
|
16
|
-
import UppyEncryptPlugin from 'uppy-encrypt';
|
|
30
|
+
import { UppyEncryptPlugin } from 'uppy-encrypt';
|
|
17
31
|
|
|
18
32
|
const uppy = new Uppy();
|
|
19
33
|
uppy.use(UppyEncryptPlugin);
|
package/dist/index.cjs
CHANGED
|
@@ -88,9 +88,11 @@ var UppyEncrypt = class {
|
|
|
88
88
|
constructor(uppy, file, password) {
|
|
89
89
|
this.streamCanceled = false;
|
|
90
90
|
this.index = 0;
|
|
91
|
+
var _a;
|
|
91
92
|
this.uppy = uppy;
|
|
92
93
|
this.file = file;
|
|
93
94
|
this.password = password;
|
|
95
|
+
this.fileSize = (_a = file.size) != null ? _a : file.data.size;
|
|
94
96
|
uppy.on("cancel-all", () => {
|
|
95
97
|
this.streamCanceled = true;
|
|
96
98
|
});
|
|
@@ -127,7 +129,7 @@ var UppyEncrypt = class {
|
|
|
127
129
|
if (!this.streamController) {
|
|
128
130
|
throw new Error("Encryption stream does not exist");
|
|
129
131
|
}
|
|
130
|
-
while (this.index < this.
|
|
132
|
+
while (this.index < this.fileSize) {
|
|
131
133
|
if (this.streamCanceled) {
|
|
132
134
|
yield this.stream.cancel();
|
|
133
135
|
return false;
|
|
@@ -137,14 +139,14 @@ var UppyEncrypt = class {
|
|
|
137
139
|
this.streamController.enqueue(this.salt);
|
|
138
140
|
this.streamController.enqueue(this.header);
|
|
139
141
|
}
|
|
140
|
-
const tag = this.index + CHUNK_SIZE < this.
|
|
142
|
+
const tag = this.index + CHUNK_SIZE < this.fileSize ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
|
|
141
143
|
const chunk = yield this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();
|
|
142
144
|
const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);
|
|
143
145
|
this.streamController.enqueue(new Uint8Array(encryptedChunk));
|
|
144
146
|
this.uppy.emit("preprocess-progress", this.file, {
|
|
145
147
|
mode: "determinate",
|
|
146
148
|
message: `Encrypting ${this.file.name}...`,
|
|
147
|
-
value: this.index / this.
|
|
149
|
+
value: this.index / this.fileSize
|
|
148
150
|
});
|
|
149
151
|
this.index += CHUNK_SIZE;
|
|
150
152
|
}
|
|
@@ -289,6 +291,7 @@ var UppyDecrypt = class {
|
|
|
289
291
|
// src/index.ts
|
|
290
292
|
var import_libsodium_wrappers_sumo3 = __toESM(require("libsodium-wrappers-sumo"), 1);
|
|
291
293
|
var import_core = require("@uppy/core");
|
|
294
|
+
var BasePluginCompat = import_core.BasePlugin;
|
|
292
295
|
var sodiumIsReady = false;
|
|
293
296
|
var uppyEncryptReady = () => __async(void 0, null, function* () {
|
|
294
297
|
if (!sodiumIsReady) {
|
|
@@ -296,7 +299,7 @@ var uppyEncryptReady = () => __async(void 0, null, function* () {
|
|
|
296
299
|
sodiumIsReady = true;
|
|
297
300
|
}
|
|
298
301
|
});
|
|
299
|
-
var UppyEncryptPlugin = class extends
|
|
302
|
+
var UppyEncryptPlugin = class extends BasePluginCompat {
|
|
300
303
|
constructor(uppy, opts) {
|
|
301
304
|
var _a;
|
|
302
305
|
super(uppy, opts);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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 as _BasePlugin, type Uppy } from '@uppy/core';\n\ninterface UppyEncryptPluginOptions {\n id?: string;\n password?: string | null;\n}\n\n// Uppy 3 exposed `BasePlugin<TOptions>` (1 generic); Uppy 4 exposes\n// `BasePlugin<Opts, M, B, PluginState>` where Opts/M/B have no defaults.\n// Cast to a structural type covering the BasePlugin instance surface that\n// `uppy.use()` type-checks against on both majors — including setOptions,\n// getPluginState, setPluginState which Uppy 3's `.use()` requires.\n// Runtime behavior is identical: the real BasePlugin supplies these.\nconst BasePluginCompat = _BasePlugin as unknown as new (\n uppy: Uppy,\n opts?: unknown,\n) => {\n id: string;\n type: string;\n uppy: Uppy;\n install(): void;\n uninstall(): void;\n setOptions(newOpts: Partial<Record<string, unknown>>): void;\n getPluginState(): Record<string, unknown>;\n setPluginState(update?: Partial<Record<string, unknown>>): void;\n update(state: unknown): void;\n afterUpdate(): void;\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 BasePluginCompat {\n opts: UppyEncryptPluginOptions;\n\n constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions) {\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// `any` generics keep this compatible with both Uppy 3.x\n// (`UppyFile<TMeta, TBody>`) and Uppy 4.x (`UppyFile<M extends Meta, B extends Body>`)\n// without forcing consumers to pin a specific Body shape.\ntype AnyUppyFile = UppyFile<any, any>;\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: AnyUppyFile;\n private fileSize: number;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private streamCanceled = false;\n\n private index = 0;\n\n constructor(uppy: Uppy, file: AnyUppyFile, password: string) {\n this.uppy = uppy;\n this.file = file;\n this.password = password;\n // Uppy 4 types `UppyFile.size` as `number | null`; fall back to the\n // underlying Blob/File size when Uppy hasn't populated it yet.\n this.fileSize = file.size ?? file.data.size;\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.fileSize) {\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.fileSize\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 as any, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: this.index / this.fileSize,\n });\n\n this.index += CHUNK_SIZE;\n }\n\n this.uppy.emit('preprocess-progress', this.file as any, {\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,qCAAoB;;;ACDb,IAAM,aAAa,KAAK,OAAO;AAC/B,IAAM,YAAY;;;ADSzB,IAAI;AAAA,CACH,MAAY;AACX,QAAM,+BAAAA,QAAQ;AACd,WAAS,+BAAAA;AACX,IAAG;AAEH,IAAqB,cAArB,MAAiC;AAAA,EAe/B,YAAY,MAAY,MAAmB,UAAkB;AAJ7D,SAAQ,iBAAiB;AAEzB,SAAQ,QAAQ;AA7BlB;AAgCI,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,WAAW;AAGhB,SAAK,YAAW,UAAK,SAAL,YAAa,KAAK,KAAK;AAGvC,SAAK,GAAG,cAAc,MAAM;AAC1B,WAAK,iBAAiB;AAAA,IACxB,CAAC;AAED,SAAK;AACL,SAAK,SAAS,IAAI,eAAe;AAAA,MAC/B,OAAO,CAAC,eAAe;AACrB,aAAK,mBAAmB;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,SAAK,OAAO,OAAO,gBAAgB,OAAO,uBAAuB;AACjE,SAAK,MAAM,OAAO;AAAA,MAChB,OAAO;AAAA,MACP;AAAA,MACA,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAEA,UAAM,MAAM,OAAO,gDAAgD,KAAK,GAAG;AAC3E,SAAK,QAAQ,IAAI;AACjB,SAAK,SAAS,IAAI;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,mBAAmB;AACxB,WAAO,OAAO,UAAU,OAAO,gBAAgB,EAAE,GAAG,OAAO,gBAAgB,kBAAkB;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA,EAKM,cAAc;AAAA;AAClB,UAAI,CAAC,KAAK,kBAAkB;AAC1B,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AAEA,aAAO,KAAK,QAAQ,KAAK,UAAU;AACjC,YAAI,KAAK,gBAAgB;AACvB,gBAAM,KAAK,OAAO,OAAO;AACzB,iBAAO;AAAA,QACT;AAGA,YAAI,KAAK,UAAU,GAAG;AACpB,eAAK,iBAAiB,QAAQ,IAAI,WAAW,IAAI,YAAY,EAAE,OAAO,SAAS,CAAC,CAAC;AACjF,eAAK,iBAAiB,QAAQ,KAAK,IAAI;AACvC,eAAK,iBAAiB,QAAQ,KAAK,MAAM;AAAA,QAC3C;AAEA,cAAM,MACJ,KAAK,QAAQ,aAAa,KAAK,WAC3B,OAAO,oDACP,OAAO;AAEb,cAAM,QAAQ,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,UAAU,EAAE,YAAY;AAC1F,cAAM,iBAAiB,OAAO,2CAA2C,KAAK,OAAO,IAAI,WAAW,KAAK,GAAG,MAAM,GAAG;AAErH,aAAK,iBAAiB,QAAQ,IAAI,WAAW,cAAc,CAAC;AAE5D,aAAK,KAAK,KAAK,uBAAuB,KAAK,MAAa;AAAA,UACtD,MAAM;AAAA,UACN,SAAS,cAAc,KAAK,KAAK,IAAI;AAAA,UACrC,OAAO,KAAK,QAAQ,KAAK;AAAA,QAC3B,CAAC;AAED,aAAK,SAAS;AAAA,MAChB;AAEA,WAAK,KAAK,KAAK,uBAAuB,KAAK,MAAa;AAAA,QACtD,MAAM;AAAA,QACN,SAAS,cAAc,KAAK,KAAK,IAAI;AAAA,QACrC,OAAO;AAAA,MACT,CAAC;AAED,WAAK,iBAAiB,MAAM;AAE5B,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,mBAAmB;AAAA;AACvB,YAAM,WAAW,IAAI,SAAS,KAAK,MAAM;AACzC,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAqB;AAEnB,UAAM,MAAM,OAAO,gDAAgD,KAAK,GAAG;AAE3E,UAAM,WAAW,KAAK,UAAU,EAAE,MAAM,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK,KAAK,QAAQ,KAAK,CAAC;AAChG,UAAM,iBAAiB,OAAO;AAAA,MAC5B,IAAI;AAAA,MACJ,IAAI,WAAW,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AAAA,MACjD;AAAA,MACA,OAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,QAAQ,OAAO,UAAU,IAAI,QAAQ,OAAO,gBAAgB,kBAAkB;AAAA,MAC9E,MAAM,OAAO,UAAU,gBAAgB,OAAO,gBAAgB,kBAAkB;AAAA,IAClF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB;AAChB,WAAO,OAAO,kBAAkB,KAAK,UAAU,OAAO,oCAAoC,OAAO,kCAAkC;AAAA,EACrI;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY;AACV,WAAO,OAAO,UAAU,KAAK,QAAQ,OAAO,gBAAgB,kBAAkB;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AACR,WAAO,OAAO,UAAU,KAAK,MAAM,OAAO,gBAAgB,kBAAkB;AAAA,EAC9E;AACF;;;AElLA,IAAAC,kCAAoB;AASpB,IAAIC;AAAA,CACH,MAAY;AACX,QAAM,gCAAAC,QAAQ;AACd,EAAAD,UAAS,gCAAAC;AACX,IAAG;AAEH,IAAqB,cAArB,MAAiC;AAAA,EAS/B,YAAY,UAAkB,MAAc,QAAgB;AAF5D,SAAQ,QAAQ;AAGd,UAAM,WAAWD,QAAO,YAAY,MAAMA,QAAO,gBAAgB,kBAAkB;AACnF,UAAM,aAAaA,QAAO,YAAY,QAAQA,QAAO,gBAAgB,kBAAkB;AAEvF,SAAK;AACL,SAAK,SAAS,IAAI,eAAe;AAAA,MAC/B,OAAO,CAAC,eAAe;AACrB,aAAK,mBAAmB;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,SAAK,cAAc;AAEnB,SAAK,MAAMA,QAAO;AAAA,MAChBA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACAA,QAAO;AAAA,MACPA,QAAO;AAAA,MACPA,QAAO;AAAA,IACT;AAEA,SAAK,QAAQA,QAAO,gDAAgD,YAAY,KAAK,GAAG;AAExF,SAAK,QAAQ,UAAU,SAAS,SAAS,SAAS,WAAW;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,eAAe,MAAc,UAAkB;AACpD,WAAOA,QAAO,yBAAyB,MAAM,QAAQ;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,YAAY,MAAY;AAAA;AAC5B,UAAI,CAAC,KAAK,kBAAkB;AAC1B,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AAEA,aAAO,KAAK,QAAQ,KAAK,MAAM;AAC7B,cAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,aAAaA,QAAO,4CAA4C,EAAE,YAAY;AACtI,cAAM,iBAAiBA,QAAO,2CAA2C,KAAK,OAAO,IAAI,WAAW,KAAK,CAAC;AAE1G,aAAK,iBAAiB,QAAQ,eAAe,OAAO;AAEpD,aAAK,SAAS,aAAaA,QAAO;AAAA,MACpC;AAEA,WAAK,iBAAiB,MAAM;AAE5B,YAAM,WAAW,IAAI,SAAS,KAAK,QAAQ,EAAE,SAAS,EAAE,gBAAgB,KAAK,YAAY,EAAE,CAAC;AAC5F,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAqB,QAAgB,MAAc;AAEjD,UAAM,QAAQA,QAAO,gDAAgDA,QAAO,YAAY,QAAQA,QAAO,gBAAgB,kBAAkB,GAAG,KAAK,GAAG;AACpJ,UAAM,iBAAiBA,QAAO,2CAA2C,OAAOA,QAAO,YAAY,MAAMA,QAAO,gBAAgB,kBAAkB,CAAC;AAEnJ,QAAI,CAAC;AAAgB,YAAM,IAAI,MAAM,6BAA6B;AAClE,UAAM,gBAAgB,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,eAAe,OAAO,CAAC;AACjF,QAAI,cAAc;AAAM,WAAK,cAAc,cAAc;AACzD,WAAO;AAAA,EACT;AACF;;;AHnGA,IAAAE,kCAAoB;AACpB,kBAAqD;AAarD,IAAM,mBAAmB,YAAAC;AAkBzB,IAAI,gBAAgB;AACb,IAAM,mBAAmB,MAAY;AAC1C,MAAI,CAAC,eAAe;AAClB,UAAM,gCAAAC,QAAQ;AACd,oBAAgB;AAAA,EAClB;AACF;AAEO,IAAM,oBAAN,cAAgC,iBAAiB;AAAA,EAGtD,YAAY,MAAY,MAAiC;AA7C3D;AA8CI,UAAM,MAAM,IAAI;AAChB,SAAK,MAAK,kCAAM,OAAN,YAAY;AACtB,SAAK,OAAO;AAEZ,UAAM,iBAAiB;AAAA,MACrB,UAAU;AAAA,IACZ;AACA,SAAK,OAAO,kCAAK,iBAAmB;AAEpC,SAAK,eAAe,KAAK,aAAa,KAAK,IAAI;AAAA,EACjD;AAAA,EAEM,aAAa,SAAmB;AAAA;AAEpC,WAAK,KAAK,WAAW,KAAK,KAAK,YAAY,YAAY,iBAAiB;AAGxE,WAAK,KAAK,QAAQ,EAAE,UAAU,KAAK,KAAK,SAAS,CAAC;AAElD,iBAAW,UAAU,SAAS;AAC5B,cAAM,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrC,cAAM,MAAM,IAAI,YAAY,KAAK,MAAM,MAAM,KAAK,KAAK,QAAQ;AAC/D,YAAI,MAAM,IAAI,YAAY,GAAG;AAC3B,eAAK,KAAK,KAAK,uBAAuB,IAAI;AAC1C,cAAI,OAAO,MAAM,IAAI,iBAAiB;AACtC,eAAK,KAAK,aAAa,QAAQ;AAAA,YAC7B,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,KAAK;AAAA,UACb,CAAC;AAED,eAAK,KAAK,YAAY,QAAQ;AAAA,YAC5B,MAAM,GAAG,KAAK,IAAI;AAAA,YAClB,MAAM;AAAA,YACN,YAAY;AAAA,cACV,MAAM,IAAI,QAAQ;AAAA,cAClB,QAAQ,IAAI,UAAU;AAAA,cACtB,MAAM,IAAI,gBAAgB;AAAA,cAC1B,MAAM,IAAI,mBAAmB;AAAA,YAC/B;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEA,UAAU;AACR,SAAK,KAAK,gBAAgB,KAAK,YAAY;AAAA,EAC7C;AAAA,EAEA,YAAY;AACV,SAAK,KAAK,mBAAmB,KAAK,YAAY;AAAA,EAChD;AACF;","names":["_sodium","import_libsodium_wrappers_sumo","sodium","_sodium","import_libsodium_wrappers_sumo","_BasePlugin","_sodium"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Uppy, UppyFile
|
|
1
|
+
import { Uppy, UppyFile } from '@uppy/core';
|
|
2
2
|
|
|
3
|
+
type AnyUppyFile = UppyFile<any, any>;
|
|
3
4
|
declare class UppyEncrypt {
|
|
4
5
|
private uppy;
|
|
5
6
|
private password;
|
|
@@ -8,11 +9,12 @@ declare class UppyEncrypt {
|
|
|
8
9
|
private state;
|
|
9
10
|
private header;
|
|
10
11
|
private file;
|
|
12
|
+
private fileSize;
|
|
11
13
|
private stream;
|
|
12
14
|
private streamController;
|
|
13
15
|
private streamCanceled;
|
|
14
16
|
private index;
|
|
15
|
-
constructor(uppy: Uppy, file:
|
|
17
|
+
constructor(uppy: Uppy, file: AnyUppyFile, password: string);
|
|
16
18
|
static generatePassword(): string;
|
|
17
19
|
encryptFile(): Promise<boolean>;
|
|
18
20
|
getEncryptedFile(): Promise<Blob>;
|
|
@@ -42,13 +44,26 @@ declare class UppyDecrypt {
|
|
|
42
44
|
getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
interface UppyEncryptPluginOptions
|
|
46
|
-
|
|
47
|
+
interface UppyEncryptPluginOptions {
|
|
48
|
+
id?: string;
|
|
49
|
+
password?: string | null;
|
|
47
50
|
}
|
|
51
|
+
declare const BasePluginCompat: new (uppy: Uppy, opts?: unknown) => {
|
|
52
|
+
id: string;
|
|
53
|
+
type: string;
|
|
54
|
+
uppy: Uppy;
|
|
55
|
+
install(): void;
|
|
56
|
+
uninstall(): void;
|
|
57
|
+
setOptions(newOpts: Partial<Record<string, unknown>>): void;
|
|
58
|
+
getPluginState(): Record<string, unknown>;
|
|
59
|
+
setPluginState(update?: Partial<Record<string, unknown>>): void;
|
|
60
|
+
update(state: unknown): void;
|
|
61
|
+
afterUpdate(): void;
|
|
62
|
+
};
|
|
48
63
|
declare const uppyEncryptReady: () => Promise<void>;
|
|
49
|
-
declare class UppyEncryptPlugin extends
|
|
64
|
+
declare class UppyEncryptPlugin extends BasePluginCompat {
|
|
50
65
|
opts: UppyEncryptPluginOptions;
|
|
51
|
-
constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions
|
|
66
|
+
constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions);
|
|
52
67
|
encryptFiles(fileIds: string[]): Promise<void>;
|
|
53
68
|
install(): void;
|
|
54
69
|
uninstall(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Uppy, UppyFile
|
|
1
|
+
import { Uppy, UppyFile } from '@uppy/core';
|
|
2
2
|
|
|
3
|
+
type AnyUppyFile = UppyFile<any, any>;
|
|
3
4
|
declare class UppyEncrypt {
|
|
4
5
|
private uppy;
|
|
5
6
|
private password;
|
|
@@ -8,11 +9,12 @@ declare class UppyEncrypt {
|
|
|
8
9
|
private state;
|
|
9
10
|
private header;
|
|
10
11
|
private file;
|
|
12
|
+
private fileSize;
|
|
11
13
|
private stream;
|
|
12
14
|
private streamController;
|
|
13
15
|
private streamCanceled;
|
|
14
16
|
private index;
|
|
15
|
-
constructor(uppy: Uppy, file:
|
|
17
|
+
constructor(uppy: Uppy, file: AnyUppyFile, password: string);
|
|
16
18
|
static generatePassword(): string;
|
|
17
19
|
encryptFile(): Promise<boolean>;
|
|
18
20
|
getEncryptedFile(): Promise<Blob>;
|
|
@@ -42,13 +44,26 @@ declare class UppyDecrypt {
|
|
|
42
44
|
getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
interface UppyEncryptPluginOptions
|
|
46
|
-
|
|
47
|
+
interface UppyEncryptPluginOptions {
|
|
48
|
+
id?: string;
|
|
49
|
+
password?: string | null;
|
|
47
50
|
}
|
|
51
|
+
declare const BasePluginCompat: new (uppy: Uppy, opts?: unknown) => {
|
|
52
|
+
id: string;
|
|
53
|
+
type: string;
|
|
54
|
+
uppy: Uppy;
|
|
55
|
+
install(): void;
|
|
56
|
+
uninstall(): void;
|
|
57
|
+
setOptions(newOpts: Partial<Record<string, unknown>>): void;
|
|
58
|
+
getPluginState(): Record<string, unknown>;
|
|
59
|
+
setPluginState(update?: Partial<Record<string, unknown>>): void;
|
|
60
|
+
update(state: unknown): void;
|
|
61
|
+
afterUpdate(): void;
|
|
62
|
+
};
|
|
48
63
|
declare const uppyEncryptReady: () => Promise<void>;
|
|
49
|
-
declare class UppyEncryptPlugin extends
|
|
64
|
+
declare class UppyEncryptPlugin extends BasePluginCompat {
|
|
50
65
|
opts: UppyEncryptPluginOptions;
|
|
51
|
-
constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions
|
|
66
|
+
constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions);
|
|
52
67
|
encryptFiles(fileIds: string[]): Promise<void>;
|
|
53
68
|
install(): void;
|
|
54
69
|
uninstall(): void;
|
package/dist/index.js
CHANGED
|
@@ -52,9 +52,11 @@ var UppyEncrypt = class {
|
|
|
52
52
|
constructor(uppy, file, password) {
|
|
53
53
|
this.streamCanceled = false;
|
|
54
54
|
this.index = 0;
|
|
55
|
+
var _a;
|
|
55
56
|
this.uppy = uppy;
|
|
56
57
|
this.file = file;
|
|
57
58
|
this.password = password;
|
|
59
|
+
this.fileSize = (_a = file.size) != null ? _a : file.data.size;
|
|
58
60
|
uppy.on("cancel-all", () => {
|
|
59
61
|
this.streamCanceled = true;
|
|
60
62
|
});
|
|
@@ -91,7 +93,7 @@ var UppyEncrypt = class {
|
|
|
91
93
|
if (!this.streamController) {
|
|
92
94
|
throw new Error("Encryption stream does not exist");
|
|
93
95
|
}
|
|
94
|
-
while (this.index < this.
|
|
96
|
+
while (this.index < this.fileSize) {
|
|
95
97
|
if (this.streamCanceled) {
|
|
96
98
|
yield this.stream.cancel();
|
|
97
99
|
return false;
|
|
@@ -101,14 +103,14 @@ var UppyEncrypt = class {
|
|
|
101
103
|
this.streamController.enqueue(this.salt);
|
|
102
104
|
this.streamController.enqueue(this.header);
|
|
103
105
|
}
|
|
104
|
-
const tag = this.index + CHUNK_SIZE < this.
|
|
106
|
+
const tag = this.index + CHUNK_SIZE < this.fileSize ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
|
|
105
107
|
const chunk = yield this.file.data.slice(this.index, this.index + CHUNK_SIZE).arrayBuffer();
|
|
106
108
|
const encryptedChunk = sodium.crypto_secretstream_xchacha20poly1305_push(this.state, new Uint8Array(chunk), null, tag);
|
|
107
109
|
this.streamController.enqueue(new Uint8Array(encryptedChunk));
|
|
108
110
|
this.uppy.emit("preprocess-progress", this.file, {
|
|
109
111
|
mode: "determinate",
|
|
110
112
|
message: `Encrypting ${this.file.name}...`,
|
|
111
|
-
value: this.index / this.
|
|
113
|
+
value: this.index / this.fileSize
|
|
112
114
|
});
|
|
113
115
|
this.index += CHUNK_SIZE;
|
|
114
116
|
}
|
|
@@ -252,7 +254,8 @@ var UppyDecrypt = class {
|
|
|
252
254
|
|
|
253
255
|
// src/index.ts
|
|
254
256
|
import _sodium3 from "libsodium-wrappers-sumo";
|
|
255
|
-
import { BasePlugin } from "@uppy/core";
|
|
257
|
+
import { BasePlugin as _BasePlugin } from "@uppy/core";
|
|
258
|
+
var BasePluginCompat = _BasePlugin;
|
|
256
259
|
var sodiumIsReady = false;
|
|
257
260
|
var uppyEncryptReady = () => __async(void 0, null, function* () {
|
|
258
261
|
if (!sodiumIsReady) {
|
|
@@ -260,7 +263,7 @@ var uppyEncryptReady = () => __async(void 0, null, function* () {
|
|
|
260
263
|
sodiumIsReady = true;
|
|
261
264
|
}
|
|
262
265
|
});
|
|
263
|
-
var UppyEncryptPlugin = class extends
|
|
266
|
+
var UppyEncryptPlugin = class extends BasePluginCompat {
|
|
264
267
|
constructor(uppy, opts) {
|
|
265
268
|
var _a;
|
|
266
269
|
super(uppy, opts);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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// `any` generics keep this compatible with both Uppy 3.x\n// (`UppyFile<TMeta, TBody>`) and Uppy 4.x (`UppyFile<M extends Meta, B extends Body>`)\n// without forcing consumers to pin a specific Body shape.\ntype AnyUppyFile = UppyFile<any, any>;\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: AnyUppyFile;\n private fileSize: number;\n private stream: ReadableStream;\n private streamController: ReadableStreamDefaultController | undefined;\n private streamCanceled = false;\n\n private index = 0;\n\n constructor(uppy: Uppy, file: AnyUppyFile, password: string) {\n this.uppy = uppy;\n this.file = file;\n this.password = password;\n // Uppy 4 types `UppyFile.size` as `number | null`; fall back to the\n // underlying Blob/File size when Uppy hasn't populated it yet.\n this.fileSize = file.size ?? file.data.size;\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.fileSize) {\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.fileSize\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 as any, {\n mode: 'determinate',\n message: `Encrypting ${this.file.name}...`,\n value: this.index / this.fileSize,\n });\n\n this.index += CHUNK_SIZE;\n }\n\n this.uppy.emit('preprocess-progress', this.file as any, {\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 as _BasePlugin, type Uppy } from '@uppy/core';\n\ninterface UppyEncryptPluginOptions {\n id?: string;\n password?: string | null;\n}\n\n// Uppy 3 exposed `BasePlugin<TOptions>` (1 generic); Uppy 4 exposes\n// `BasePlugin<Opts, M, B, PluginState>` where Opts/M/B have no defaults.\n// Cast to a structural type covering the BasePlugin instance surface that\n// `uppy.use()` type-checks against on both majors — including setOptions,\n// getPluginState, setPluginState which Uppy 3's `.use()` requires.\n// Runtime behavior is identical: the real BasePlugin supplies these.\nconst BasePluginCompat = _BasePlugin as unknown as new (\n uppy: Uppy,\n opts?: unknown,\n) => {\n id: string;\n type: string;\n uppy: Uppy;\n install(): void;\n uninstall(): void;\n setOptions(newOpts: Partial<Record<string, unknown>>): void;\n getPluginState(): Record<string, unknown>;\n setPluginState(update?: Partial<Record<string, unknown>>): void;\n update(state: unknown): void;\n afterUpdate(): void;\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 BasePluginCompat {\n opts: UppyEncryptPluginOptions;\n\n constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions) {\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uppy-encrypt",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Uppy plugin to encrypt and decrypt files in the browser before upload using libsodium-wrappers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsup",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"test:uppy3": "npm i --no-save @uppy/core@^3.7.1 && vitest run; npm i --no-save @uppy/core@^4.0.0"
|
|
22
24
|
},
|
|
23
25
|
"repository": {
|
|
24
26
|
"type": "git",
|
|
@@ -37,13 +39,18 @@
|
|
|
37
39
|
},
|
|
38
40
|
"homepage": "https://github.com/0sumcode/uppy-encrypt#readme",
|
|
39
41
|
"devDependencies": {
|
|
42
|
+
"@uppy/core": "^4.0.0",
|
|
43
|
+
"happy-dom": "^20.8.9",
|
|
40
44
|
"ts-node": "^10.9.1",
|
|
41
45
|
"tsup": "^8.0.1",
|
|
42
|
-
"typescript": "^5.3.2"
|
|
46
|
+
"typescript": "^5.3.2",
|
|
47
|
+
"vitest": "^4.1.4"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@uppy/core": "^3.7.1 || ^4.0.0"
|
|
43
51
|
},
|
|
44
52
|
"dependencies": {
|
|
45
53
|
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
46
|
-
"@uppy/core": "^3.7.1",
|
|
47
54
|
"libsodium-wrappers-sumo": "^0.7.13"
|
|
48
55
|
}
|
|
49
56
|
}
|