@brftech/filex-core 0.30.0 → 0.31.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/dist/index.d.ts CHANGED
@@ -61,9 +61,40 @@ declare type __VLS_Props_12 = {
61
61
  locale: LocaleCode;
62
62
  /** True while the parent is creating the folder + uploading the marker. */
63
63
  busy?: boolean;
64
+ /** Short id of this installation's E2E escrow key, when one is configured.
65
+ * Shown BEFORE the folder is created: escrow means the operator can open
66
+ * it without the password, and that is not a detail to discover later. */
67
+ escrowKid?: string | null;
64
68
  };
65
69
 
66
70
  declare type __VLS_Props_13 = {
71
+ open: boolean;
72
+ locale: LocaleCode;
73
+ /** The key itself. Shown, copied, downloaded — never emitted or stored. */
74
+ recoveryKey: string;
75
+ /** Folder name, for the downloaded file and the dialog copy. */
76
+ folderName?: string;
77
+ /** Set when this installation holds an escrow key for the folder too. */
78
+ escrowKid?: string | null;
79
+ /** 'created' = a new folder; 'upgraded' = an existing folder gained recovery. */
80
+ variant?: 'created' | 'upgraded';
81
+ };
82
+
83
+ declare type __VLS_Props_14 = {
84
+ open: boolean;
85
+ locale: LocaleCode;
86
+ /** The folder has a user recovery key slot (v2 markers created since 0.31). */
87
+ hasRecovery: boolean;
88
+ /** The folder has an escrow slot AND this installation has escrow enabled. */
89
+ hasEscrow: boolean;
90
+ /** Short id of the escrow key the folder was sealed to. */
91
+ escrowKid?: string | null;
92
+ busy?: boolean;
93
+ /** Set by the parent after a failed attempt. */
94
+ error?: string | null;
95
+ };
96
+
97
+ declare type __VLS_Props_15 = {
67
98
  config: ExplorerConfig;
68
99
  /** Which half to open on. */
69
100
  initialTab?: 'storages' | 'connect';
@@ -72,7 +103,7 @@ declare type __VLS_Props_13 = {
72
103
  closable?: boolean;
73
104
  };
74
105
 
75
- declare type __VLS_Props_14 = {
106
+ declare type __VLS_Props_16 = {
76
107
  fields: StorageField[];
77
108
  modelValue: Record<string, unknown>;
78
109
  locale: LocaleCode;
@@ -81,18 +112,18 @@ declare type __VLS_Props_14 = {
81
112
  disabled?: boolean;
82
113
  };
83
114
 
84
- declare type __VLS_Props_15 = {
115
+ declare type __VLS_Props_17 = {
85
116
  guide: ProtocolGuide;
86
117
  locale: LocaleCode;
87
118
  };
88
119
 
89
- declare type __VLS_Props_16 = {
120
+ declare type __VLS_Props_18 = {
90
121
  config: ExplorerConfig;
91
122
  /** Storage names the caller may see, for the confinement picker. */
92
123
  storages: string[];
93
124
  };
94
125
 
95
- declare type __VLS_Props_17 = {
126
+ declare type __VLS_Props_19 = {
96
127
  config: ExplorerConfig;
97
128
  /**
98
129
  * Draw the key list and the paste box.
@@ -106,27 +137,6 @@ declare type __VLS_Props_17 = {
106
137
  keysVisible?: boolean;
107
138
  };
108
139
 
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
140
  declare type __VLS_Props_2 = {
131
141
  open: boolean;
132
142
  locale: LocaleCode;
@@ -161,6 +171,27 @@ declare type __VLS_Props_2 = {
161
171
  theme?: 'light' | 'dark' | 'auto';
162
172
  };
163
173
 
174
+ declare type __VLS_Props_20 = {
175
+ config: ExplorerConfig;
176
+ /** Storage names the caller may see, for the confinement picker. */
177
+ storages: string[];
178
+ };
179
+
180
+ declare type __VLS_Props_21 = {
181
+ config: ExplorerConfig;
182
+ /**
183
+ * Render the full self-service key manager (scopes, folder confinement,
184
+ * expiry) instead of the one-field minter the guides embed.
185
+ */
186
+ full?: boolean;
187
+ /**
188
+ * Which protocol the surrounding guide is showing. It only changes the
189
+ * default label — a token minted here works on all of them, and pretending
190
+ * otherwise would have people mint one per protocol.
191
+ */
192
+ protocol?: string;
193
+ };
194
+
164
195
  declare type __VLS_Props_3 = {
165
196
  starred: boolean;
166
197
  nodeId: number;
@@ -174,6 +205,17 @@ declare type __VLS_Props_3 = {
174
205
  authCredentials?: RequestCredentials;
175
206
  /** Compact mode for grid view (no label, just the icon). */
176
207
  compact?: boolean;
208
+ /**
209
+ * Card mode — the same button sitting ON a grid/gallery tile instead of in
210
+ * a list cell: a round translucent chip in the tile's corner. It is the SAME
211
+ * component, deliberately: a card star written separately is a second
212
+ * starring path, and the two drift the first time one of them is fixed.
213
+ */
214
+ card?: boolean;
215
+ /** Locale for the title/label. ⚠ The strings used to be hardcoded English
216
+ * ("Star"/"Unstar"), which is a Turkish user's only untranslated control in
217
+ * the row. */
218
+ locale?: LocaleCode;
177
219
  };
178
220
 
179
221
  declare type __VLS_Props_4 = {
@@ -399,11 +441,33 @@ export declare interface Capabilities {
399
441
  /** Longest life a new share link may be given, in days (0 = no ceiling).
400
442
  * Read by the share dialogs so they offer only expiries the server keeps. */
401
443
  share_max_ttl_days?: number;
444
+ /** Is the caller a person (`'user'` — a session OR their own API token) or an
445
+ * integration (`'app'` — a host proxy, a bot, an MCP client)? The explorer
446
+ * reads it to decide whether to draw the identity-bearing surfaces; see
447
+ * ExplorerConfig.callerKind. Absent on a server older than the app/user
448
+ * token split, which is why every reader treats "missing" as a person. */
449
+ caller_kind?: 'user' | 'app';
402
450
  external?: {
403
451
  onlyoffice?: ExternalServiceStatus;
404
452
  drawio?: ExternalServiceStatus;
405
453
  mermaid?: ExternalServiceStatus;
406
454
  };
455
+ /** Whether this installation holds an escrow key for E2E-encrypted folders,
456
+ * and the public half the browser wraps new folders' master keys to.
457
+ *
458
+ * Published on purpose. Escrow means the operator can open the folders you
459
+ * create here without your password, and someone about to create one is
460
+ * entitled to know that BEFORE they create it. Fixed at install time
461
+ * (FILEX_INSTALLATION_E2E_ESCROW_KEY), so this answer never changes for a
462
+ * running installation. */
463
+ e2e_escrow?: {
464
+ enabled: boolean;
465
+ /** Short id of the escrow key (SHA-256(SPKI)[:8], hex). */
466
+ kid?: string;
467
+ alg?: string;
468
+ /** Base64 SPKI. Public material — it can only seal, never open. */
469
+ public_key?: string;
470
+ };
407
471
  }
408
472
 
409
473
  export declare function clearResume(store: ResumeStorage | null, key: string): void;
@@ -421,7 +485,7 @@ export declare interface ClipboardState {
421
485
  */
422
486
  export declare function comboFromEvent(e: KeyboardEvent): string | null;
423
487
 
424
- export declare const ConnectionGuideView: DefineComponent<__VLS_Props_15, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_15> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
488
+ export declare const ConnectionGuideView: DefineComponent<__VLS_Props_17, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_17> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
425
489
 
426
490
  export declare type ConnectionsApi = ReturnType<typeof useConnections>;
427
491
 
@@ -446,13 +510,13 @@ export declare function connectionsBase(config: ExplorerConfig): string;
446
510
  */
447
511
  export declare function connectionsOrigin(config: ExplorerConfig): string;
448
512
 
449
- export declare const ConnectionsPanel: DefineComponent<__VLS_Props_13, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
513
+ export declare const ConnectionsPanel: DefineComponent<__VLS_Props_15, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
450
514
  error: (err: {
451
515
  message: string;
452
516
  }) => any;
453
517
  close: () => any;
454
518
  changed: () => any;
455
- }, string, PublicProps, Readonly<__VLS_Props_13> & Readonly<{
519
+ }, string, PublicProps, Readonly<__VLS_Props_15> & Readonly<{
456
520
  onError?: ((err: {
457
521
  message: string;
458
522
  }) => any) | undefined;
@@ -478,31 +542,59 @@ declare interface ContextAction {
478
542
  divider?: boolean;
479
543
  }
480
544
 
545
+ export declare interface CreatedFolder {
546
+ marker: E2eMarker;
547
+ /** The folder master key, ready for encryptFile/decryptFile. */
548
+ fmk: CryptoKey;
549
+ /** Show this ONCE. filex never stores it and can never show it again. */
550
+ recoveryKey: string;
551
+ }
552
+
481
553
  /**
482
- * Tiny per-explorer key ring: encrypted-folder root (wire path) KEK.
483
- * Lives ONLY in memory "Kilitle" drops the entry, a reload drops all.
554
+ * Create a v2 encrypted folder: random FMK, wrapped under the password KEK,
555
+ * under a freshly minted user recovery key, and when the installation has
556
+ * escrow enabled — to the escrow public key.
557
+ */
558
+ export declare function createEncryptedFolder(password: string, opts?: CreateFolderOptions): Promise<CreatedFolder>;
559
+
560
+ export declare interface CreateFolderOptions {
561
+ iterations?: number;
562
+ /** Base64 SPKI of the installation escrow key, when escrow is enabled. */
563
+ escrowPublicKey?: string | null;
564
+ }
565
+
566
+ /**
567
+ * Tiny per-explorer key ring: encrypted-folder root (wire path) → FMK.
568
+ * Lives ONLY in memory — "Lock" drops the entry, a reload drops all.
484
569
  */
485
570
  export declare function createKeyRing(): {
486
571
  get(root: string): CryptoKey | undefined;
487
- set(root: string, kek: CryptoKey): void;
488
- /** Drop one folder's key ("Kilitle"). */
572
+ set(root: string, fmk: CryptoKey): void;
573
+ /** Drop one folder's key ("Lock"). */
489
574
  lock(root: string): void;
490
575
  has(root: string): boolean;
491
576
  clear(): void;
492
577
  };
493
578
 
494
- /** Create a fresh folder marker for `password` (also returns the derived KEK). */
579
+ /**
580
+ * Create a v1 folder marker — the pre-0.31 format, with NO recovery of any
581
+ * kind.
582
+ *
583
+ * @deprecated Use `createEncryptedFolder`. Kept exported, and kept producing
584
+ * a genuine v1 marker, so an embedder pinned to the old API keeps creating
585
+ * folders this build can still open rather than half-formed v2 ones.
586
+ */
495
587
  export declare function createMarker(password: string, iterations?: number): Promise<{
496
588
  marker: E2eMarker;
497
589
  kek: CryptoKey;
498
590
  }>;
499
591
 
500
592
  /**
501
- * Decrypt a 'filexe2e' blob with the folder KEK. Throws E2eDecryptError on
502
- * a wrong key / tampered data, and a plain Error when the header is not an
503
- * e2e file at all.
593
+ * Decrypt a 'filexe2e' blob with the folder master key. Throws
594
+ * E2eDecryptError on a wrong key / tampered data, and a plain Error when the
595
+ * header is not an e2e file at all.
504
596
  */
505
- export declare function decryptFile(kek: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer>;
597
+ export declare function decryptFile(fmk: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer>;
506
598
 
507
599
  export declare const DEFAULT_THEME_ID = "default";
508
600
 
@@ -522,6 +614,9 @@ export declare function deriveKek(password: string, salt: Uint8Array, iterations
522
614
 
523
615
  export declare const E2E_DEFAULT_ITERATIONS = 600000;
524
616
 
617
+ /** The only escrow algorithm this version understands. */
618
+ export declare const E2E_ESCROW_ALG = "RSA-OAEP-256";
619
+
525
620
  export declare const E2E_MAGIC = "filexe2e";
526
621
 
527
622
  /**
@@ -529,33 +624,71 @@ export declare const E2E_MAGIC = "filexe2e";
529
624
  *
530
625
  * WebCrypto ONLY — zero dependencies. Design doc: docs/E2E-ENCRYPTION.md.
531
626
  *
532
- * Scheme (v1):
533
- * folder password ─PBKDF2-SHA256(600k iter, per-folder 16B salt)─▶ KEK (AES-256-GCM)
534
- * per-file random 32B DEK (AES-256-GCM) encrypts the content one-shot;
535
- * the DEK is wrapped with the KEK and stored in the file's own header.
627
+ * ── Scheme ────────────────────────────────────────────────────────────
628
+ *
629
+ * Every encrypted file wraps its own random DEK under ONE key, the folder
630
+ * master key (FMK), and stores the wrapped copy in its own 97-byte header.
631
+ * The FMK is what a "key slot" in the folder marker hands back:
536
632
  *
537
- * File layout ('filexe2e' magic, fixed 97-byte header):
633
+ * password ─PBKDF2-SHA256(600k, 16B salt)─▶ KEK ─┐
634
+ * recovery key ─HKDF-SHA256(16B salt)─▶ RKEK ────┼─▶ unwraps the FMK
635
+ * escrow private key ─RSA-OAEP-256───────────────┘
636
+ * │
637
+ * per-file random 32B DEK ◀── AES-GCM-wrapped by the FMK, in the header
638
+ *
639
+ * Adding a recovery path therefore costs one more wrapped copy of a single
640
+ * 32-byte key in the marker — not a re-encrypt of anything. The file format
641
+ * below is UNCHANGED from v1 and stays that way; only the marker grew.
642
+ *
643
+ * ── Marker versions ───────────────────────────────────────────────────
644
+ *
645
+ * v1 (shipped up to 0.30.1) has no slots: the DEK is wrapped directly by
646
+ * the password KEK. Read that as "the FMK *is* the KEK". Such folders keep
647
+ * opening with nothing but their password, forever — the v1 read path is a
648
+ * first-class path here, not a migration shim.
649
+ *
650
+ * v2 adds the slots. It comes in two flavours, told apart by `fmk`:
651
+ * - `fmk: 'wrapped'` — a fresh random FMK, held in `fmk_pw` wrapped under
652
+ * the password KEK. Every folder created from 0.31 on.
653
+ * - `fmk: 'kek'` — a v1 folder that was given recovery keys in place.
654
+ * Its files were already wrapped under the KEK and are not rewritten, so
655
+ * the FMK stays defined as "the password-derived KEK" and the recovery
656
+ * slots wrap those raw 32 bytes. The password path is byte-identical to
657
+ * v1; only the extra slots are new.
658
+ *
659
+ * ── Invariants ────────────────────────────────────────────────────────
660
+ *
661
+ * - No key, password or recovery key is ever stored, logged or sent to a
662
+ * server. The FMK lives in an in-memory key ring and dies with the tab.
663
+ * - `deriveKek` imports non-extractable. Raw KEK bytes are produced ONLY
664
+ * by `deriveKekBits`, only while upgrading a v1 marker, and only long
665
+ * enough to wrap them into the new slots.
666
+ * - A folder created while escrow was off carries no escrow slot, so the
667
+ * escrow key cannot open it. That is arithmetic, not policy.
668
+ *
669
+ * File layout ('filexe2e' magic, fixed 97-byte header) — UNCHANGED in v2:
538
670
  * [0..8) magic "filexe2e"
539
671
  * [8] version 0x01
540
672
  * [9..21) wrapIV (12B) — GCM IV of the DEK wrap
541
- * [21..69) wrappedDEK (48B = 32B DEK + 16B GCM tag)
673
+ * [21..69) wrappedDEK (48B = 32B DEK + 16B GCM tag), wrapped by the FMK
542
674
  * [69..81) dataIV (12B) — GCM IV of the content
543
675
  * [81..97) reserved (zeros; v2 chunking/metadata)
544
676
  * [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
677
  */
552
678
  export declare const E2E_MARKER_NAME = ".filex-e2e.json";
553
679
 
680
+ /** Marker schema version written by this build. v1 markers still read. */
681
+ export declare const E2E_MARKER_VERSION = 2;
682
+
554
683
  /** MVP single-shot in-memory ceiling — larger uploads are refused with a warning. */
555
684
  export declare const E2E_MAX_FILE_BYTES: number;
556
685
 
557
686
  export declare const E2E_MIN_PASSWORD_LEN = 8;
558
687
 
688
+ /** Entropy of a user recovery key: 20 bytes = 160 bits = exactly 32 base32 chars. */
689
+ export declare const E2E_RECOVERY_KEY_BYTES = 20;
690
+
691
+ /** File-header version byte. Unchanged by the recovery work. */
559
692
  export declare const E2E_VERSION = 1;
560
693
 
561
694
  /** Thrown on wrong password / corrupted ciphertext (GCM tag mismatch). */
@@ -563,6 +696,17 @@ export declare class E2eDecryptError extends Error {
563
696
  constructor(msg?: string);
564
697
  }
565
698
 
699
+ /** Escrow slot: the FMK encrypted to the installation's escrow public key. */
700
+ export declare interface E2eEscrowSlot {
701
+ /** First 8 bytes of SHA-256(SPKI), hex — names WHICH escrow key this is. */
702
+ kid: string;
703
+ alg: string;
704
+ blob: string;
705
+ }
706
+
707
+ /** How the folder master key is obtained from the password slot. */
708
+ export declare type E2eFmkMode = 'kek' | 'wrapped';
709
+
566
710
  export declare type E2eKeyRing = ReturnType<typeof createKeyRing>;
567
711
 
568
712
  export declare interface E2eMarker {
@@ -570,8 +714,36 @@ export declare interface E2eMarker {
570
714
  salt: string;
571
715
  iter: number;
572
716
  verify: string;
717
+ /** v2 only. Absent on a v1 marker, where the FMK is implicitly the KEK. */
718
+ fmk?: E2eFmkMode;
719
+ /** v2 + fmk==='wrapped' only: base64 12B IV || AES-GCM(KEK, FMK). */
720
+ fmk_pw?: string;
721
+ /** v2 only, optional: the user recovery key slot. */
722
+ rk?: E2eRecoverySlot;
723
+ /** v2 only, optional: the operator escrow slot. */
724
+ esc?: E2eEscrowSlot;
725
+ }
726
+
727
+ /** User-recovery-key slot: HKDF salt + the FMK wrapped under the derived key. */
728
+ export declare interface E2eRecoverySlot {
729
+ salt: string;
730
+ blob: string;
573
731
  }
574
732
 
733
+ export declare const E2eRecoveryUnlockModal: DefineComponent<__VLS_Props_14, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
734
+ close: () => any;
735
+ submit: (payload: {
736
+ mode: "recovery" | "escrow";
737
+ value: string;
738
+ }) => any;
739
+ }, string, PublicProps, Readonly<__VLS_Props_14> & Readonly<{
740
+ onClose?: (() => any) | undefined;
741
+ onSubmit?: ((payload: {
742
+ mode: "recovery" | "escrow";
743
+ value: string;
744
+ }) => any) | undefined;
745
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
746
+
575
747
  /** Current combo for an action ('' = unbound). */
576
748
  export declare function effectiveCombo(id: string): string;
577
749
 
@@ -593,11 +765,14 @@ password: string;
593
765
  }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
594
766
 
595
767
  /**
596
- * Encrypt `content` under the folder KEK: mints a fresh DEK, encrypts the
597
- * content one-shot, wraps the DEK with the KEK and prepends the fixed
768
+ * Encrypt `content` under the folder master key: mints a fresh DEK, encrypts
769
+ * the content one-shot, wraps the DEK with the FMK and prepends the fixed
598
770
  * 'filexe2e' header. Throws when content exceeds E2E_MAX_FILE_BYTES.
771
+ *
772
+ * `fmk` is the key an unlock returned. On a v1 folder that is the password
773
+ * KEK, which is why v1 files keep working untouched.
599
774
  */
600
- export declare function encryptFile(kek: CryptoKey, content: ArrayBuffer): Promise<ArrayBuffer>;
775
+ export declare function encryptFile(fmk: CryptoKey, content: ArrayBuffer): Promise<ArrayBuffer>;
601
776
 
602
777
  /**
603
778
  * Resolved endpoint map. `useFileApi` derives this once on construction
@@ -635,6 +810,8 @@ export declare interface EndpointMap {
635
810
  restore: string | null;
636
811
  trashList: string | null;
637
812
  trashRestore: string | null;
813
+ e2eEscrowChallenge: string | null;
814
+ e2eEscrowUsed: string | null;
638
815
  }
639
816
 
640
817
  export declare function ensureHighlight(): Promise<unknown | null>;
@@ -646,6 +823,13 @@ export declare function ensureHighlight(): Promise<unknown | null>;
646
823
  */
647
824
  export declare function ensureMonaco(): Promise<unknown | null>;
648
825
 
826
+ /**
827
+ * Stable short name for an escrow key: first 8 bytes of SHA-256(SPKI), hex.
828
+ * Written into every escrow slot so a marker says WHICH key opens it, and so
829
+ * the UI can tell "this server's escrow key" from "some other one".
830
+ */
831
+ export declare function escrowKeyId(spkiB64: string): Promise<string>;
832
+
649
833
  export declare interface ExplorerConfig {
650
834
  /**
651
835
  * Modern shorthand: URL prefix for the standard /api/files/* layout.
@@ -707,6 +891,10 @@ export declare interface ExplorerConfig {
707
891
  trashList?: string;
708
892
  /** filex trash restore endpoint — `POST { node_id }`. */
709
893
  trashRestore?: string;
894
+ /** E2E escrow proof-of-possession — `POST { path } → { id, challenge }`. */
895
+ e2eEscrowChallenge?: string;
896
+ /** E2E escrow use report — `POST { path, id, nonce }`. */
897
+ e2eEscrowUsed?: string;
710
898
  /** Public share base URL — `${shareBase}/${uuid}` */
711
899
  shareBase?: string;
712
900
  /** Auth strategy (see AuthConfig). */
@@ -848,11 +1036,37 @@ export declare interface ExplorerConfig {
848
1036
  * the bug this became: for a year the sole place to mint the token the FTPS
849
1037
  * guide told you to use was the admin panel.
850
1038
  *
1039
+ * ⚠ Role is not the same question as `callerKind`. "API keys" IS hidden for
1040
+ * an app token — not because of what that caller may do, but because there is
1041
+ * no single person behind it whose keys they would be. See `callerKind`.
1042
+ *
851
1043
  * ⚠ The entries live in the panel, so `sideNav: false` takes them with it.
852
1044
  * Hosts that want the surface without the panel mount `<filex-connections>`
853
1045
  * (or `ConnectionsPanel`) on a page of their own.
854
1046
  */
855
1047
  connections?: boolean;
1048
+ /**
1049
+ * Who is behind this explorer — a person, or an integration?
1050
+ *
1051
+ * `'user'` (a signed-in human, or their own API token) draws everything.
1052
+ * `'app'` suppresses the surfaces that only mean something for ONE person:
1053
+ * **API keys**, **Recent**, **Starred** and **Shared with me**. Upload, the
1054
+ * storage list, Trash and "How to connect" stay — an embed's users still
1055
+ * upload files and still need mount instructions.
1056
+ *
1057
+ * Why it exists: a filex API token authenticates AS its owner, and the embeds
1058
+ * we run authenticate every visitor with ONE shared token injected by the
1059
+ * host's proxy. v0.30.0 put "API keys" in the panel, so under that token an
1060
+ * embed visitor could list and revoke the credential the embed itself runs
1061
+ * on — and "your Recent" meant the token owner's history shown to a stranger.
1062
+ *
1063
+ * Default: read from `GET /api/files/capabilities` (`caller_kind`), which is
1064
+ * authoritative because only the server knows the token's kind (migration
1065
+ * 00030). Set this only to answer BEFORE that request lands — a host that
1066
+ * already knows it proxies with an app token spares its users the flash of a
1067
+ * Starred row that then disappears. A value here wins over the server's.
1068
+ */
1069
+ callerKind?: 'user' | 'app';
856
1070
  /** Default view. */
857
1071
  viewMode?: 'list' | 'grid';
858
1072
  /** Override max upload size (MB) — falls back to /limits otherwise. */
@@ -1454,6 +1668,12 @@ export declare interface FileNode {
1454
1668
  */
1455
1669
  export declare function findShortcutConflict(combo: string, excludeId: string): ShortcutConflict | null;
1456
1670
 
1671
+ /**
1672
+ * Format 20 raw bytes as the string the user writes down:
1673
+ * `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX` (160 bits, no padding waste).
1674
+ */
1675
+ export declare function formatRecoveryKey(raw: Uint8Array): string;
1676
+
1457
1677
  /** What an FTP client needs to be told, computed on the server. */
1458
1678
  declare interface FTPSFacts {
1459
1679
  enabled: boolean;
@@ -1470,6 +1690,9 @@ declare interface FTPSFacts {
1470
1690
  self_signed: boolean;
1471
1691
  }
1472
1692
 
1693
+ /** Mint a fresh user recovery key. Shown once, never stored by filex. */
1694
+ export declare function generateRecoveryKey(): string;
1695
+
1473
1696
  /**
1474
1697
  * Generate a stylesheet that mirrors styles/variables.css' selector
1475
1698
  * cascade 1:1 (light base → explicit-dark selectors → prefers-dark media
@@ -1627,11 +1850,17 @@ export declare interface GuideFact {
1627
1850
  export declare function guideProtocols(): string[];
1628
1851
 
1629
1852
  /** True when the buffer starts with the 'filexe2e' magic. */
1630
- export declare function hasMagic(buf: ArrayBuffer | Uint8Array): boolean;
1853
+ export declare function hasMagic(data: ArrayBuffer | Uint8Array): boolean;
1631
1854
 
1632
1855
  /** `https://fm.example.com` → `fm.example.com`; anything unparseable comes back whole. */
1633
1856
  export declare function hostOf(origin: string): string;
1634
1857
 
1858
+ /** Import the escrow private key the admin pastes in (base64 PKCS#8, PEM tolerated). */
1859
+ export declare function importEscrowPrivateKey(pkcs8B64: string): Promise<CryptoKey>;
1860
+
1861
+ /** Import the installation escrow public key (base64 SPKI, as the server serves it). */
1862
+ export declare function importEscrowPublicKey(spkiB64: string): Promise<CryptoKey>;
1863
+
1635
1864
  declare interface InviteResponse {
1636
1865
  mode: 'granted' | 'user_created' | 'shared';
1637
1866
  user_id?: number;
@@ -1685,6 +1914,12 @@ export declare interface ManagerResponse {
1685
1914
  files: FileNode[];
1686
1915
  }
1687
1916
 
1917
+ /** True when the folder has an operator escrow slot. */
1918
+ export declare function markerHasEscrow(m: E2eMarker | null): boolean;
1919
+
1920
+ /** True when the folder has a user recovery key slot. */
1921
+ export declare function markerHasRecovery(m: E2eMarker | null): boolean;
1922
+
1688
1923
  /** True when the hit matched (at least partly) inside file CONTENT. */
1689
1924
  export declare function matchedInContent(matched: unknown): boolean;
1690
1925
 
@@ -1737,7 +1972,7 @@ export declare interface NFSExportCreated extends NFSConnection {
1737
1972
  path: string;
1738
1973
  }
1739
1974
 
1740
- export declare const NFSExportsPanel: DefineComponent<__VLS_Props_18, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1975
+ export declare const NFSExportsPanel: DefineComponent<__VLS_Props_20, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1741
1976
  active: (v: {
1742
1977
  host: string;
1743
1978
  port: number;
@@ -1745,7 +1980,7 @@ enabled: boolean;
1745
1980
  path?: string;
1746
1981
  readOnly: boolean;
1747
1982
  }) => any;
1748
- }, string, PublicProps, Readonly<__VLS_Props_18> & Readonly<{
1983
+ }, string, PublicProps, Readonly<__VLS_Props_20> & Readonly<{
1749
1984
  onActive?: ((v: {
1750
1985
  host: string;
1751
1986
  port: number;
@@ -1865,9 +2100,16 @@ export declare type OperationsStore = ReturnType<typeof useOperations>;
1865
2100
 
1866
2101
  export declare type OperationStatus = 'running' | 'done' | 'error' | 'aborted';
1867
2102
 
1868
- /** Parse marker JSON text; returns null when the shape is not a v1 marker. */
2103
+ /** Parse marker JSON text; returns null when the shape is not a marker we read. */
1869
2104
  export declare function parseMarker(text: string): E2eMarker | null;
1870
2105
 
2106
+ /**
2107
+ * Parse a typed-in recovery key back to its 20 bytes, or null when it is not
2108
+ * one. Forgiving about how a human retypes it: case, dashes, spaces and the
2109
+ * Crockford look-alikes (O to 0, I/L to 1) are all normalised away.
2110
+ */
2111
+ export declare function parseRecoveryKey(s: string): Uint8Array | null;
2112
+
1871
2113
  export declare interface PendingOp {
1872
2114
  id: number;
1873
2115
  op_type: 'copy' | 'move' | 'delete';
@@ -1988,6 +2230,12 @@ declare interface RecentNode {
1988
2230
  last_opened?: string;
1989
2231
  }
1990
2232
 
2233
+ export declare const RecoveryKeyModal: DefineComponent<__VLS_Props_13, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2234
+ close: () => any;
2235
+ }, string, PublicProps, Readonly<__VLS_Props_13> & Readonly<{
2236
+ onClose?: (() => any) | undefined;
2237
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
2238
+
1991
2239
  declare function reload(): Promise<void>;
1992
2240
 
1993
2241
  export declare function resetAllShortcuts(): void;
@@ -2096,14 +2344,14 @@ export declare interface S3KeyRequest {
2096
2344
  expires_at?: string;
2097
2345
  }
2098
2346
 
2099
- export declare const S3KeysPanel: DefineComponent<__VLS_Props_16, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2347
+ export declare const S3KeysPanel: DefineComponent<__VLS_Props_18, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2100
2348
  active: (v: {
2101
2349
  accessKeyID: string;
2102
2350
  secret?: string;
2103
2351
  endpoint: string;
2104
2352
  pathStyle: boolean;
2105
2353
  }) => any;
2106
- }, string, PublicProps, Readonly<__VLS_Props_16> & Readonly<{
2354
+ }, string, PublicProps, Readonly<__VLS_Props_18> & Readonly<{
2107
2355
  onActive?: ((v: {
2108
2356
  accessKeyID: string;
2109
2357
  secret?: string;
@@ -2220,6 +2468,7 @@ export declare interface ShortcutHandlers {
2220
2468
  onShowHelp?: () => void;
2221
2469
  onToggleInspector?: () => void;
2222
2470
  onToggleHidden?: () => void;
2471
+ onStar?: () => void;
2223
2472
  onQuickLook?: () => void;
2224
2473
  onTabNew?: () => void;
2225
2474
  onTabClose?: () => void;
@@ -2286,7 +2535,7 @@ export declare interface SSHConnection {
2286
2535
  ftps?: FTPSFacts;
2287
2536
  }
2288
2537
 
2289
- export declare const SSHKeysPanel: DefineComponent<__VLS_Props_17, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2538
+ export declare const SSHKeysPanel: DefineComponent<__VLS_Props_19, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2290
2539
  active: (v: {
2291
2540
  host: string;
2292
2541
  port: number;
@@ -2295,7 +2544,7 @@ enabled: boolean;
2295
2544
  hasKey: boolean;
2296
2545
  ftps?: FTPSFacts;
2297
2546
  }) => any;
2298
- }, string, PublicProps, Readonly<__VLS_Props_17> & Readonly<{
2547
+ }, string, PublicProps, Readonly<__VLS_Props_19> & Readonly<{
2299
2548
  onActive?: ((v: {
2300
2549
  host: string;
2301
2550
  port: number;
@@ -2391,9 +2640,9 @@ export declare interface StorageFieldOption {
2391
2640
  i18n_key?: string;
2392
2641
  }
2393
2642
 
2394
- export declare const StorageFields: DefineComponent<__VLS_Props_14, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2643
+ export declare const StorageFields: DefineComponent<__VLS_Props_16, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2395
2644
  "update:modelValue": (v: Record<string, unknown>) => any;
2396
- }, string, PublicProps, Readonly<__VLS_Props_14> & Readonly<{
2645
+ }, string, PublicProps, Readonly<__VLS_Props_16> & Readonly<{
2397
2646
  "onUpdate:modelValue"?: ((v: Record<string, unknown>) => any) | undefined;
2398
2647
  }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
2399
2648
 
@@ -2550,11 +2799,11 @@ export declare const THEMES: ThemeDef[];
2550
2799
  /** Map of `--fe-*` custom property → value. */
2551
2800
  export declare type ThemeTokenMap = Record<string, string>;
2552
2801
 
2553
- export declare const TokensPanel: DefineComponent<__VLS_Props_19, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2802
+ export declare const TokensPanel: DefineComponent<__VLS_Props_21, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2554
2803
  active: (v: {
2555
2804
  hasToken: boolean;
2556
2805
  }) => any;
2557
- }, string, PublicProps, Readonly<__VLS_Props_19> & Readonly<{
2806
+ }, string, PublicProps, Readonly<__VLS_Props_21> & Readonly<{
2558
2807
  onActive?: ((v: {
2559
2808
  hasToken: boolean;
2560
2809
  }) => any) | undefined;
@@ -2600,6 +2849,44 @@ declare interface TrashEntry {
2600
2849
  ttl_days?: number | null;
2601
2850
  }
2602
2851
 
2852
+ /**
2853
+ * Unlock with the installation escrow private key.
2854
+ *
2855
+ * Returns null when the folder has no escrow slot — which is the case for
2856
+ * every folder created while escrow was off, and is why escrow cannot be
2857
+ * turned on retroactively.
2858
+ */
2859
+ export declare function unlockWithEscrowKey(marker: E2eMarker, privateKey: CryptoKey): Promise<CryptoKey | null>;
2860
+
2861
+ /**
2862
+ * Unlock with the folder password. Returns the FMK (the key `decryptFile`
2863
+ * wants) or null when the password is wrong.
2864
+ */
2865
+ export declare function unlockWithPassword(marker: E2eMarker, password: string): Promise<CryptoKey | null>;
2866
+
2867
+ /**
2868
+ * Unlock with the user recovery key shown when the folder was created.
2869
+ * Returns null for a malformed key, a wrong key, or a folder that has no
2870
+ * recovery slot at all — the caller cannot tell those apart, and neither can
2871
+ * an attacker.
2872
+ */
2873
+ export declare function unlockWithRecoveryKey(marker: E2eMarker, recoveryKey: string): Promise<CryptoKey | null>;
2874
+
2875
+ /**
2876
+ * Give an existing v1 folder recovery keys, in place and without rewriting a
2877
+ * single file.
2878
+ *
2879
+ * The v1 files are wrapped under the password KEK, so the FMK stays defined
2880
+ * as "the KEK" (`fmk: 'kek'`) and the new slots wrap those raw bytes. The
2881
+ * password path afterwards is byte-identical to what it was.
2882
+ *
2883
+ * ⚠ Requires the password — this is only callable at the one moment filex
2884
+ * ever has it. There is no way to give a v1 folder recovery without it.
2885
+ * ⚠ When the installation has escrow on, this ALSO hands the operator a key
2886
+ * to a folder that did not have one. The caller must say so before asking.
2887
+ */
2888
+ export declare function upgradeMarkerV1(marker: E2eMarker, password: string, opts?: CreateFolderOptions): Promise<CreatedFolder>;
2889
+
2603
2890
  export declare interface UploadFinalizeResponse {
2604
2891
  s3Key: string;
2605
2892
  url?: string;
@@ -2759,6 +3046,19 @@ export declare function useFileApi(config: ExplorerConfig): {
2759
3046
  fetchArrayBuffer: (path: string) => Promise<ArrayBuffer>;
2760
3047
  limits: () => Promise<UploadLimits>;
2761
3048
  capabilities: () => Promise<Capabilities>;
3049
+ e2eEscrowChallenge: (path: string) => Promise<{
3050
+ id: string;
3051
+ challenge: string;
3052
+ kid: string;
3053
+ }>;
3054
+ e2eEscrowUsed: (payload: {
3055
+ path: string;
3056
+ id: string;
3057
+ nonce: string;
3058
+ }) => Promise<{
3059
+ ok: boolean;
3060
+ notified: boolean;
3061
+ }>;
2762
3062
  createShare: (payload: {
2763
3063
  path: string;
2764
3064
  password?: boolean;
@@ -3271,6 +3571,10 @@ export declare function useUploadChunked(config: ExplorerConfig, api: FileApi, s
3271
3571
  * Check `password` against a folder marker. Resolves to the derived KEK on
3272
3572
  * success, or `null` on a wrong password (GCM tag mismatch on the verify
3273
3573
  * blob). Never talks to any server.
3574
+ *
3575
+ * ⚠ This returns the KEK, not the FMK. On a v1 folder they are the same key;
3576
+ * on a v2 `fmk: 'wrapped'` folder they are not. Use `unlockWithPassword` to
3577
+ * get the key that actually decrypts files.
3274
3578
  */
3275
3579
  export declare function verifyPassword(marker: E2eMarker, password: string): Promise<CryptoKey | null>;
3276
3580