uppy-encrypt 1.0.2 → 2.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/README.md CHANGED
@@ -1,13 +1,27 @@
1
1
  # Uppy Encrypt
2
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.
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
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.file.size) {
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.file.size ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
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.file.size
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 import_core.BasePlugin {
302
+ var UppyEncryptPlugin = class extends BasePluginCompat {
300
303
  constructor(uppy, opts) {
301
304
  var _a;
302
305
  super(uppy, opts);
@@ -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 narrow structural type so the same source compiles against\n// either major version. Runtime behavior is identical.\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};\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":"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","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, BasePlugin, DefaultPluginOptions } from '@uppy/core';
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: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string);
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,21 @@ declare class UppyDecrypt {
42
44
  getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
43
45
  }
44
46
 
45
- interface UppyEncryptPluginOptions extends DefaultPluginOptions {
46
- password: string | null;
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
+ };
48
58
  declare const uppyEncryptReady: () => Promise<void>;
49
- declare class UppyEncryptPlugin extends BasePlugin {
59
+ declare class UppyEncryptPlugin extends BasePluginCompat {
50
60
  opts: UppyEncryptPluginOptions;
51
- constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined);
61
+ constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions);
52
62
  encryptFiles(fileIds: string[]): Promise<void>;
53
63
  install(): void;
54
64
  uninstall(): void;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Uppy, UppyFile, BasePlugin, DefaultPluginOptions } from '@uppy/core';
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: UppyFile<Record<string, unknown>, Record<string, unknown>>, password: string);
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,21 @@ declare class UppyDecrypt {
42
44
  getDecryptedMetaData(header: string, meta: string): DecryptedMetaData;
43
45
  }
44
46
 
45
- interface UppyEncryptPluginOptions extends DefaultPluginOptions {
46
- password: string | null;
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
+ };
48
58
  declare const uppyEncryptReady: () => Promise<void>;
49
- declare class UppyEncryptPlugin extends BasePlugin {
59
+ declare class UppyEncryptPlugin extends BasePluginCompat {
50
60
  opts: UppyEncryptPluginOptions;
51
- constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions | undefined);
61
+ constructor(uppy: Uppy, opts?: UppyEncryptPluginOptions);
52
62
  encryptFiles(fileIds: string[]): Promise<void>;
53
63
  install(): void;
54
64
  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.file.size) {
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.file.size ? sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE : sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
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.file.size
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 BasePlugin {
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 narrow structural type so the same source compiles against\n// either major version. Runtime behavior is identical.\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};\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": "1.0.2",
3
+ "version": "2.0.0",
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": "echo \"Error: no test specified\" && exit 1"
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
  }