@secrecy/lib 1.83.4 → 1.84.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.
|
@@ -28,16 +28,17 @@ export const downloadDataLinkSchema = z.union([
|
|
|
28
28
|
downloadDataLinkS3Schema,
|
|
29
29
|
downloadDataLinkLiteSchema,
|
|
30
30
|
]);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const rawDataLinkRes = await fetch(`${dataUrl}${opts.dataLinkSlug}/json`, {
|
|
31
|
+
export async function fetchDataLinkMetadata(dataLinkSlug, dataUrl) {
|
|
32
|
+
let baseUrl = dataUrl ?? 'https://data.secrecy.tech/';
|
|
33
|
+
baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
34
|
+
const rawDataLinkRes = await fetch(`${baseUrl}${dataLinkSlug}/json`, {
|
|
36
35
|
// ! disable cookies for now
|
|
37
36
|
// credentials: 'include',
|
|
38
37
|
}).catch((err) => {
|
|
39
38
|
console.error(err);
|
|
40
|
-
throw new Error(`Unable to fetch "${`${
|
|
39
|
+
throw new Error(`Unable to fetch "${`${baseUrl}${dataLinkSlug}/json"`}`, {
|
|
40
|
+
cause: err,
|
|
41
|
+
});
|
|
41
42
|
});
|
|
42
43
|
const rawDataLink = await rawDataLinkRes.json().catch((err) => {
|
|
43
44
|
console.error(err);
|
|
@@ -47,28 +48,33 @@ export async function downloadDataFromLink(opts) {
|
|
|
47
48
|
if (dataLink.error) {
|
|
48
49
|
throw new Error(`Should not happen!`, { cause: dataLink.error });
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
return dataLink.data;
|
|
52
|
+
}
|
|
53
|
+
// TODO: Add an opt header for authed users!
|
|
54
|
+
export async function downloadDataFromLink(opts) {
|
|
55
|
+
const dataLink = await fetchDataLinkMetadata(opts.dataLinkSlug, opts.dataUrl);
|
|
56
|
+
if (dataLink.isEncrypted && !opts.crypto) {
|
|
51
57
|
throw new Error('Unable to read encrypted data without password!');
|
|
52
58
|
}
|
|
53
59
|
const bytes = await buildBytesFromDataLink({
|
|
54
|
-
dataLink: dataLink
|
|
55
|
-
totalBytes: Number(dataLink.
|
|
60
|
+
dataLink: dataLink,
|
|
61
|
+
totalBytes: Number(dataLink.size),
|
|
56
62
|
signal: opts.signal,
|
|
57
63
|
onDownloadProgress: opts.downloadProgress,
|
|
58
64
|
});
|
|
59
|
-
if (!dataLink.
|
|
65
|
+
if (!dataLink.isEncrypted) {
|
|
60
66
|
return bytes;
|
|
61
67
|
}
|
|
62
68
|
if (!opts.crypto) {
|
|
63
69
|
throw new Error('Unable to read encrypted data without password!');
|
|
64
70
|
}
|
|
65
71
|
try {
|
|
66
|
-
const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, opts.crypto.password + dataLink.
|
|
72
|
+
const salt = sodium.crypto_generichash(sodium.crypto_pwhash_SALTBYTES, opts.crypto.password + dataLink.md5);
|
|
67
73
|
const derivedPassword = derivePassword(opts.crypto.password, salt);
|
|
68
74
|
const decryptedSharingDataKey = decryptSecretBox(sodium.from_hex(opts.crypto.key), derivedPassword);
|
|
69
75
|
const decryptedBytes = await decrypt(decryptedSharingDataKey, bytes, opts.decryptProgress, opts.signal);
|
|
70
76
|
const md5Content = await md5(decryptedBytes);
|
|
71
|
-
if (md5Content !== dataLink.
|
|
77
|
+
if (md5Content !== dataLink.md5) {
|
|
72
78
|
throw new Error(`Content does not match`);
|
|
73
79
|
}
|
|
74
80
|
return decompress(decryptedBytes);
|
|
@@ -7,7 +7,7 @@ self.sodium = {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
importScripts('https://cdn.jsdelivr.net/gh/jedisct1/libsodium.js@
|
|
10
|
+
importScripts('https://cdn.jsdelivr.net/gh/jedisct1/libsodium.js@0.7.15/dist/browsers/sodium.js');
|
|
11
11
|
importScripts('https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js');
|
|
12
12
|
|
|
13
13
|
function* chunks(arr, n) {
|
|
@@ -32,6 +32,27 @@ export type DownloadDataFromLinkOptions = {
|
|
|
32
32
|
downloadProgress?: (progress: Progress) => void;
|
|
33
33
|
signal?: AbortSignal;
|
|
34
34
|
};
|
|
35
|
+
export declare function fetchDataLinkMetadata(dataLinkSlug: string, dataUrl?: string): Promise<{
|
|
36
|
+
name: string;
|
|
37
|
+
md5: string;
|
|
38
|
+
md5Encrypted: string | null;
|
|
39
|
+
size: bigint;
|
|
40
|
+
parts: {
|
|
41
|
+
order: number;
|
|
42
|
+
md5: string;
|
|
43
|
+
contentUrl: string;
|
|
44
|
+
}[];
|
|
45
|
+
mime: string;
|
|
46
|
+
isEncrypted: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
name: string;
|
|
49
|
+
md5: string;
|
|
50
|
+
md5Encrypted: string | null;
|
|
51
|
+
size: bigint;
|
|
52
|
+
bytes: string;
|
|
53
|
+
mime: string;
|
|
54
|
+
isEncrypted: boolean;
|
|
55
|
+
}>;
|
|
35
56
|
export declare function downloadDataFromLink(opts: DownloadDataFromLinkOptions & {
|
|
36
57
|
dataUrl?: string;
|
|
37
58
|
}): Promise<Uint8Array<ArrayBuffer>>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const workerSodiumScript = "let sodium;\n\nself.sodium = {\n onload: (sod) => {\n sodium = sod\n postMessage({ event: \"ready\" })\n }\n};\n\nimportScripts('https://cdn.jsdelivr.net/gh/jedisct1/libsodium.js@
|
|
1
|
+
export declare const workerSodiumScript = "let sodium;\n\nself.sodium = {\n onload: (sod) => {\n sodium = sod\n postMessage({ event: \"ready\" })\n }\n};\n\nimportScripts('https://cdn.jsdelivr.net/gh/jedisct1/libsodium.js@0.7.15/dist/browsers/sodium.js');\nimportScripts('https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js');\n\nfunction* chunks(arr, n) {\n for (let i = 0; i < arr.length; i += n) {\n yield arr.slice(i, i + n);\n }\n}\n\nfunction assert(c, message) {\n if (!c) {\n throw new Error(message);\n }\n}\n\nfunction encrypt(key) {\n let destroyed = false;\n const {\n state,\n header\n } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);\n\n const encrypt = (tag, plaintext) => {\n assert(destroyed === false, \"state already destroyed\");\n\n return sodium.crypto_secretstream_xchacha20poly1305_push(\n state,\n plaintext,\n null,\n tag\n );\n };\n\n function destroy() {\n assert(destroyed === false, \"state already destroyed\");\n destroyed = true;\n }\n\n return {\n encrypt,\n destroy,\n header\n };\n}\n\nfunction decrypt(header, key) {\n assert(\n header.byteLength >=\n sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES,\n \"header must be at least HEADERBYTES (\" + sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES + \") long\"\n );\n assert(\n key.byteLength >= sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES,\n \"key must be at least KEYBYTES (\" + sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES + \") long\"\n );\n\n let destroyed = false;\n const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(\n header,\n key\n );\n\n const decrypt = ciphertext => {\n assert(destroyed === false, \"state already destroyed\");\n\n return sodium.crypto_secretstream_xchacha20poly1305_pull(state, ciphertext);\n };\n\n function destroy() {\n assert(destroyed === false, \"state already destroyed\");\n destroyed = true;\n }\n\n return {\n decrypt,\n destroy\n };\n}\n\nconst CHUNK_SIZE = 8192;\n\nasync function encryptSecretstream(key, data, progress) {\n const { encrypt: crypt, destroy, header } = encrypt(key);\n const cryptedChunk =\n CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;\n const max =\n Math.ceil(data.byteLength / CHUNK_SIZE) * cryptedChunk + header.byteLength;\n\n progress?.({\n percent: 0,\n total: max,\n current: 0\n });\n const final = new Uint8Array(max);\n const sparkEncrypted = new SparkMD5.ArrayBuffer();\n const spark = new SparkMD5.ArrayBuffer();\n\n final.set(header);\n sparkEncrypted.append(header);\n let total = header.byteLength;\n progress?.({\n percent: total / max,\n total: max,\n current: total\n });\n let lastPercent = total / max;\n\n for (const chunk of chunks(data, CHUNK_SIZE)) {\n spark.append(chunk);\n const tag =\n chunk.length < CHUNK_SIZE\n ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL\n : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;\n const crypted = crypt(tag, chunk);\n sparkEncrypted.append(crypted);\n final.set(crypted, total);\n total += crypted.byteLength;\n const percent = total / max;\n if (percent > lastPercent + 0.01) {\n progress?.({\n percent,\n total: max,\n current: total\n });\n lastPercent = percent;\n }\n }\n\n destroy();\n progress?.({\n percent: 1,\n total,\n current: total\n });\n return {\n data: final.slice(0, total),\n md5Encrypted: sparkEncrypted.end(),\n md5: spark.end()\n };\n}\n\nasync function decryptSecretstream(key, data, progress) {\n const header = data.slice(\n 0,\n sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES\n );\n data = data.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);\n\n const { decrypt: decryptt, destroy } = decrypt(header, key);\n const chunkSize =\n CHUNK_SIZE + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;\n const max = Math.ceil(data.byteLength / chunkSize) * CHUNK_SIZE;\n\n progress?.({\n percent: 0,\n total: max,\n current: 0\n });\n const final = new Uint8Array(max);\n let total = 0;\n let lastPercent = total / max;\n\n for (const chunk of chunks(data, chunkSize)) {\n const tmp = decryptt(chunk);\n final.set(tmp.message, total);\n total += tmp.message.byteLength;\n\n const percent = total / max;\n if (percent > lastPercent + 0.01) {\n progress?.({\n percent,\n total: max,\n current: total\n });\n lastPercent = percent;\n }\n }\n\n destroy();\n progress?.({\n percent: 1,\n total,\n current: total\n });\n return final.slice(0, total);\n}\n\nself.onmessage = async ({ data }) => {\n switch (data.event) {\n case \"encrypt\": {\n postMessage({ event: \"working\" })\n postMessage({\n event: \"encrypt-result\",\n data: await encryptSecretstream(data.key, data.data, progress => postMessage({\n event: \"encrypt-progress\",\n data: progress\n }))\n });\n postMessage({ event: \"ready\" })\n break;\n }\n case \"decrypt\": {\n postMessage({ event: \"working\" })\n postMessage({\n event: \"decrypt-result\",\n data: await decryptSecretstream(data.key, data.data, progress => postMessage({\n event: \"decrypt-progress\",\n data: progress\n }))\n });\n postMessage({ event: \"ready\" })\n break;\n }\n }\n}";
|
|
2
2
|
export declare const workerMd5Script = "importScripts('https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js');\n\nfunction* chunks(arr, n) {\n for (let i = 0; i < arr.length; i += n) {\n yield arr.slice(i, i + n);\n }\n}\n\nconst CHUNK_SIZE = 8192;\n\nfunction md5(data) {\n const spark = new SparkMD5.ArrayBuffer();\n for (const chunk of chunks(data, CHUNK_SIZE)) {\n spark.append(chunk);\n }\n return spark.end();\n}\n\n\nself.onmessage = ({ data }) => {\n switch (data.event) {\n case \"md5\": {\n postMessage({\n event: \"md5-result\",\n data: md5(data.data)\n });\n break;\n }\n }\n}";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@secrecy/lib",
|
|
3
3
|
"author": "Anonymize <anonymize@gmail.com>",
|
|
4
4
|
"description": "Anonymize Secrecy Library",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.84.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/anonymize-org/lib.git"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"npm-run-all": "^4.1.5",
|
|
69
69
|
"prettier": "^3.6.2",
|
|
70
70
|
"rimraf": "^6.0.1",
|
|
71
|
-
"semantic-release": "^
|
|
71
|
+
"semantic-release": "^25.0.2",
|
|
72
72
|
"typedoc": "^0.28.14",
|
|
73
73
|
"typedoc-plugin-markdown": "^4.9.0",
|
|
74
74
|
"typedoc-plugin-missing-exports": "^4.1.2",
|