@brftech/filex-core 0.30.1 → 0.32.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 +37 -2
- package/dist/filex-core.js +10571 -8278
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +95 -87
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +548 -66
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +1104 -26
- package/src/components/Breadcrumb.vue +4 -2
- package/src/components/CommandPalette.vue +18 -2
- package/src/components/E2eRecoveryUnlockModal.vue +193 -0
- package/src/components/EncryptedFolderModal.vue +10 -0
- package/src/components/FilterBar.vue +244 -0
- package/src/components/GalleryView.vue +48 -0
- package/src/components/GridView.vue +85 -3
- package/src/components/InspectorPanel.vue +231 -8
- package/src/components/ListView.vue +11 -1
- package/src/components/RecoveryKeyModal.vue +133 -0
- package/src/components/SecondaryPane.vue +15 -1
- package/src/components/SideNav.vue +329 -6
- package/src/components/StarButton.vue +27 -15
- package/src/components/Toolbar.vue +235 -49
- package/src/components/ViewSwitcher.vue +81 -0
- package/src/composables/useFileApi.ts +83 -0
- package/src/composables/useKeyboardShortcuts.ts +7 -0
- package/src/index.ts +33 -1
- package/src/lib/e2ecrypto.ts +716 -70
- package/src/lib/fileFilters.ts +143 -0
- package/src/lib/listing.ts +103 -1
- package/src/lib/star.ts +42 -0
- package/src/lib/tags.ts +105 -0
- package/src/locales/en.ts +154 -2
- package/src/locales/tr.ts +154 -2
- package/src/modals/PermissionsModal.vue +18 -2
- package/src/styles/base.css +743 -0
- package/src/types/ExplorerConfig.ts +53 -1
- package/src/types/FileNode.ts +23 -0
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,13 @@ declare type __VLS_Props_11 = {
|
|
|
54
54
|
/** ui-fix — mirror the main panel's virtual `.trash` row at storage root
|
|
55
55
|
* so both split panes list identical rows (no row-offset). Defaults on. */
|
|
56
56
|
trashVisible?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The navigation panel is already offering Trash, so neither pane draws the
|
|
59
|
+
* virtual row. Passed down rather than worked out here: the panel belongs to
|
|
60
|
+
* the host explorer, and a pane that guessed at it is how the two halves of a
|
|
61
|
+
* split end up listing different rows.
|
|
62
|
+
*/
|
|
63
|
+
navOffersTrash?: boolean;
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
declare type __VLS_Props_12 = {
|
|
@@ -61,9 +68,46 @@ declare type __VLS_Props_12 = {
|
|
|
61
68
|
locale: LocaleCode;
|
|
62
69
|
/** True while the parent is creating the folder + uploading the marker. */
|
|
63
70
|
busy?: boolean;
|
|
71
|
+
/** Short id of this installation's E2E escrow key, when one is configured.
|
|
72
|
+
* Shown BEFORE the folder is created: escrow means the operator can open
|
|
73
|
+
* it without the password, and that is not a detail to discover later. */
|
|
74
|
+
escrowKid?: string | null;
|
|
64
75
|
};
|
|
65
76
|
|
|
66
77
|
declare type __VLS_Props_13 = {
|
|
78
|
+
open: boolean;
|
|
79
|
+
locale: LocaleCode;
|
|
80
|
+
/** The key itself. Shown, copied, downloaded — never emitted or stored. */
|
|
81
|
+
recoveryKey: string;
|
|
82
|
+
/** Folder name, for the downloaded file and the dialog copy. */
|
|
83
|
+
folderName?: string;
|
|
84
|
+
/** Set when this installation holds an escrow key for the folder too. */
|
|
85
|
+
escrowKid?: string | null;
|
|
86
|
+
/** 'created' = a new folder; 'upgraded' = an existing folder gained recovery. */
|
|
87
|
+
variant?: 'created' | 'upgraded';
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
declare type __VLS_Props_14 = {
|
|
91
|
+
open: boolean;
|
|
92
|
+
locale: LocaleCode;
|
|
93
|
+
/** The folder has a user recovery key slot (v2 markers created since 0.31). */
|
|
94
|
+
hasRecovery: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Whether the escrow door applies to THIS folder — see escrowAvailability.
|
|
97
|
+
* 'predates' and 'other-key' are not "no escrow tab"; they are two
|
|
98
|
+
* different facts the dialog has to state, because an admin who knows the
|
|
99
|
+
* installation has escrow reads a missing tab as a bug and tries the key
|
|
100
|
+
* anyway.
|
|
101
|
+
*/
|
|
102
|
+
escrowState: EscrowAvailability;
|
|
103
|
+
/** Short id of the escrow key THIS FOLDER was sealed to, when it has one. */
|
|
104
|
+
escrowKid?: string | null;
|
|
105
|
+
busy?: boolean;
|
|
106
|
+
/** Set by the parent after a failed attempt. */
|
|
107
|
+
error?: string | null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
declare type __VLS_Props_15 = {
|
|
67
111
|
config: ExplorerConfig;
|
|
68
112
|
/** Which half to open on. */
|
|
69
113
|
initialTab?: 'storages' | 'connect';
|
|
@@ -72,7 +116,7 @@ declare type __VLS_Props_13 = {
|
|
|
72
116
|
closable?: boolean;
|
|
73
117
|
};
|
|
74
118
|
|
|
75
|
-
declare type
|
|
119
|
+
declare type __VLS_Props_16 = {
|
|
76
120
|
fields: StorageField[];
|
|
77
121
|
modelValue: Record<string, unknown>;
|
|
78
122
|
locale: LocaleCode;
|
|
@@ -81,18 +125,18 @@ declare type __VLS_Props_14 = {
|
|
|
81
125
|
disabled?: boolean;
|
|
82
126
|
};
|
|
83
127
|
|
|
84
|
-
declare type
|
|
128
|
+
declare type __VLS_Props_17 = {
|
|
85
129
|
guide: ProtocolGuide;
|
|
86
130
|
locale: LocaleCode;
|
|
87
131
|
};
|
|
88
132
|
|
|
89
|
-
declare type
|
|
133
|
+
declare type __VLS_Props_18 = {
|
|
90
134
|
config: ExplorerConfig;
|
|
91
135
|
/** Storage names the caller may see, for the confinement picker. */
|
|
92
136
|
storages: string[];
|
|
93
137
|
};
|
|
94
138
|
|
|
95
|
-
declare type
|
|
139
|
+
declare type __VLS_Props_19 = {
|
|
96
140
|
config: ExplorerConfig;
|
|
97
141
|
/**
|
|
98
142
|
* Draw the key list and the paste box.
|
|
@@ -106,27 +150,6 @@ declare type __VLS_Props_17 = {
|
|
|
106
150
|
keysVisible?: boolean;
|
|
107
151
|
};
|
|
108
152
|
|
|
109
|
-
declare type __VLS_Props_18 = {
|
|
110
|
-
config: ExplorerConfig;
|
|
111
|
-
/** Storage names the caller may see, for the confinement picker. */
|
|
112
|
-
storages: string[];
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
declare type __VLS_Props_19 = {
|
|
116
|
-
config: ExplorerConfig;
|
|
117
|
-
/**
|
|
118
|
-
* Render the full self-service key manager (scopes, folder confinement,
|
|
119
|
-
* expiry) instead of the one-field minter the guides embed.
|
|
120
|
-
*/
|
|
121
|
-
full?: boolean;
|
|
122
|
-
/**
|
|
123
|
-
* Which protocol the surrounding guide is showing. It only changes the
|
|
124
|
-
* default label — a token minted here works on all of them, and pretending
|
|
125
|
-
* otherwise would have people mint one per protocol.
|
|
126
|
-
*/
|
|
127
|
-
protocol?: string;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
153
|
declare type __VLS_Props_2 = {
|
|
131
154
|
open: boolean;
|
|
132
155
|
locale: LocaleCode;
|
|
@@ -161,6 +184,27 @@ declare type __VLS_Props_2 = {
|
|
|
161
184
|
theme?: 'light' | 'dark' | 'auto';
|
|
162
185
|
};
|
|
163
186
|
|
|
187
|
+
declare type __VLS_Props_20 = {
|
|
188
|
+
config: ExplorerConfig;
|
|
189
|
+
/** Storage names the caller may see, for the confinement picker. */
|
|
190
|
+
storages: string[];
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
declare type __VLS_Props_21 = {
|
|
194
|
+
config: ExplorerConfig;
|
|
195
|
+
/**
|
|
196
|
+
* Render the full self-service key manager (scopes, folder confinement,
|
|
197
|
+
* expiry) instead of the one-field minter the guides embed.
|
|
198
|
+
*/
|
|
199
|
+
full?: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Which protocol the surrounding guide is showing. It only changes the
|
|
202
|
+
* default label — a token minted here works on all of them, and pretending
|
|
203
|
+
* otherwise would have people mint one per protocol.
|
|
204
|
+
*/
|
|
205
|
+
protocol?: string;
|
|
206
|
+
};
|
|
207
|
+
|
|
164
208
|
declare type __VLS_Props_3 = {
|
|
165
209
|
starred: boolean;
|
|
166
210
|
nodeId: number;
|
|
@@ -174,6 +218,17 @@ declare type __VLS_Props_3 = {
|
|
|
174
218
|
authCredentials?: RequestCredentials;
|
|
175
219
|
/** Compact mode for grid view (no label, just the icon). */
|
|
176
220
|
compact?: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Card mode — the same button sitting ON a grid/gallery tile instead of in
|
|
223
|
+
* a list cell: a round translucent chip in the tile's corner. It is the SAME
|
|
224
|
+
* component, deliberately: a card star written separately is a second
|
|
225
|
+
* starring path, and the two drift the first time one of them is fixed.
|
|
226
|
+
*/
|
|
227
|
+
card?: boolean;
|
|
228
|
+
/** Locale for the title/label. ⚠ The strings used to be hardcoded English
|
|
229
|
+
* ("Star"/"Unstar"), which is a Turkish user's only untranslated control in
|
|
230
|
+
* the row. */
|
|
231
|
+
locale?: LocaleCode;
|
|
177
232
|
};
|
|
178
233
|
|
|
179
234
|
declare type __VLS_Props_4 = {
|
|
@@ -245,6 +300,40 @@ declare type __VLS_Props_9 = {
|
|
|
245
300
|
narrow?: boolean;
|
|
246
301
|
};
|
|
247
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Give an EXISTING v2 folder an escrow slot, in place, using the folder
|
|
305
|
+
* password its owner has just typed.
|
|
306
|
+
*
|
|
307
|
+
* ── Why this exists ─────────────────────────────────────────────────
|
|
308
|
+
*
|
|
309
|
+
* Escrow used to be all-or-nothing at install time, and then adoptable but
|
|
310
|
+
* never retroactive: on any installation that had been running for a while,
|
|
311
|
+
* escrow covered only folders nobody had created yet. On a real deployment
|
|
312
|
+
* the folders that matter already exist, so "new folders only" means escrow
|
|
313
|
+
* covers nothing anyone cares about.
|
|
314
|
+
*
|
|
315
|
+
* The server still cannot do this, and that has not changed: adding a slot
|
|
316
|
+
* needs the folder master key, which needs a credential the server has never
|
|
317
|
+
* held. What CAN do it is the browser, at the one moment the password is in
|
|
318
|
+
* memory — exactly where `upgradeMarkerV1` already lives. Same moment, same
|
|
319
|
+
* shape, different slot.
|
|
320
|
+
*
|
|
321
|
+
* ⚠⚠ Accepting hands the operator of this installation a second, permanent
|
|
322
|
+
* way into this folder. It is the folder's owner who decides, from inside,
|
|
323
|
+
* with the password; no configuration change and no admin action can do it
|
|
324
|
+
* for them. The caller MUST say that in those words before calling this —
|
|
325
|
+
* see `e2e.escrowoffer.*` in the locales.
|
|
326
|
+
*
|
|
327
|
+
* ⚠ v2 only. A v1 marker has no slots at all; the path for those is
|
|
328
|
+
* `upgradeMarkerV1`, which already seals an escrow slot when the
|
|
329
|
+
* installation has a key and already discloses it. Two doors into the same
|
|
330
|
+
* room would be two chances to get the disclosure wrong.
|
|
331
|
+
*
|
|
332
|
+
* ⚠ No file is re-encrypted, moved or rewritten. Only `.filex-e2e.json`
|
|
333
|
+
* changes, and only by gaining `esc` (and losing `esc_declined`).
|
|
334
|
+
*/
|
|
335
|
+
export declare function addEscrowSlot(marker: E2eMarker, password: string, escrowPublicKey: string): Promise<E2eMarker>;
|
|
336
|
+
|
|
248
337
|
/**
|
|
249
338
|
* The self-service API-token surface (`/api/tokens`).
|
|
250
339
|
*
|
|
@@ -399,11 +488,33 @@ export declare interface Capabilities {
|
|
|
399
488
|
/** Longest life a new share link may be given, in days (0 = no ceiling).
|
|
400
489
|
* Read by the share dialogs so they offer only expiries the server keeps. */
|
|
401
490
|
share_max_ttl_days?: number;
|
|
491
|
+
/** Is the caller a person (`'user'` — a session OR their own API token) or an
|
|
492
|
+
* integration (`'app'` — a host proxy, a bot, an MCP client)? The explorer
|
|
493
|
+
* reads it to decide whether to draw the identity-bearing surfaces; see
|
|
494
|
+
* ExplorerConfig.callerKind. Absent on a server older than the app/user
|
|
495
|
+
* token split, which is why every reader treats "missing" as a person. */
|
|
496
|
+
caller_kind?: 'user' | 'app';
|
|
402
497
|
external?: {
|
|
403
498
|
onlyoffice?: ExternalServiceStatus;
|
|
404
499
|
drawio?: ExternalServiceStatus;
|
|
405
500
|
mermaid?: ExternalServiceStatus;
|
|
406
501
|
};
|
|
502
|
+
/** Whether this installation holds an escrow key for E2E-encrypted folders,
|
|
503
|
+
* and the public half the browser wraps new folders' master keys to.
|
|
504
|
+
*
|
|
505
|
+
* Published on purpose. Escrow means the operator can open the folders you
|
|
506
|
+
* create here without your password, and someone about to create one is
|
|
507
|
+
* entitled to know that BEFORE they create it. Fixed at install time
|
|
508
|
+
* (FILEX_INSTALLATION_E2E_ESCROW_KEY), so this answer never changes for a
|
|
509
|
+
* running installation. */
|
|
510
|
+
e2e_escrow?: {
|
|
511
|
+
enabled: boolean;
|
|
512
|
+
/** Short id of the escrow key (SHA-256(SPKI)[:8], hex). */
|
|
513
|
+
kid?: string;
|
|
514
|
+
alg?: string;
|
|
515
|
+
/** Base64 SPKI. Public material — it can only seal, never open. */
|
|
516
|
+
public_key?: string;
|
|
517
|
+
};
|
|
407
518
|
}
|
|
408
519
|
|
|
409
520
|
export declare function clearResume(store: ResumeStorage | null, key: string): void;
|
|
@@ -421,7 +532,7 @@ export declare interface ClipboardState {
|
|
|
421
532
|
*/
|
|
422
533
|
export declare function comboFromEvent(e: KeyboardEvent): string | null;
|
|
423
534
|
|
|
424
|
-
export declare const ConnectionGuideView: DefineComponent<
|
|
535
|
+
export declare const ConnectionGuideView: DefineComponent<__VLS_Props_17, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_17> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
425
536
|
|
|
426
537
|
export declare type ConnectionsApi = ReturnType<typeof useConnections>;
|
|
427
538
|
|
|
@@ -446,13 +557,13 @@ export declare function connectionsBase(config: ExplorerConfig): string;
|
|
|
446
557
|
*/
|
|
447
558
|
export declare function connectionsOrigin(config: ExplorerConfig): string;
|
|
448
559
|
|
|
449
|
-
export declare const ConnectionsPanel: DefineComponent<
|
|
560
|
+
export declare const ConnectionsPanel: DefineComponent<__VLS_Props_15, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
450
561
|
error: (err: {
|
|
451
562
|
message: string;
|
|
452
563
|
}) => any;
|
|
453
564
|
close: () => any;
|
|
454
565
|
changed: () => any;
|
|
455
|
-
}, string, PublicProps, Readonly<
|
|
566
|
+
}, string, PublicProps, Readonly<__VLS_Props_15> & Readonly<{
|
|
456
567
|
onError?: ((err: {
|
|
457
568
|
message: string;
|
|
458
569
|
}) => any) | undefined;
|
|
@@ -478,31 +589,72 @@ declare interface ContextAction {
|
|
|
478
589
|
divider?: boolean;
|
|
479
590
|
}
|
|
480
591
|
|
|
592
|
+
export declare interface CreatedFolder {
|
|
593
|
+
marker: E2eMarker;
|
|
594
|
+
/** The folder master key, ready for encryptFile/decryptFile. */
|
|
595
|
+
fmk: CryptoKey;
|
|
596
|
+
/** Show this ONCE. filex never stores it and can never show it again. */
|
|
597
|
+
recoveryKey: string;
|
|
598
|
+
}
|
|
599
|
+
|
|
481
600
|
/**
|
|
482
|
-
*
|
|
483
|
-
*
|
|
601
|
+
* Create a v2 encrypted folder: random FMK, wrapped under the password KEK,
|
|
602
|
+
* under a freshly minted user recovery key, and — when the installation has
|
|
603
|
+
* escrow enabled — to the escrow public key.
|
|
604
|
+
*/
|
|
605
|
+
export declare function createEncryptedFolder(password: string, opts?: CreateFolderOptions): Promise<CreatedFolder>;
|
|
606
|
+
|
|
607
|
+
export declare interface CreateFolderOptions {
|
|
608
|
+
iterations?: number;
|
|
609
|
+
/** Base64 SPKI of the installation escrow key, when escrow is enabled. */
|
|
610
|
+
escrowPublicKey?: string | null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Tiny per-explorer key ring: encrypted-folder root (wire path) → FMK.
|
|
615
|
+
* Lives ONLY in memory — "Lock" drops the entry, a reload drops all.
|
|
484
616
|
*/
|
|
485
617
|
export declare function createKeyRing(): {
|
|
486
618
|
get(root: string): CryptoKey | undefined;
|
|
487
|
-
set(root: string,
|
|
488
|
-
/** Drop one folder's key ("
|
|
619
|
+
set(root: string, fmk: CryptoKey): void;
|
|
620
|
+
/** Drop one folder's key ("Lock"). */
|
|
489
621
|
lock(root: string): void;
|
|
490
622
|
has(root: string): boolean;
|
|
491
623
|
clear(): void;
|
|
492
624
|
};
|
|
493
625
|
|
|
494
|
-
/**
|
|
626
|
+
/**
|
|
627
|
+
* Create a v1 folder marker — the pre-0.31 format, with NO recovery of any
|
|
628
|
+
* kind.
|
|
629
|
+
*
|
|
630
|
+
* @deprecated Use `createEncryptedFolder`. Kept exported, and kept producing
|
|
631
|
+
* a genuine v1 marker, so an embedder pinned to the old API keeps creating
|
|
632
|
+
* folders this build can still open rather than half-formed v2 ones.
|
|
633
|
+
*/
|
|
495
634
|
export declare function createMarker(password: string, iterations?: number): Promise<{
|
|
496
635
|
marker: E2eMarker;
|
|
497
636
|
kek: CryptoKey;
|
|
498
637
|
}>;
|
|
499
638
|
|
|
500
639
|
/**
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
640
|
+
* Record that this folder's owner was offered an escrow slot and declined.
|
|
641
|
+
*
|
|
642
|
+
* A refusal is a decision, not a delay: without this the offer would come
|
|
643
|
+
* back on every single unlock, which is how people learn to click past
|
|
644
|
+
* security dialogs without reading them. Nothing about the folder's keys
|
|
645
|
+
* changes — the only effect is that filex stops asking.
|
|
646
|
+
*
|
|
647
|
+
* Reversible by `addEscrowSlot`, which is the way back for somebody who
|
|
648
|
+
* says no today and changes their mind next month.
|
|
649
|
+
*/
|
|
650
|
+
export declare function declineEscrowSlot(marker: E2eMarker, when: string): E2eMarker;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Decrypt a 'filexe2e' blob with the folder master key. Throws
|
|
654
|
+
* E2eDecryptError on a wrong key / tampered data, and a plain Error when the
|
|
655
|
+
* header is not an e2e file at all.
|
|
504
656
|
*/
|
|
505
|
-
export declare function decryptFile(
|
|
657
|
+
export declare function decryptFile(fmk: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer>;
|
|
506
658
|
|
|
507
659
|
export declare const DEFAULT_THEME_ID = "default";
|
|
508
660
|
|
|
@@ -522,6 +674,9 @@ export declare function deriveKek(password: string, salt: Uint8Array, iterations
|
|
|
522
674
|
|
|
523
675
|
export declare const E2E_DEFAULT_ITERATIONS = 600000;
|
|
524
676
|
|
|
677
|
+
/** The only escrow algorithm this version understands. */
|
|
678
|
+
export declare const E2E_ESCROW_ALG = "RSA-OAEP-256";
|
|
679
|
+
|
|
525
680
|
export declare const E2E_MAGIC = "filexe2e";
|
|
526
681
|
|
|
527
682
|
/**
|
|
@@ -529,33 +684,80 @@ export declare const E2E_MAGIC = "filexe2e";
|
|
|
529
684
|
*
|
|
530
685
|
* WebCrypto ONLY — zero dependencies. Design doc: docs/E2E-ENCRYPTION.md.
|
|
531
686
|
*
|
|
532
|
-
* Scheme
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
687
|
+
* ── Scheme ────────────────────────────────────────────────────────────
|
|
688
|
+
*
|
|
689
|
+
* Every encrypted file wraps its own random DEK under ONE key, the folder
|
|
690
|
+
* master key (FMK), and stores the wrapped copy in its own 97-byte header.
|
|
691
|
+
* The FMK is what a "key slot" in the folder marker hands back:
|
|
692
|
+
*
|
|
693
|
+
* password ─PBKDF2-SHA256(600k, 16B salt)─▶ KEK ─┐
|
|
694
|
+
* recovery key ─HKDF-SHA256(16B salt)─▶ RKEK ────┼─▶ unwraps the FMK
|
|
695
|
+
* escrow private key ─RSA-OAEP-256───────────────┘
|
|
696
|
+
* │
|
|
697
|
+
* per-file random 32B DEK ◀── AES-GCM-wrapped by the FMK, in the header
|
|
698
|
+
*
|
|
699
|
+
* Adding a recovery path therefore costs one more wrapped copy of a single
|
|
700
|
+
* 32-byte key in the marker — not a re-encrypt of anything. The file format
|
|
701
|
+
* below is UNCHANGED from v1 and stays that way; only the marker grew.
|
|
702
|
+
*
|
|
703
|
+
* ── Marker versions ───────────────────────────────────────────────────
|
|
536
704
|
*
|
|
537
|
-
*
|
|
705
|
+
* v1 (shipped up to 0.30.1) has no slots: the DEK is wrapped directly by
|
|
706
|
+
* the password KEK. Read that as "the FMK *is* the KEK". Such folders keep
|
|
707
|
+
* opening with nothing but their password, forever — the v1 read path is a
|
|
708
|
+
* first-class path here, not a migration shim.
|
|
709
|
+
*
|
|
710
|
+
* v2 adds the slots. It comes in two flavours, told apart by `fmk`:
|
|
711
|
+
* - `fmk: 'wrapped'` — a fresh random FMK, held in `fmk_pw` wrapped under
|
|
712
|
+
* the password KEK. Every folder created from 0.31 on.
|
|
713
|
+
* - `fmk: 'kek'` — a v1 folder that was given recovery keys in place.
|
|
714
|
+
* Its files were already wrapped under the KEK and are not rewritten, so
|
|
715
|
+
* the FMK stays defined as "the password-derived KEK" and the recovery
|
|
716
|
+
* slots wrap those raw 32 bytes. The password path is byte-identical to
|
|
717
|
+
* v1; only the extra slots are new.
|
|
718
|
+
*
|
|
719
|
+
* ── Invariants ────────────────────────────────────────────────────────
|
|
720
|
+
*
|
|
721
|
+
* - No key, password or recovery key is ever stored, logged or sent to a
|
|
722
|
+
* server. The FMK lives in an in-memory key ring and dies with the tab.
|
|
723
|
+
* - `deriveKek` imports non-extractable. Raw KEK bytes are produced ONLY
|
|
724
|
+
* by `deriveKekBits`, only for a marker whose FMK *is* the KEK
|
|
725
|
+
* (`upgradeMarkerV1`, and `addEscrowSlot` on a folder it produced), and
|
|
726
|
+
* only long enough to wrap them into a slot.
|
|
727
|
+
* - A folder created while escrow was off carries no escrow slot, so the
|
|
728
|
+
* escrow key cannot open it, and nothing the OPERATOR does changes
|
|
729
|
+
* that — not enabling escrow, not adopting it, not any admin action or
|
|
730
|
+
* future version. That is arithmetic, not policy: adding a slot needs
|
|
731
|
+
* the folder master key, and the server has never held a credential
|
|
732
|
+
* that produces one.
|
|
733
|
+
* - The folder's OWNER can, from inside, with the password:
|
|
734
|
+
* `addEscrowSlot`. That is the only door, it opens from one side only,
|
|
735
|
+
* and it is the reason `escrowAvailability` says "not as things stand"
|
|
736
|
+
* rather than "never".
|
|
737
|
+
*
|
|
738
|
+
* File layout ('filexe2e' magic, fixed 97-byte header) — UNCHANGED in v2:
|
|
538
739
|
* [0..8) magic "filexe2e"
|
|
539
740
|
* [8] version 0x01
|
|
540
741
|
* [9..21) wrapIV (12B) — GCM IV of the DEK wrap
|
|
541
|
-
* [21..69) wrappedDEK (48B = 32B DEK + 16B GCM tag)
|
|
742
|
+
* [21..69) wrappedDEK (48B = 32B DEK + 16B GCM tag), wrapped by the FMK
|
|
542
743
|
* [69..81) dataIV (12B) — GCM IV of the content
|
|
543
744
|
* [81..97) reserved (zeros; v2 chunking/metadata)
|
|
544
745
|
* [97..) ciphertext (content + 16B GCM tag)
|
|
545
|
-
*
|
|
546
|
-
* Folder marker `.filex-e2e.json` at the encrypted-folder root:
|
|
547
|
-
* { v:1, salt:<b64 16B>, iter:600000, verify:<b64 12B IV || GCM('filex-e2e-verify-v1')> }
|
|
548
|
-
*
|
|
549
|
-
* The KEK NEVER leaves memory — no storage of any kind. Password loss is
|
|
550
|
-
* data loss by design (no recovery path exists anywhere).
|
|
551
746
|
*/
|
|
552
747
|
export declare const E2E_MARKER_NAME = ".filex-e2e.json";
|
|
553
748
|
|
|
749
|
+
/** Marker schema version written by this build. v1 markers still read. */
|
|
750
|
+
export declare const E2E_MARKER_VERSION = 2;
|
|
751
|
+
|
|
554
752
|
/** MVP single-shot in-memory ceiling — larger uploads are refused with a warning. */
|
|
555
753
|
export declare const E2E_MAX_FILE_BYTES: number;
|
|
556
754
|
|
|
557
755
|
export declare const E2E_MIN_PASSWORD_LEN = 8;
|
|
558
756
|
|
|
757
|
+
/** Entropy of a user recovery key: 20 bytes = 160 bits = exactly 32 base32 chars. */
|
|
758
|
+
export declare const E2E_RECOVERY_KEY_BYTES = 20;
|
|
759
|
+
|
|
760
|
+
/** File-header version byte. Unchanged by the recovery work. */
|
|
559
761
|
export declare const E2E_VERSION = 1;
|
|
560
762
|
|
|
561
763
|
/** Thrown on wrong password / corrupted ciphertext (GCM tag mismatch). */
|
|
@@ -563,6 +765,17 @@ export declare class E2eDecryptError extends Error {
|
|
|
563
765
|
constructor(msg?: string);
|
|
564
766
|
}
|
|
565
767
|
|
|
768
|
+
/** Escrow slot: the FMK encrypted to the installation's escrow public key. */
|
|
769
|
+
export declare interface E2eEscrowSlot {
|
|
770
|
+
/** First 8 bytes of SHA-256(SPKI), hex — names WHICH escrow key this is. */
|
|
771
|
+
kid: string;
|
|
772
|
+
alg: string;
|
|
773
|
+
blob: string;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** How the folder master key is obtained from the password slot. */
|
|
777
|
+
export declare type E2eFmkMode = 'kek' | 'wrapped';
|
|
778
|
+
|
|
566
779
|
export declare type E2eKeyRing = ReturnType<typeof createKeyRing>;
|
|
567
780
|
|
|
568
781
|
export declare interface E2eMarker {
|
|
@@ -570,8 +783,53 @@ export declare interface E2eMarker {
|
|
|
570
783
|
salt: string;
|
|
571
784
|
iter: number;
|
|
572
785
|
verify: string;
|
|
786
|
+
/** v2 only. Absent on a v1 marker, where the FMK is implicitly the KEK. */
|
|
787
|
+
fmk?: E2eFmkMode;
|
|
788
|
+
/** v2 + fmk==='wrapped' only: base64 12B IV || AES-GCM(KEK, FMK). */
|
|
789
|
+
fmk_pw?: string;
|
|
790
|
+
/** v2 only, optional: the user recovery key slot. */
|
|
791
|
+
rk?: E2eRecoverySlot;
|
|
792
|
+
/** v2 only, optional: the operator escrow slot. */
|
|
793
|
+
esc?: E2eEscrowSlot;
|
|
794
|
+
/**
|
|
795
|
+
* v2 only, optional: an ISO timestamp recording that this folder's owner
|
|
796
|
+
* was OFFERED an escrow slot and said no.
|
|
797
|
+
*
|
|
798
|
+
* It lives in the marker rather than in browser storage because the unit
|
|
799
|
+
* of the decision is the FOLDER, not the device: the same person opening
|
|
800
|
+
* the folder from their phone must not be asked again, and a decision
|
|
801
|
+
* that vanished when someone cleared their site data would be no decision
|
|
802
|
+
* at all. It travels with the folder through a move, a backup and a
|
|
803
|
+
* restore, for the same reason the key slots do.
|
|
804
|
+
*
|
|
805
|
+
* It holds no key material and hides nothing from the operator — it is a
|
|
806
|
+
* record of an answer, and its only effect is that filex stops asking.
|
|
807
|
+
* `addEscrowSlot` clears it, so a decline is reversible by the one person
|
|
808
|
+
* who can reverse it.
|
|
809
|
+
*/
|
|
810
|
+
esc_declined?: string;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/** User-recovery-key slot: HKDF salt + the FMK wrapped under the derived key. */
|
|
814
|
+
export declare interface E2eRecoverySlot {
|
|
815
|
+
salt: string;
|
|
816
|
+
blob: string;
|
|
573
817
|
}
|
|
574
818
|
|
|
819
|
+
export declare const E2eRecoveryUnlockModal: DefineComponent<__VLS_Props_14, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
820
|
+
close: () => any;
|
|
821
|
+
submit: (payload: {
|
|
822
|
+
mode: "recovery" | "escrow";
|
|
823
|
+
value: string;
|
|
824
|
+
}) => any;
|
|
825
|
+
}, string, PublicProps, Readonly<__VLS_Props_14> & Readonly<{
|
|
826
|
+
onClose?: (() => any) | undefined;
|
|
827
|
+
onSubmit?: ((payload: {
|
|
828
|
+
mode: "recovery" | "escrow";
|
|
829
|
+
value: string;
|
|
830
|
+
}) => any) | undefined;
|
|
831
|
+
}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
|
|
832
|
+
|
|
575
833
|
/** Current combo for an action ('' = unbound). */
|
|
576
834
|
export declare function effectiveCombo(id: string): string;
|
|
577
835
|
|
|
@@ -593,11 +851,14 @@ password: string;
|
|
|
593
851
|
}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
|
|
594
852
|
|
|
595
853
|
/**
|
|
596
|
-
* Encrypt `content` under the folder
|
|
597
|
-
* content one-shot, wraps the DEK with the
|
|
854
|
+
* Encrypt `content` under the folder master key: mints a fresh DEK, encrypts
|
|
855
|
+
* the content one-shot, wraps the DEK with the FMK and prepends the fixed
|
|
598
856
|
* 'filexe2e' header. Throws when content exceeds E2E_MAX_FILE_BYTES.
|
|
857
|
+
*
|
|
858
|
+
* `fmk` is the key an unlock returned. On a v1 folder that is the password
|
|
859
|
+
* KEK, which is why v1 files keep working untouched.
|
|
599
860
|
*/
|
|
600
|
-
export declare function encryptFile(
|
|
861
|
+
export declare function encryptFile(fmk: CryptoKey, content: ArrayBuffer): Promise<ArrayBuffer>;
|
|
601
862
|
|
|
602
863
|
/**
|
|
603
864
|
* Resolved endpoint map. `useFileApi` derives this once on construction
|
|
@@ -635,6 +896,8 @@ export declare interface EndpointMap {
|
|
|
635
896
|
restore: string | null;
|
|
636
897
|
trashList: string | null;
|
|
637
898
|
trashRestore: string | null;
|
|
899
|
+
e2eEscrowChallenge: string | null;
|
|
900
|
+
e2eEscrowUsed: string | null;
|
|
638
901
|
}
|
|
639
902
|
|
|
640
903
|
export declare function ensureHighlight(): Promise<unknown | null>;
|
|
@@ -646,6 +909,69 @@ export declare function ensureHighlight(): Promise<unknown | null>;
|
|
|
646
909
|
*/
|
|
647
910
|
export declare function ensureMonaco(): Promise<unknown | null>;
|
|
648
911
|
|
|
912
|
+
/**
|
|
913
|
+
* Why the escrow door is, or is not, on offer for this folder.
|
|
914
|
+
*
|
|
915
|
+
* 'off' this installation has no escrow key at all.
|
|
916
|
+
* 'available' the folder is sealed to THIS installation's escrow key.
|
|
917
|
+
* 'predates' the installation has an escrow key, and this folder has no
|
|
918
|
+
* escrow slot: it was created before escrow existed here.
|
|
919
|
+
* 'other-key' the folder carries an escrow slot sealed to a DIFFERENT
|
|
920
|
+
* key id — it came from another installation, via a restore
|
|
921
|
+
* or a copied data directory.
|
|
922
|
+
*
|
|
923
|
+
* ⚠ 'predates' exists because escrow can be ADOPTED by an installation
|
|
924
|
+
* that already has folders (FILEX_INSTALLATION_E2E_ESCROW_ADOPT), and
|
|
925
|
+
* adoption is not retroactive: the folder's master key was wrapped to its
|
|
926
|
+
* recovery paths when the folder was created. Before this distinction
|
|
927
|
+
* existed the dialog simply showed no Escrow tab, which is true but says
|
|
928
|
+
* nothing — an admin who knows escrow is on reads a missing tab as a bug,
|
|
929
|
+
* tries the key anyway, and learns the real answer from a failure. The UI
|
|
930
|
+
* has to say it instead.
|
|
931
|
+
*
|
|
932
|
+
* ⚠⚠ 'predates' means "not as things stand", NOT "never". The folder's
|
|
933
|
+
* owner can add a slot from inside with the password (`addEscrowSlot`,
|
|
934
|
+
* offered at unlock). Any wording built on this state has to leave that
|
|
935
|
+
* door visible, or it tells an operator their escrow key can never reach a
|
|
936
|
+
* folder whose owner could hand it over this afternoon.
|
|
937
|
+
*
|
|
938
|
+
* ⚠ 'other-key' was a quieter lie: the dialog labelled the escrow field
|
|
939
|
+
* with the INSTALLATION's key id whatever the folder's slot said, so a
|
|
940
|
+
* folder restored from another install looked openable by the key the
|
|
941
|
+
* operator has, and was not.
|
|
942
|
+
*/
|
|
943
|
+
export declare type EscrowAvailability = 'off' | 'available' | 'predates' | 'other-key';
|
|
944
|
+
|
|
945
|
+
export declare function escrowAvailability(m: E2eMarker | null, installationKid: string | null | undefined): EscrowAvailability;
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Stable short name for an escrow key: first 8 bytes of SHA-256(SPKI), hex.
|
|
949
|
+
* Written into every escrow slot so a marker says WHICH key opens it, and so
|
|
950
|
+
* the UI can tell "this server's escrow key" from "some other one".
|
|
951
|
+
*/
|
|
952
|
+
export declare function escrowKeyId(spkiB64: string): Promise<string>;
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Whether this folder's owner should be offered an escrow slot, and whether
|
|
956
|
+
* they have already answered.
|
|
957
|
+
*
|
|
958
|
+
* 'n/a' nothing to offer: the installation has no escrow key, the
|
|
959
|
+
* folder already has a slot, or the marker is v1 (whose path
|
|
960
|
+
* is `upgradeMarkerV1`).
|
|
961
|
+
* 'offer' the offer applies and no answer has been recorded.
|
|
962
|
+
* 'declined' the offer applies and the owner said no. Do not ask again;
|
|
963
|
+
* leave a way back.
|
|
964
|
+
*
|
|
965
|
+
* ⚠ This deliberately does NOT look at whether the folder is unlocked. That
|
|
966
|
+
* is the caller's business, and it matters: the offer may only be shown
|
|
967
|
+
* after an unlock actually succeeded, because accepting needs the password
|
|
968
|
+
* and because asking someone who cannot open the folder to give away a key
|
|
969
|
+
* to it is asking the wrong person.
|
|
970
|
+
*/
|
|
971
|
+
export declare type EscrowOfferState = 'n/a' | 'offer' | 'declined';
|
|
972
|
+
|
|
973
|
+
export declare function escrowOfferState(m: E2eMarker | null, installationKid: string | null | undefined): EscrowOfferState;
|
|
974
|
+
|
|
649
975
|
export declare interface ExplorerConfig {
|
|
650
976
|
/**
|
|
651
977
|
* Modern shorthand: URL prefix for the standard /api/files/* layout.
|
|
@@ -707,6 +1033,10 @@ export declare interface ExplorerConfig {
|
|
|
707
1033
|
trashList?: string;
|
|
708
1034
|
/** filex trash restore endpoint — `POST { node_id }`. */
|
|
709
1035
|
trashRestore?: string;
|
|
1036
|
+
/** E2E escrow proof-of-possession — `POST { path } → { id, challenge }`. */
|
|
1037
|
+
e2eEscrowChallenge?: string;
|
|
1038
|
+
/** E2E escrow use report — `POST { path, id, nonce }`. */
|
|
1039
|
+
e2eEscrowUsed?: string;
|
|
710
1040
|
/** Public share base URL — `${shareBase}/${uuid}` */
|
|
711
1041
|
shareBase?: string;
|
|
712
1042
|
/** Auth strategy (see AuthConfig). */
|
|
@@ -800,6 +1130,22 @@ export declare interface ExplorerConfig {
|
|
|
800
1130
|
* navigation panel starts expanded, the tab strip
|
|
801
1131
|
* and split pane are off, the gallery view mode and
|
|
802
1132
|
* the host's "How to connect" surface are hidden.
|
|
1133
|
+
* 'drive' — everything `simple` does, plus the shell an end
|
|
1134
|
+
* user already knows: one primary "New" menu, one
|
|
1135
|
+
* search field in the header (with its ⌘K/Ctrl+K
|
|
1136
|
+
* escalation into the command palette), a filter row
|
|
1137
|
+
* under the breadcrumb, Folders and Files as
|
|
1138
|
+
* labelled sections in grid view, the details panel
|
|
1139
|
+
* split into Details / Activity, and a storage line
|
|
1140
|
+
* under the navigation.
|
|
1141
|
+
*
|
|
1142
|
+
* ⚠ `drive` is a SUPERSET of `simple`, not a sibling: everything `simple`
|
|
1143
|
+
* turns off stays off, and the code asks `simpleUi` for those questions so a
|
|
1144
|
+
* later change to `simple` cannot silently miss `drive`. It is a third value
|
|
1145
|
+
* rather than a second boolean because "which chrome" is ONE question with
|
|
1146
|
+
* three answers — a `driveShell: true` next to `uiProfile: 'standard'` would
|
|
1147
|
+
* be a combination nobody can describe, and keeping that question single is
|
|
1148
|
+
* why `uiProfile` was a preset to begin with.
|
|
803
1149
|
*
|
|
804
1150
|
* Why it exists (GitHub #14): the reporter's users are not in IT and read
|
|
805
1151
|
* split panes, tabs and mount instructions as a file manager they would have
|
|
@@ -811,7 +1157,7 @@ export declare interface ExplorerConfig {
|
|
|
811
1157
|
* rest of the chrome differ. A viewer's own collapse choice, once made,
|
|
812
1158
|
* outranks the profile — it is a per-viewer preference, not a policy.
|
|
813
1159
|
*/
|
|
814
|
-
uiProfile?: 'standard' | 'simple';
|
|
1160
|
+
uiProfile?: 'standard' | 'simple' | 'drive';
|
|
815
1161
|
/**
|
|
816
1162
|
* Render the navigation panel (Upload · Recent / Starred / Shared with me /
|
|
817
1163
|
* Trash · the storage list). Collapsible to an icon rail by the viewer, whose
|
|
@@ -848,11 +1194,37 @@ export declare interface ExplorerConfig {
|
|
|
848
1194
|
* the bug this became: for a year the sole place to mint the token the FTPS
|
|
849
1195
|
* guide told you to use was the admin panel.
|
|
850
1196
|
*
|
|
1197
|
+
* ⚠ Role is not the same question as `callerKind`. "API keys" IS hidden for
|
|
1198
|
+
* an app token — not because of what that caller may do, but because there is
|
|
1199
|
+
* no single person behind it whose keys they would be. See `callerKind`.
|
|
1200
|
+
*
|
|
851
1201
|
* ⚠ The entries live in the panel, so `sideNav: false` takes them with it.
|
|
852
1202
|
* Hosts that want the surface without the panel mount `<filex-connections>`
|
|
853
1203
|
* (or `ConnectionsPanel`) on a page of their own.
|
|
854
1204
|
*/
|
|
855
1205
|
connections?: boolean;
|
|
1206
|
+
/**
|
|
1207
|
+
* Who is behind this explorer — a person, or an integration?
|
|
1208
|
+
*
|
|
1209
|
+
* `'user'` (a signed-in human, or their own API token) draws everything.
|
|
1210
|
+
* `'app'` suppresses the surfaces that only mean something for ONE person:
|
|
1211
|
+
* **API keys**, **Recent**, **Starred** and **Shared with me**. Upload, the
|
|
1212
|
+
* storage list, Trash and "How to connect" stay — an embed's users still
|
|
1213
|
+
* upload files and still need mount instructions.
|
|
1214
|
+
*
|
|
1215
|
+
* Why it exists: a filex API token authenticates AS its owner, and the embeds
|
|
1216
|
+
* we run authenticate every visitor with ONE shared token injected by the
|
|
1217
|
+
* host's proxy. v0.30.0 put "API keys" in the panel, so under that token an
|
|
1218
|
+
* embed visitor could list and revoke the credential the embed itself runs
|
|
1219
|
+
* on — and "your Recent" meant the token owner's history shown to a stranger.
|
|
1220
|
+
*
|
|
1221
|
+
* Default: read from `GET /api/files/capabilities` (`caller_kind`), which is
|
|
1222
|
+
* authoritative because only the server knows the token's kind (migration
|
|
1223
|
+
* 00030). Set this only to answer BEFORE that request lands — a host that
|
|
1224
|
+
* already knows it proxies with an app token spares its users the flash of a
|
|
1225
|
+
* Starred row that then disappears. A value here wins over the server's.
|
|
1226
|
+
*/
|
|
1227
|
+
callerKind?: 'user' | 'app';
|
|
856
1228
|
/** Default view. */
|
|
857
1229
|
viewMode?: 'list' | 'grid';
|
|
858
1230
|
/** Override max upload size (MB) — falls back to /limits otherwise. */
|
|
@@ -1110,6 +1482,8 @@ inspectorOpen?: boolean;
|
|
|
1110
1482
|
navOpen?: boolean;
|
|
1111
1483
|
navEnabled?: boolean;
|
|
1112
1484
|
viewModes?: ViewMode[];
|
|
1485
|
+
shell?: "classic" | "drive";
|
|
1486
|
+
scopeLabel?: string;
|
|
1113
1487
|
}> & Readonly<{
|
|
1114
1488
|
onAction?: ((key: string) => any) | undefined;
|
|
1115
1489
|
onUpload?: (() => any) | undefined;
|
|
@@ -1124,6 +1498,7 @@ onRefresh?: (() => any) | undefined;
|
|
|
1124
1498
|
"onToggle-nav"?: (() => any) | undefined;
|
|
1125
1499
|
"onOpen-theme"?: (() => any) | undefined;
|
|
1126
1500
|
"onOpen-shortcut-settings"?: (() => any) | undefined;
|
|
1501
|
+
"onOpen-palette"?: ((query: string) => any) | undefined;
|
|
1127
1502
|
}>, {
|
|
1128
1503
|
focusSearch: () => Promise<void>;
|
|
1129
1504
|
}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
@@ -1140,6 +1515,7 @@ refresh: () => any;
|
|
|
1140
1515
|
"toggle-nav": () => any;
|
|
1141
1516
|
"open-theme": () => any;
|
|
1142
1517
|
"open-shortcut-settings": () => any;
|
|
1518
|
+
"open-palette": (query: string) => any;
|
|
1143
1519
|
}, PublicProps, {}, false, {}, {}, GlobalComponents, GlobalDirectives, string, {
|
|
1144
1520
|
primaryEl: HTMLDivElement;
|
|
1145
1521
|
wideMoreBtnEl: HTMLButtonElement;
|
|
@@ -1249,6 +1625,8 @@ inspectorOpen?: boolean;
|
|
|
1249
1625
|
navOpen?: boolean;
|
|
1250
1626
|
navEnabled?: boolean;
|
|
1251
1627
|
viewModes?: ViewMode[];
|
|
1628
|
+
shell?: "classic" | "drive";
|
|
1629
|
+
scopeLabel?: string;
|
|
1252
1630
|
}> & Readonly<{
|
|
1253
1631
|
onAction?: ((key: string) => any) | undefined;
|
|
1254
1632
|
onUpload?: (() => any) | undefined;
|
|
@@ -1263,6 +1641,7 @@ onRefresh?: (() => any) | undefined;
|
|
|
1263
1641
|
"onToggle-nav"?: (() => any) | undefined;
|
|
1264
1642
|
"onOpen-theme"?: (() => any) | undefined;
|
|
1265
1643
|
"onOpen-shortcut-settings"?: (() => any) | undefined;
|
|
1644
|
+
"onOpen-palette"?: ((query: string) => any) | undefined;
|
|
1266
1645
|
}>, {
|
|
1267
1646
|
focusSearch: () => Promise<void>;
|
|
1268
1647
|
}, {}, {}, {}, {}> | null;
|
|
@@ -1282,6 +1661,7 @@ viewMode?: ViewMode;
|
|
|
1282
1661
|
thumbSrc?: (n: FileNode) => string | null;
|
|
1283
1662
|
keepBadgeFor?: (n: FileNode) => "kept" | "syncing" | "cloud" | "partial" | null;
|
|
1284
1663
|
trashVisible?: boolean;
|
|
1664
|
+
navOffersTrash?: boolean;
|
|
1285
1665
|
}> & Readonly<{
|
|
1286
1666
|
onTransfer?: ((p: {
|
|
1287
1667
|
sources: string[];
|
|
@@ -1336,6 +1716,7 @@ viewMode?: ViewMode;
|
|
|
1336
1716
|
thumbSrc?: (n: FileNode) => string | null;
|
|
1337
1717
|
keepBadgeFor?: (n: FileNode) => "kept" | "syncing" | "cloud" | "partial" | null;
|
|
1338
1718
|
trashVisible?: boolean;
|
|
1719
|
+
navOffersTrash?: boolean;
|
|
1339
1720
|
}> & Readonly<{
|
|
1340
1721
|
onTransfer?: ((p: {
|
|
1341
1722
|
sources: string[];
|
|
@@ -1454,6 +1835,12 @@ export declare interface FileNode {
|
|
|
1454
1835
|
*/
|
|
1455
1836
|
export declare function findShortcutConflict(combo: string, excludeId: string): ShortcutConflict | null;
|
|
1456
1837
|
|
|
1838
|
+
/**
|
|
1839
|
+
* Format 20 raw bytes as the string the user writes down:
|
|
1840
|
+
* `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX` (160 bits, no padding waste).
|
|
1841
|
+
*/
|
|
1842
|
+
export declare function formatRecoveryKey(raw: Uint8Array): string;
|
|
1843
|
+
|
|
1457
1844
|
/** What an FTP client needs to be told, computed on the server. */
|
|
1458
1845
|
declare interface FTPSFacts {
|
|
1459
1846
|
enabled: boolean;
|
|
@@ -1470,6 +1857,9 @@ declare interface FTPSFacts {
|
|
|
1470
1857
|
self_signed: boolean;
|
|
1471
1858
|
}
|
|
1472
1859
|
|
|
1860
|
+
/** Mint a fresh user recovery key. Shown once, never stored by filex. */
|
|
1861
|
+
export declare function generateRecoveryKey(): string;
|
|
1862
|
+
|
|
1473
1863
|
/**
|
|
1474
1864
|
* Generate a stylesheet that mirrors styles/variables.css' selector
|
|
1475
1865
|
* cascade 1:1 (light base → explicit-dark selectors → prefers-dark media
|
|
@@ -1627,11 +2017,17 @@ export declare interface GuideFact {
|
|
|
1627
2017
|
export declare function guideProtocols(): string[];
|
|
1628
2018
|
|
|
1629
2019
|
/** True when the buffer starts with the 'filexe2e' magic. */
|
|
1630
|
-
export declare function hasMagic(
|
|
2020
|
+
export declare function hasMagic(data: ArrayBuffer | Uint8Array): boolean;
|
|
1631
2021
|
|
|
1632
2022
|
/** `https://fm.example.com` → `fm.example.com`; anything unparseable comes back whole. */
|
|
1633
2023
|
export declare function hostOf(origin: string): string;
|
|
1634
2024
|
|
|
2025
|
+
/** Import the escrow private key the admin pastes in (base64 PKCS#8, PEM tolerated). */
|
|
2026
|
+
export declare function importEscrowPrivateKey(pkcs8B64: string): Promise<CryptoKey>;
|
|
2027
|
+
|
|
2028
|
+
/** Import the installation escrow public key (base64 SPKI, as the server serves it). */
|
|
2029
|
+
export declare function importEscrowPublicKey(spkiB64: string): Promise<CryptoKey>;
|
|
2030
|
+
|
|
1635
2031
|
declare interface InviteResponse {
|
|
1636
2032
|
mode: 'granted' | 'user_created' | 'shared';
|
|
1637
2033
|
user_id?: number;
|
|
@@ -1685,6 +2081,12 @@ export declare interface ManagerResponse {
|
|
|
1685
2081
|
files: FileNode[];
|
|
1686
2082
|
}
|
|
1687
2083
|
|
|
2084
|
+
/** True when the folder has an operator escrow slot. */
|
|
2085
|
+
export declare function markerHasEscrow(m: E2eMarker | null): boolean;
|
|
2086
|
+
|
|
2087
|
+
/** True when the folder has a user recovery key slot. */
|
|
2088
|
+
export declare function markerHasRecovery(m: E2eMarker | null): boolean;
|
|
2089
|
+
|
|
1688
2090
|
/** True when the hit matched (at least partly) inside file CONTENT. */
|
|
1689
2091
|
export declare function matchedInContent(matched: unknown): boolean;
|
|
1690
2092
|
|
|
@@ -1737,7 +2139,7 @@ export declare interface NFSExportCreated extends NFSConnection {
|
|
|
1737
2139
|
path: string;
|
|
1738
2140
|
}
|
|
1739
2141
|
|
|
1740
|
-
export declare const NFSExportsPanel: DefineComponent<
|
|
2142
|
+
export declare const NFSExportsPanel: DefineComponent<__VLS_Props_20, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
1741
2143
|
active: (v: {
|
|
1742
2144
|
host: string;
|
|
1743
2145
|
port: number;
|
|
@@ -1745,7 +2147,7 @@ enabled: boolean;
|
|
|
1745
2147
|
path?: string;
|
|
1746
2148
|
readOnly: boolean;
|
|
1747
2149
|
}) => any;
|
|
1748
|
-
}, string, PublicProps, Readonly<
|
|
2150
|
+
}, string, PublicProps, Readonly<__VLS_Props_20> & Readonly<{
|
|
1749
2151
|
onActive?: ((v: {
|
|
1750
2152
|
host: string;
|
|
1751
2153
|
port: number;
|
|
@@ -1865,9 +2267,16 @@ export declare type OperationsStore = ReturnType<typeof useOperations>;
|
|
|
1865
2267
|
|
|
1866
2268
|
export declare type OperationStatus = 'running' | 'done' | 'error' | 'aborted';
|
|
1867
2269
|
|
|
1868
|
-
/** Parse marker JSON text; returns null when the shape is not a
|
|
2270
|
+
/** Parse marker JSON text; returns null when the shape is not a marker we read. */
|
|
1869
2271
|
export declare function parseMarker(text: string): E2eMarker | null;
|
|
1870
2272
|
|
|
2273
|
+
/**
|
|
2274
|
+
* Parse a typed-in recovery key back to its 20 bytes, or null when it is not
|
|
2275
|
+
* one. Forgiving about how a human retypes it: case, dashes, spaces and the
|
|
2276
|
+
* Crockford look-alikes (O to 0, I/L to 1) are all normalised away.
|
|
2277
|
+
*/
|
|
2278
|
+
export declare function parseRecoveryKey(s: string): Uint8Array | null;
|
|
2279
|
+
|
|
1871
2280
|
export declare interface PendingOp {
|
|
1872
2281
|
id: number;
|
|
1873
2282
|
op_type: 'copy' | 'move' | 'delete';
|
|
@@ -1971,6 +2380,14 @@ onNav?: ((delta: number) => any) | undefined;
|
|
|
1971
2380
|
"onOpen-full"?: (() => any) | undefined;
|
|
1972
2381
|
}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
|
|
1973
2382
|
|
|
2383
|
+
/** surucu:d1 — `GET /api/files/quota/me` (quota.Snapshot). */
|
|
2384
|
+
declare interface QuotaSnapshot {
|
|
2385
|
+
used_bytes: number;
|
|
2386
|
+
quota_bytes: number;
|
|
2387
|
+
percent_used: number;
|
|
2388
|
+
unlimited: boolean;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
1974
2391
|
export declare const RecentlyOpened: DefineComponent<__VLS_Props_5, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
1975
2392
|
error: (message: string) => any;
|
|
1976
2393
|
open: (node: RecentNode) => any;
|
|
@@ -1988,6 +2405,12 @@ declare interface RecentNode {
|
|
|
1988
2405
|
last_opened?: string;
|
|
1989
2406
|
}
|
|
1990
2407
|
|
|
2408
|
+
export declare const RecoveryKeyModal: DefineComponent<__VLS_Props_13, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
2409
|
+
close: () => any;
|
|
2410
|
+
}, string, PublicProps, Readonly<__VLS_Props_13> & Readonly<{
|
|
2411
|
+
onClose?: (() => any) | undefined;
|
|
2412
|
+
}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
|
|
2413
|
+
|
|
1991
2414
|
declare function reload(): Promise<void>;
|
|
1992
2415
|
|
|
1993
2416
|
export declare function resetAllShortcuts(): void;
|
|
@@ -2096,14 +2519,14 @@ export declare interface S3KeyRequest {
|
|
|
2096
2519
|
expires_at?: string;
|
|
2097
2520
|
}
|
|
2098
2521
|
|
|
2099
|
-
export declare const S3KeysPanel: DefineComponent<
|
|
2522
|
+
export declare const S3KeysPanel: DefineComponent<__VLS_Props_18, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
2100
2523
|
active: (v: {
|
|
2101
2524
|
accessKeyID: string;
|
|
2102
2525
|
secret?: string;
|
|
2103
2526
|
endpoint: string;
|
|
2104
2527
|
pathStyle: boolean;
|
|
2105
2528
|
}) => any;
|
|
2106
|
-
}, string, PublicProps, Readonly<
|
|
2529
|
+
}, string, PublicProps, Readonly<__VLS_Props_18> & Readonly<{
|
|
2107
2530
|
onActive?: ((v: {
|
|
2108
2531
|
accessKeyID: string;
|
|
2109
2532
|
secret?: string;
|
|
@@ -2220,6 +2643,7 @@ export declare interface ShortcutHandlers {
|
|
|
2220
2643
|
onShowHelp?: () => void;
|
|
2221
2644
|
onToggleInspector?: () => void;
|
|
2222
2645
|
onToggleHidden?: () => void;
|
|
2646
|
+
onStar?: () => void;
|
|
2223
2647
|
onQuickLook?: () => void;
|
|
2224
2648
|
onTabNew?: () => void;
|
|
2225
2649
|
onTabClose?: () => void;
|
|
@@ -2286,7 +2710,7 @@ export declare interface SSHConnection {
|
|
|
2286
2710
|
ftps?: FTPSFacts;
|
|
2287
2711
|
}
|
|
2288
2712
|
|
|
2289
|
-
export declare const SSHKeysPanel: DefineComponent<
|
|
2713
|
+
export declare const SSHKeysPanel: DefineComponent<__VLS_Props_19, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
2290
2714
|
active: (v: {
|
|
2291
2715
|
host: string;
|
|
2292
2716
|
port: number;
|
|
@@ -2295,7 +2719,7 @@ enabled: boolean;
|
|
|
2295
2719
|
hasKey: boolean;
|
|
2296
2720
|
ftps?: FTPSFacts;
|
|
2297
2721
|
}) => any;
|
|
2298
|
-
}, string, PublicProps, Readonly<
|
|
2722
|
+
}, string, PublicProps, Readonly<__VLS_Props_19> & Readonly<{
|
|
2299
2723
|
onActive?: ((v: {
|
|
2300
2724
|
host: string;
|
|
2301
2725
|
port: number;
|
|
@@ -2391,9 +2815,9 @@ export declare interface StorageFieldOption {
|
|
|
2391
2815
|
i18n_key?: string;
|
|
2392
2816
|
}
|
|
2393
2817
|
|
|
2394
|
-
export declare const StorageFields: DefineComponent<
|
|
2818
|
+
export declare const StorageFields: DefineComponent<__VLS_Props_16, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
2395
2819
|
"update:modelValue": (v: Record<string, unknown>) => any;
|
|
2396
|
-
}, string, PublicProps, Readonly<
|
|
2820
|
+
}, string, PublicProps, Readonly<__VLS_Props_16> & Readonly<{
|
|
2397
2821
|
"onUpdate:modelValue"?: ((v: Record<string, unknown>) => any) | undefined;
|
|
2398
2822
|
}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
2399
2823
|
|
|
@@ -2550,11 +2974,11 @@ export declare const THEMES: ThemeDef[];
|
|
|
2550
2974
|
/** Map of `--fe-*` custom property → value. */
|
|
2551
2975
|
export declare type ThemeTokenMap = Record<string, string>;
|
|
2552
2976
|
|
|
2553
|
-
export declare const TokensPanel: DefineComponent<
|
|
2977
|
+
export declare const TokensPanel: DefineComponent<__VLS_Props_21, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
2554
2978
|
active: (v: {
|
|
2555
2979
|
hasToken: boolean;
|
|
2556
2980
|
}) => any;
|
|
2557
|
-
}, string, PublicProps, Readonly<
|
|
2981
|
+
}, string, PublicProps, Readonly<__VLS_Props_21> & Readonly<{
|
|
2558
2982
|
onActive?: ((v: {
|
|
2559
2983
|
hasToken: boolean;
|
|
2560
2984
|
}) => any) | undefined;
|
|
@@ -2600,6 +3024,44 @@ declare interface TrashEntry {
|
|
|
2600
3024
|
ttl_days?: number | null;
|
|
2601
3025
|
}
|
|
2602
3026
|
|
|
3027
|
+
/**
|
|
3028
|
+
* Unlock with the installation escrow private key.
|
|
3029
|
+
*
|
|
3030
|
+
* Returns null when the folder has no escrow slot — which is the case for
|
|
3031
|
+
* every folder created while escrow was off, and is why escrow cannot be
|
|
3032
|
+
* turned on retroactively.
|
|
3033
|
+
*/
|
|
3034
|
+
export declare function unlockWithEscrowKey(marker: E2eMarker, privateKey: CryptoKey): Promise<CryptoKey | null>;
|
|
3035
|
+
|
|
3036
|
+
/**
|
|
3037
|
+
* Unlock with the folder password. Returns the FMK (the key `decryptFile`
|
|
3038
|
+
* wants) or null when the password is wrong.
|
|
3039
|
+
*/
|
|
3040
|
+
export declare function unlockWithPassword(marker: E2eMarker, password: string): Promise<CryptoKey | null>;
|
|
3041
|
+
|
|
3042
|
+
/**
|
|
3043
|
+
* Unlock with the user recovery key shown when the folder was created.
|
|
3044
|
+
* Returns null for a malformed key, a wrong key, or a folder that has no
|
|
3045
|
+
* recovery slot at all — the caller cannot tell those apart, and neither can
|
|
3046
|
+
* an attacker.
|
|
3047
|
+
*/
|
|
3048
|
+
export declare function unlockWithRecoveryKey(marker: E2eMarker, recoveryKey: string): Promise<CryptoKey | null>;
|
|
3049
|
+
|
|
3050
|
+
/**
|
|
3051
|
+
* Give an existing v1 folder recovery keys, in place and without rewriting a
|
|
3052
|
+
* single file.
|
|
3053
|
+
*
|
|
3054
|
+
* The v1 files are wrapped under the password KEK, so the FMK stays defined
|
|
3055
|
+
* as "the KEK" (`fmk: 'kek'`) and the new slots wrap those raw bytes. The
|
|
3056
|
+
* password path afterwards is byte-identical to what it was.
|
|
3057
|
+
*
|
|
3058
|
+
* ⚠ Requires the password — this is only callable at the one moment filex
|
|
3059
|
+
* ever has it. There is no way to give a v1 folder recovery without it.
|
|
3060
|
+
* ⚠ When the installation has escrow on, this ALSO hands the operator a key
|
|
3061
|
+
* to a folder that did not have one. The caller must say so before asking.
|
|
3062
|
+
*/
|
|
3063
|
+
export declare function upgradeMarkerV1(marker: E2eMarker, password: string, opts?: CreateFolderOptions): Promise<CreatedFolder>;
|
|
3064
|
+
|
|
2603
3065
|
export declare interface UploadFinalizeResponse {
|
|
2604
3066
|
s3Key: string;
|
|
2605
3067
|
url?: string;
|
|
@@ -2721,6 +3183,7 @@ export declare function useFileApi(config: ExplorerConfig): {
|
|
|
2721
3183
|
limit?: number;
|
|
2722
3184
|
scope?: GlobalSearchScope;
|
|
2723
3185
|
}) => Promise<GlobalSearchHit[]>;
|
|
3186
|
+
quotaMe: () => Promise<QuotaSnapshot | null>;
|
|
2724
3187
|
subfolders: (path: string) => Promise<{
|
|
2725
3188
|
folders: FileNode[];
|
|
2726
3189
|
}>;
|
|
@@ -2751,7 +3214,9 @@ export declare function useFileApi(config: ExplorerConfig): {
|
|
|
2751
3214
|
uploadMultipart: (path: string, files: File[], onProgress?: (p: number) => void) => Promise<ManagerResponse>;
|
|
2752
3215
|
downloadUrl: (path: string) => string;
|
|
2753
3216
|
previewUrl: (path: string) => string;
|
|
2754
|
-
fetchBlob: (path: string
|
|
3217
|
+
fetchBlob: (path: string, opts?: {
|
|
3218
|
+
fresh?: boolean;
|
|
3219
|
+
}) => Promise<{
|
|
2755
3220
|
url: string;
|
|
2756
3221
|
blob: Blob;
|
|
2757
3222
|
mime: string;
|
|
@@ -2759,6 +3224,19 @@ export declare function useFileApi(config: ExplorerConfig): {
|
|
|
2759
3224
|
fetchArrayBuffer: (path: string) => Promise<ArrayBuffer>;
|
|
2760
3225
|
limits: () => Promise<UploadLimits>;
|
|
2761
3226
|
capabilities: () => Promise<Capabilities>;
|
|
3227
|
+
e2eEscrowChallenge: (path: string) => Promise<{
|
|
3228
|
+
id: string;
|
|
3229
|
+
challenge: string;
|
|
3230
|
+
kid: string;
|
|
3231
|
+
}>;
|
|
3232
|
+
e2eEscrowUsed: (payload: {
|
|
3233
|
+
path: string;
|
|
3234
|
+
id: string;
|
|
3235
|
+
nonce: string;
|
|
3236
|
+
}) => Promise<{
|
|
3237
|
+
ok: boolean;
|
|
3238
|
+
notified: boolean;
|
|
3239
|
+
}>;
|
|
2762
3240
|
createShare: (payload: {
|
|
2763
3241
|
path: string;
|
|
2764
3242
|
password?: boolean;
|
|
@@ -3271,6 +3749,10 @@ export declare function useUploadChunked(config: ExplorerConfig, api: FileApi, s
|
|
|
3271
3749
|
* Check `password` against a folder marker. Resolves to the derived KEK on
|
|
3272
3750
|
* success, or `null` on a wrong password (GCM tag mismatch on the verify
|
|
3273
3751
|
* blob). Never talks to any server.
|
|
3752
|
+
*
|
|
3753
|
+
* ⚠ This returns the KEK, not the FMK. On a v1 folder they are the same key;
|
|
3754
|
+
* on a v2 `fmk: 'wrapped'` folder they are not. Use `unlockWithPassword` to
|
|
3755
|
+
* get the key that actually decrypts files.
|
|
3274
3756
|
*/
|
|
3275
3757
|
export declare function verifyPassword(marker: E2eMarker, password: string): Promise<CryptoKey | null>;
|
|
3276
3758
|
|