bytifi 0.1.3 → 0.1.5
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 +48 -35
- package/bin/bytifi.js +23 -10
- package/lib/decrypt.js +135 -34
- package/lib/upload.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,45 +53,57 @@ Prefer the environment variable — keys on the command line can appear in shell
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
bytifi upload ./photo.png
|
|
56
|
-
bytifi upload ./
|
|
57
|
-
bytifi upload ./
|
|
58
|
-
bytifi upload ./large.iso
|
|
56
|
+
bytifi upload "./my video (1).mp4"
|
|
57
|
+
bytifi upload ./report.pdf --expires 60 --delete-on-download
|
|
58
|
+
bytifi upload ./large.iso --json > upload.json
|
|
59
|
+
bytifi upload ./photo.png -q
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
Upload accepts **one file at a time**. Quote paths that contain spaces. Avoid shell globs like `**` — your shell may expand them into dozens of paths.
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
### Decrypt from a link
|
|
65
|
+
|
|
66
|
+
Download and decrypt directly from a share URL or link token. No API key required.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bytifi decrypt 'https://bytifi.com/link?link=LINK_ID#token=ENCRYPTION_TOKEN'
|
|
70
|
+
bytifi decrypt LINK_ID --token ENCRYPTION_TOKEN -o ./restored.mp4
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Decrypt a downloaded encrypted file
|
|
74
|
+
|
|
75
|
+
If you already downloaded the encrypted blob from `/f/LINK_ID` (browser, curl, etc.):
|
|
64
76
|
|
|
65
77
|
```bash
|
|
66
|
-
#
|
|
67
|
-
bytifi decrypt
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
bytifi decrypt ./parts/ --link TOKEN --token ENCRYPTION_TOKEN
|
|
78
|
+
# Easiest — use the upload JSON from when you uploaded
|
|
79
|
+
bytifi decrypt ./downloaded-file --upload-json upload.json
|
|
80
|
+
|
|
81
|
+
# Or pass both values manually
|
|
82
|
+
bytifi decrypt "./my video (1).mp4" \
|
|
83
|
+
--link LINK_ID \
|
|
84
|
+
--token ENCRYPTION_TOKEN
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
#### Link ID vs encryption token
|
|
88
|
+
|
|
89
|
+
Bytifi uses two different values — don't swap them:
|
|
90
|
+
|
|
91
|
+
| Name | Upload JSON field | Example location |
|
|
92
|
+
|------|-------------------|------------------|
|
|
93
|
+
| **Link ID** | `link` | `/f/QeVuslvdaP-okMxG`, `link?link=QeVuslvdaP-okMxG` |
|
|
94
|
+
| **Encryption token** | `encryptionToken` | `#token=2LTlmBrDkO4GJg0...` in `shareUrl` |
|
|
77
95
|
|
|
78
|
-
|
|
96
|
+
- `--link` = link ID (short, ~16 chars)
|
|
97
|
+
- `--token` = encryption key (long, ~43 chars)
|
|
79
98
|
|
|
80
|
-
|
|
99
|
+
### Offline decrypt workflow
|
|
100
|
+
|
|
101
|
+
Save metadata when you upload, so you can decrypt after the link expires:
|
|
81
102
|
|
|
82
103
|
```bash
|
|
83
|
-
# 1. Upload and save the JSON output
|
|
84
104
|
bytifi upload ./report.pdf --json > upload.json
|
|
85
|
-
|
|
86
|
-
# 2. Download the encrypted file manually (browser, curl, etc.)
|
|
87
105
|
curl -L "$(jq -r .encryptedFile upload.json)" -o report.encrypted
|
|
88
|
-
|
|
89
|
-
# 3. Save metadata and decrypt locally (no API call needed)
|
|
90
|
-
jq -c .clientEncryptionMeta upload.json > report.meta.json
|
|
91
|
-
bytifi decrypt ./report.encrypted \
|
|
92
|
-
--meta ./report.meta.json \
|
|
93
|
-
--token "$(jq -r .encryptionToken upload.json)" \
|
|
94
|
-
-o ./report.pdf
|
|
106
|
+
bytifi decrypt ./report.encrypted --upload-json upload.json -o ./report.pdf
|
|
95
107
|
```
|
|
96
108
|
|
|
97
109
|
Without a global install:
|
|
@@ -120,9 +132,10 @@ Note: with `npm exec`, put `--` before the file path so npm does not swallow `--
|
|
|
120
132
|
|
|
121
133
|
| Flag | Description |
|
|
122
134
|
|------|-------------|
|
|
123
|
-
| `--token` | Encryption token (
|
|
124
|
-
| `--link` | Link
|
|
125
|
-
| `--
|
|
135
|
+
| `--token` | Encryption key from `#token=...` (`encryptionToken` in upload JSON) |
|
|
136
|
+
| `--link` | Link ID from upload JSON `link` field (`/f/LINK_ID`) |
|
|
137
|
+
| `--upload-json` | Upload `--json` output file (recommended for downloaded files) |
|
|
138
|
+
| `--meta` | Saved `clientEncryptionMeta` JSON for offline decrypt |
|
|
126
139
|
| `--share-url` | Share URL to read token/metadata while decrypting a local file |
|
|
127
140
|
| `-o, --output` | Output file path |
|
|
128
141
|
| `--output-dir` | Output directory when saving under the original filename |
|
|
@@ -133,9 +146,9 @@ Note: with `npm exec`, put `--` before the file path so npm does not swallow `--
|
|
|
133
146
|
|
|
134
147
|
Exit codes: `0` success, `1` usage error, `2` API error, `3` network error.
|
|
135
148
|
|
|
136
|
-
JSON output (`--json`) for upload includes `shareUrl`, `encryptedFile`, `
|
|
149
|
+
JSON output (`--json`) for upload includes `shareUrl`, `encryptedFile`, `link`, `encryptionToken`, `clientEncryptionMeta`, and `expiresAt`.
|
|
137
150
|
|
|
138
|
-
JSON output for decrypt includes `outputPath`, `originalName`, `size`, `mimeType`, `expiresAt`, `
|
|
151
|
+
JSON output for decrypt includes `outputPath`, `originalName`, `size`, `mimeType`, `expiresAt`, `link`, `storageMode`, and `sourcePath` (for local decrypt).
|
|
139
152
|
|
|
140
153
|
Files over ~100 MB encrypted use multipart upload automatically. Progress prints to stderr unless `--json` or `--quiet` is set.
|
|
141
154
|
|
|
@@ -149,13 +162,13 @@ Files over ~100 MB encrypted use multipart upload automatically. Progress prints
|
|
|
149
162
|
|
|
150
163
|
**Decrypt**
|
|
151
164
|
|
|
152
|
-
1. Reads link metadata from `/api/link/:token`, or from
|
|
153
|
-
2. Downloads the encrypted file (
|
|
165
|
+
1. Reads link metadata from `/api/link/:token`, from `--upload-json`, or from `--meta`
|
|
166
|
+
2. Downloads the encrypted file (unless you pass a local encrypted file)
|
|
154
167
|
3. Decrypts locally and writes the original file to disk
|
|
155
168
|
|
|
156
169
|
## Development
|
|
157
170
|
|
|
158
171
|
```bash
|
|
159
|
-
node bin/bytifi.js upload ./file.png --json
|
|
160
|
-
node bin/bytifi.js decrypt ./file.encrypted --
|
|
172
|
+
node bin/bytifi.js upload ./file.png --json > upload.json
|
|
173
|
+
node bin/bytifi.js decrypt ./file.encrypted --upload-json upload.json
|
|
161
174
|
```
|
package/bin/bytifi.js
CHANGED
|
@@ -29,9 +29,10 @@ Upload options:
|
|
|
29
29
|
--base-url <url> API base URL (default: https://bytifi.com)
|
|
30
30
|
|
|
31
31
|
Decrypt options:
|
|
32
|
-
--token <token> Encryption token (
|
|
33
|
-
--link <
|
|
34
|
-
--
|
|
32
|
+
--token <token> Encryption key from #token=... (not the link ID)
|
|
33
|
+
--link <id> Link ID from upload JSON "link" field (/f/LINK_ID)
|
|
34
|
+
--upload-json <path> Upload --json output (easiest for downloaded files)
|
|
35
|
+
--meta <path> Saved clientEncryptionMeta JSON (offline decrypt)
|
|
35
36
|
--share-url <url> Share URL for token/metadata when decrypting a local file
|
|
36
37
|
-o, --output <path> Output file path (default: original filename)
|
|
37
38
|
--output-dir <dir> Directory for decrypted file (default: cwd)
|
|
@@ -52,12 +53,14 @@ Exit codes:
|
|
|
52
53
|
|
|
53
54
|
Examples:
|
|
54
55
|
bytifi upload ./photo.png
|
|
55
|
-
|
|
56
|
-
bytifi
|
|
57
|
-
|
|
58
|
-
bytifi decrypt
|
|
59
|
-
bytifi decrypt
|
|
60
|
-
|
|
56
|
+
bytifi upload ./video.mp4 --expires 60 --json > upload.json
|
|
57
|
+
bytifi upload ./large.iso -q
|
|
58
|
+
|
|
59
|
+
bytifi decrypt 'https://bytifi.com/link?link=abc#token=...'
|
|
60
|
+
bytifi decrypt abc --token 'ENCRYPTION_TOKEN' -o ./restored.mp4
|
|
61
|
+
|
|
62
|
+
bytifi decrypt ./downloaded.encrypted --upload-json upload.json
|
|
63
|
+
bytifi decrypt "./my file(1).mp4" --link abc --token 'ENCRYPTION_TOKEN'
|
|
61
64
|
`)
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -162,6 +165,7 @@ function parseDecryptArgs(argv) {
|
|
|
162
165
|
encryptionToken: '',
|
|
163
166
|
linkToken: '',
|
|
164
167
|
metaPath: '',
|
|
168
|
+
uploadJsonPath: '',
|
|
165
169
|
shareUrl: '',
|
|
166
170
|
output: '',
|
|
167
171
|
outputDirectory: '',
|
|
@@ -219,6 +223,12 @@ function parseDecryptArgs(argv) {
|
|
|
219
223
|
continue
|
|
220
224
|
}
|
|
221
225
|
|
|
226
|
+
if (arg === '--upload-json') {
|
|
227
|
+
options.uploadJsonPath = readFlagValue(argv, index, arg)
|
|
228
|
+
index += 1
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
|
|
222
232
|
if (arg === '--share-url') {
|
|
223
233
|
options.shareUrl = readFlagValue(argv, index, arg)
|
|
224
234
|
index += 1
|
|
@@ -354,6 +364,7 @@ async function runDecrypt(input, options) {
|
|
|
354
364
|
encryptionToken: options.encryptionToken,
|
|
355
365
|
linkToken: options.linkToken,
|
|
356
366
|
metaPath: options.metaPath,
|
|
367
|
+
uploadJsonPath: options.uploadJsonPath,
|
|
357
368
|
shareUrl: options.shareUrl,
|
|
358
369
|
output: options.output || undefined,
|
|
359
370
|
outputDirectory: options.outputDirectory || undefined,
|
|
@@ -458,7 +469,9 @@ async function main() {
|
|
|
458
469
|
}
|
|
459
470
|
|
|
460
471
|
if (positional.length > 1) {
|
|
461
|
-
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Upload accepts one file at a time (got ${positional.length}). Quote paths with spaces and avoid shell globs like **.`,
|
|
474
|
+
)
|
|
462
475
|
}
|
|
463
476
|
|
|
464
477
|
await runUpload(filePath, options)
|
package/lib/decrypt.js
CHANGED
|
@@ -92,7 +92,10 @@ export function parseDecryptInput(input, { encryptionToken = '', baseUrl = DEFAU
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
if (!resolvedEncryptionToken) {
|
|
95
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
'Missing encryption token. Pass --token with the `#token=...` value from the share URL '
|
|
97
|
+
+ '(stored as `encryptionToken` in upload JSON). This is not the same as the link ID.',
|
|
98
|
+
)
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
return {
|
|
@@ -160,16 +163,24 @@ async function readMetaFile(metaPath) {
|
|
|
160
163
|
|
|
161
164
|
async function resolveEncryptionMeta({
|
|
162
165
|
metaPath = '',
|
|
166
|
+
inlineMeta = null,
|
|
163
167
|
linkToken = '',
|
|
164
168
|
baseUrl = DEFAULT_BASE_URL,
|
|
165
169
|
signal,
|
|
166
170
|
}) {
|
|
171
|
+
if (inlineMeta) {
|
|
172
|
+
return { meta: inlineMeta }
|
|
173
|
+
}
|
|
174
|
+
|
|
167
175
|
if (metaPath) {
|
|
168
|
-
return readMetaFile(metaPath)
|
|
176
|
+
return { meta: await readMetaFile(metaPath) }
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
if (!linkToken) {
|
|
172
|
-
throw new Error(
|
|
180
|
+
throw new Error(
|
|
181
|
+
'Missing link metadata for a downloaded file. Pass --upload-json upload.json, --link LINK_ID, or --meta meta.json.\n'
|
|
182
|
+
+ 'The link ID is the `link` field in upload JSON (also appears as /f/LINK_ID and link?link=LINK_ID).',
|
|
183
|
+
)
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
const linkInfo = await fetchLinkInfo(baseUrl, linkToken, signal)
|
|
@@ -487,11 +498,78 @@ async function decryptFromLocalParts({
|
|
|
487
498
|
onProgress?.(100)
|
|
488
499
|
}
|
|
489
500
|
|
|
490
|
-
function
|
|
501
|
+
function hintTokenConfusion(encryptionToken, linkToken) {
|
|
502
|
+
const token = String(encryptionToken || '').trim()
|
|
503
|
+
|
|
504
|
+
if (!token || linkToken) return
|
|
505
|
+
|
|
506
|
+
if (token.length <= 20) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
`"${token}" looks like a link ID, not an encryption token.\n`
|
|
509
|
+
+ 'Use --link for the link ID (`link` in upload JSON) and --token for the encryption key (`encryptionToken`).\n'
|
|
510
|
+
+ 'Easiest: bytifi decrypt ./file.encrypted --upload-json upload.json',
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export async function loadUploadJson(uploadJsonPath) {
|
|
516
|
+
const raw = await fs.readFile(path.resolve(uploadJsonPath), 'utf8')
|
|
517
|
+
let parsed
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
parsed = JSON.parse(raw)
|
|
521
|
+
} catch {
|
|
522
|
+
throw new Error('Invalid upload JSON file.')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const meta = normalizeClientEncryptionMeta(parsed.clientEncryptionMeta)
|
|
526
|
+
if (!meta) {
|
|
527
|
+
throw new Error('Upload JSON is missing clientEncryptionMeta.')
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const encryptionToken = String(parsed.encryptionToken || '').trim()
|
|
531
|
+
if (!encryptionToken) {
|
|
532
|
+
throw new Error('Upload JSON is missing encryptionToken.')
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
linkToken: String(parsed.link || parsed.token || '').trim(),
|
|
537
|
+
encryptionToken,
|
|
538
|
+
meta,
|
|
539
|
+
originalName: parsed.originalName || 'download',
|
|
540
|
+
shareUrl: parsed.shareUrl || '',
|
|
541
|
+
expiresAt: parsed.expiresAt || null,
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function applyUploadJsonDefaults(options) {
|
|
546
|
+
if (!options.uploadJsonPath) {
|
|
547
|
+
return options
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const upload = await loadUploadJson(options.uploadJsonPath)
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
...options,
|
|
554
|
+
linkToken: options.linkToken || upload.linkToken,
|
|
555
|
+
encryptionToken: options.encryptionToken || upload.encryptionToken,
|
|
556
|
+
inlineMeta: options.inlineMeta || upload.meta,
|
|
557
|
+
originalName: options.originalName || upload.originalName,
|
|
558
|
+
shareUrl: options.shareUrl || upload.shareUrl,
|
|
559
|
+
uploadExpiresAt: upload.expiresAt,
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function resolveEncryptionToken(encryptionToken, shareReference, linkToken = '') {
|
|
491
564
|
const resolved = String(encryptionToken || shareReference?.encryptionToken || '').trim()
|
|
492
565
|
|
|
566
|
+
hintTokenConfusion(resolved, linkToken)
|
|
567
|
+
|
|
493
568
|
if (!resolved) {
|
|
494
|
-
throw new Error(
|
|
569
|
+
throw new Error(
|
|
570
|
+
'Missing encryption token. Pass --token with the `#token=...` value from the share URL '
|
|
571
|
+
+ '(stored as `encryptionToken` in upload JSON), or use --upload-json upload.json.',
|
|
572
|
+
)
|
|
495
573
|
}
|
|
496
574
|
|
|
497
575
|
return resolved
|
|
@@ -513,34 +591,52 @@ function buildDecryptResult({
|
|
|
513
591
|
size,
|
|
514
592
|
mimeType,
|
|
515
593
|
expiresAt,
|
|
516
|
-
linkToken,
|
|
594
|
+
link: linkToken,
|
|
517
595
|
storageMode,
|
|
518
596
|
sourcePath,
|
|
519
597
|
}
|
|
520
598
|
}
|
|
521
599
|
|
|
522
600
|
async function decryptLocalFile(inputPath, options = {}) {
|
|
601
|
+
const resolvedOptions = await applyUploadJsonDefaults(options)
|
|
523
602
|
const absolutePath = path.resolve(inputPath)
|
|
524
603
|
const stat = await fs.stat(absolutePath)
|
|
525
|
-
const shareReference = parseShareReference(
|
|
526
|
-
encryptionToken:
|
|
527
|
-
baseUrl:
|
|
604
|
+
const shareReference = parseShareReference(resolvedOptions.shareUrl || '', {
|
|
605
|
+
encryptionToken: resolvedOptions.encryptionToken,
|
|
606
|
+
baseUrl: resolvedOptions.baseUrl,
|
|
528
607
|
})
|
|
529
|
-
const linkToken =
|
|
608
|
+
const linkToken = resolvedOptions.linkToken || shareReference.linkToken
|
|
609
|
+
|
|
610
|
+
if (
|
|
611
|
+
!resolvedOptions.uploadJsonPath
|
|
612
|
+
&& !resolvedOptions.metaPath
|
|
613
|
+
&& !resolvedOptions.inlineMeta
|
|
614
|
+
&& !linkToken
|
|
615
|
+
) {
|
|
616
|
+
hintTokenConfusion(resolvedOptions.encryptionToken, '')
|
|
617
|
+
}
|
|
618
|
+
|
|
530
619
|
const resolved = await resolveEncryptionMeta({
|
|
531
|
-
metaPath:
|
|
620
|
+
metaPath: resolvedOptions.metaPath,
|
|
621
|
+
inlineMeta: resolvedOptions.inlineMeta,
|
|
532
622
|
linkToken,
|
|
533
|
-
baseUrl:
|
|
534
|
-
signal:
|
|
623
|
+
baseUrl: resolvedOptions.baseUrl || shareReference.baseUrl,
|
|
624
|
+
signal: resolvedOptions.signal,
|
|
535
625
|
})
|
|
536
626
|
const meta = resolved.meta || resolved
|
|
537
627
|
const linkInfo = resolved.linkInfo || null
|
|
538
|
-
const encryptionToken = resolveEncryptionToken(
|
|
628
|
+
const encryptionToken = resolveEncryptionToken(
|
|
629
|
+
resolvedOptions.encryptionToken,
|
|
630
|
+
shareReference,
|
|
631
|
+
linkToken,
|
|
632
|
+
)
|
|
539
633
|
const tokenBytes = importToken(encryptionToken)
|
|
540
634
|
const noncePrefix = fromBase64Url(meta.noncePrefix)
|
|
541
|
-
const originalName = linkInfo?.originalName ||
|
|
542
|
-
const outputName = sanitizeOutputName(
|
|
543
|
-
const outputPath = path.resolve(
|
|
635
|
+
const originalName = linkInfo?.originalName || resolvedOptions.originalName || 'download'
|
|
636
|
+
const outputName = sanitizeOutputName(resolvedOptions.output ? path.basename(resolvedOptions.output) : originalName)
|
|
637
|
+
const outputPath = path.resolve(
|
|
638
|
+
resolvedOptions.output || path.join(resolvedOptions.outputDirectory || process.cwd(), outputName),
|
|
639
|
+
)
|
|
544
640
|
|
|
545
641
|
await fs.mkdir(path.dirname(outputPath), { recursive: true })
|
|
546
642
|
|
|
@@ -551,8 +647,8 @@ async function decryptLocalFile(inputPath, options = {}) {
|
|
|
551
647
|
tokenBytes,
|
|
552
648
|
noncePrefix,
|
|
553
649
|
outputPath,
|
|
554
|
-
onProgress:
|
|
555
|
-
signal:
|
|
650
|
+
onProgress: resolvedOptions.onProgress,
|
|
651
|
+
signal: resolvedOptions.signal,
|
|
556
652
|
})
|
|
557
653
|
|
|
558
654
|
return buildDecryptResult({
|
|
@@ -560,7 +656,7 @@ async function decryptLocalFile(inputPath, options = {}) {
|
|
|
560
656
|
originalName,
|
|
561
657
|
size: meta.originalSize,
|
|
562
658
|
mimeType: meta.mimeType,
|
|
563
|
-
expiresAt: linkInfo?.expiresAt || null,
|
|
659
|
+
expiresAt: linkInfo?.expiresAt || resolvedOptions.uploadExpiresAt || null,
|
|
564
660
|
linkToken,
|
|
565
661
|
storageMode: 'parts',
|
|
566
662
|
sourcePath: absolutePath,
|
|
@@ -573,8 +669,8 @@ async function decryptLocalFile(inputPath, options = {}) {
|
|
|
573
669
|
tokenBytes,
|
|
574
670
|
noncePrefix,
|
|
575
671
|
outputPath,
|
|
576
|
-
onProgress:
|
|
577
|
-
signal:
|
|
672
|
+
onProgress: resolvedOptions.onProgress,
|
|
673
|
+
signal: resolvedOptions.signal,
|
|
578
674
|
})
|
|
579
675
|
|
|
580
676
|
return buildDecryptResult({
|
|
@@ -582,7 +678,7 @@ async function decryptLocalFile(inputPath, options = {}) {
|
|
|
582
678
|
originalName,
|
|
583
679
|
size: meta.originalSize,
|
|
584
680
|
mimeType: meta.mimeType,
|
|
585
|
-
expiresAt: linkInfo?.expiresAt || null,
|
|
681
|
+
expiresAt: linkInfo?.expiresAt || resolvedOptions.uploadExpiresAt || null,
|
|
586
682
|
linkToken,
|
|
587
683
|
storageMode: 'single',
|
|
588
684
|
sourcePath: absolutePath,
|
|
@@ -590,21 +686,22 @@ async function decryptLocalFile(inputPath, options = {}) {
|
|
|
590
686
|
}
|
|
591
687
|
|
|
592
688
|
export async function decryptFile(input, options = {}) {
|
|
689
|
+
const resolvedOptions = await applyUploadJsonDefaults(options)
|
|
593
690
|
const trimmedInput = String(input || '').trim()
|
|
594
691
|
if (!trimmedInput) {
|
|
595
692
|
throw new Error('Missing share URL, link token, or encrypted file path.')
|
|
596
693
|
}
|
|
597
694
|
|
|
598
|
-
if (
|
|
599
|
-
return decryptLocalFile(trimmedInput,
|
|
695
|
+
if (resolvedOptions.localFile || (!looksLikeRemoteInput(trimmedInput) && await pathExists(trimmedInput))) {
|
|
696
|
+
return decryptLocalFile(trimmedInput, resolvedOptions)
|
|
600
697
|
}
|
|
601
698
|
|
|
602
699
|
const parsed = parseDecryptInput(trimmedInput, {
|
|
603
|
-
encryptionToken:
|
|
604
|
-
baseUrl:
|
|
700
|
+
encryptionToken: resolvedOptions.encryptionToken,
|
|
701
|
+
baseUrl: resolvedOptions.baseUrl,
|
|
605
702
|
})
|
|
606
703
|
|
|
607
|
-
const linkInfo = await fetchLinkInfo(parsed.baseUrl, parsed.linkToken,
|
|
704
|
+
const linkInfo = await fetchLinkInfo(parsed.baseUrl, parsed.linkToken, resolvedOptions.signal)
|
|
608
705
|
|
|
609
706
|
if (linkInfo.status === 'expired') {
|
|
610
707
|
throw new Error('This file link has expired.')
|
|
@@ -621,8 +718,12 @@ export async function decryptFile(input, options = {}) {
|
|
|
621
718
|
|
|
622
719
|
const tokenBytes = importToken(parsed.encryptionToken)
|
|
623
720
|
const noncePrefix = fromBase64Url(meta.noncePrefix)
|
|
624
|
-
const outputName = sanitizeOutputName(
|
|
625
|
-
|
|
721
|
+
const outputName = sanitizeOutputName(
|
|
722
|
+
resolvedOptions.output ? path.basename(resolvedOptions.output) : linkInfo.originalName,
|
|
723
|
+
)
|
|
724
|
+
const outputPath = path.resolve(
|
|
725
|
+
resolvedOptions.output || path.join(resolvedOptions.outputDirectory || process.cwd(), outputName),
|
|
726
|
+
)
|
|
626
727
|
|
|
627
728
|
await fs.mkdir(path.dirname(outputPath), { recursive: true })
|
|
628
729
|
|
|
@@ -634,8 +735,8 @@ export async function decryptFile(input, options = {}) {
|
|
|
634
735
|
tokenBytes,
|
|
635
736
|
noncePrefix,
|
|
636
737
|
outputPath,
|
|
637
|
-
onProgress:
|
|
638
|
-
signal:
|
|
738
|
+
onProgress: resolvedOptions.onProgress,
|
|
739
|
+
signal: resolvedOptions.signal,
|
|
639
740
|
})
|
|
640
741
|
} else {
|
|
641
742
|
const encryptedFileUrl = linkInfo.downloadUrl
|
|
@@ -647,8 +748,8 @@ export async function decryptFile(input, options = {}) {
|
|
|
647
748
|
tokenBytes,
|
|
648
749
|
noncePrefix,
|
|
649
750
|
outputPath,
|
|
650
|
-
onProgress:
|
|
651
|
-
signal:
|
|
751
|
+
onProgress: resolvedOptions.onProgress,
|
|
752
|
+
signal: resolvedOptions.signal,
|
|
652
753
|
})
|
|
653
754
|
}
|
|
654
755
|
|
package/lib/upload.js
CHANGED
|
@@ -124,7 +124,7 @@ function buildResult(payload, context, shareUrl) {
|
|
|
124
124
|
shareUrl,
|
|
125
125
|
url: payload.url,
|
|
126
126
|
encryptedFile: payload.downloadUrl,
|
|
127
|
-
|
|
127
|
+
link: payload.token,
|
|
128
128
|
encryptionToken: context.token,
|
|
129
129
|
clientEncryptionMeta: context.meta,
|
|
130
130
|
originalName: payload.originalName || context.originalName,
|