selfstore 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -306,6 +306,12 @@ const persistence = {
306
306
  full-profile copy cannot read it. One unlock per session - `cacheLock` on the
307
307
  simple facade, or `indexedDbCache(name, { lock: true })` at the store layer.
308
308
  Still bounded by the origin (unlocked, code in the page decrypts).
309
+ A forgotten secret is unrecoverable by design, so give your users a way
310
+ out rather than a dead-end prompt: `indexedDbCache(name, { lock: true })
311
+ .clear()` needs no key and abandons the sealed cache, after which they can
312
+ start again from a backup file or from scratch. Throwing from the
313
+ `cacheLock` callback aborts the boot and hands control back to you, which
314
+ is where that offer belongs.
309
315
  - Opt-in hardening for sensitive deployments: `requireEncryption` refuses to
310
316
  write or export a plaintext backup, and `passwordPolicy` refuses a password
311
317
  weaker than a length / character-class rule - both enforced at the store, so
@@ -1,10 +1,10 @@
1
- import { B as BackupTarget } from './store-2RnED1Vq.js';
2
- export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, P as PasswordCheck, e as PasswordPolicy, f as PasswordRequirement, g as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, n as Snapshot, o as SnapshotFile, p as StatusAction, q as StatusDescriptor, r as StatusInput, s as StorageState, t as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-2RnED1Vq.js';
3
- import { K as KV } from './cache-De5_sALA.js';
4
- export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-De5_sALA.js';
5
- export { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-DsXG7QW7.js';
6
- import { c as connect$1, f as fromSession$1, p as preview, a as adopt, b as findOrCreateOwnFile, l as listBackups, d as createBackup, e as deleteBackup, r as renameBackup, g as connect$2, h as fromSession$2, i as connect$3, j as fromSession$3, k as peer } from './drive-DNiDXBDe.js';
7
- export { D as DriveAuth, m as DriveBackupInfo, n as DriveOptions, S as S3Config, o as S3ConnectOptions, W as WebdavConfig, q as WebdavConnectOptions, s as WebdavPeerOptions } from './drive-DNiDXBDe.js';
1
+ import { B as BackupTarget } from './store-C20_6JBo.js';
2
+ export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, P as PasswordCheck, e as PasswordPolicy, f as PasswordRequirement, g as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, n as Snapshot, o as SnapshotFile, p as StatusAction, q as StatusDescriptor, r as StatusInput, s as StorageState, t as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-C20_6JBo.js';
3
+ import { K as KV } from './cache-ilsK3Tki.js';
4
+ export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-ilsK3Tki.js';
5
+ export { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-6OALceUy.js';
6
+ import { c as connect$1, f as fromSession$1, p as preview, a as adopt, b as findOrCreateOwnFile, l as listBackups, d as createBackup, e as deleteBackup, r as renameBackup, g as connect$2, h as fromSession$2, i as connect$3, j as fromSession$3, k as peer } from './drive-BhiTdk-Q.js';
7
+ export { D as DriveAuth, m as DriveBackupInfo, n as DriveOptions, S as S3Config, o as S3ConnectOptions, W as WebdavConfig, q as WebdavConnectOptions, s as WebdavPeerOptions } from './drive-BhiTdk-Q.js';
8
8
  import './sync.js';
9
9
 
10
10
  /**
package/dist/advanced.js CHANGED
@@ -1,4 +1,4 @@
1
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-XI3MGK4M.js';
1
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-FL66ACWR.js';
2
2
  export { pickFromDisk, saveToDisk } from './chunk-UWVAKBU4.js';
3
3
  import './chunk-SKWHKM25.js';
4
4
  import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-TEIAFKFS.js';
package/dist/backups.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { K as KV } from './cache-De5_sALA.js';
2
- import { B as BackupTarget, L as LocalStore } from './store-2RnED1Vq.js';
1
+ import { K as KV } from './cache-ilsK3Tki.js';
2
+ import { B as BackupTarget, L as LocalStore } from './store-C20_6JBo.js';
3
3
  import { F as FlowStore } from './machine-ALMkT3mk.js';
4
4
  import './sync.js';
5
5
 
@@ -107,6 +107,17 @@ interface LockableCache extends LocalCache {
107
107
  * does not open existing data - the cache stays locked. The first secret on
108
108
  * an empty cache sets the lock. */
109
109
  unlock(secret: string | CryptoKey): Promise<boolean>;
110
+ /**
111
+ * Change the secret that seals this cache, in place: re-derive the key (a
112
+ * fresh salt for a password) and rewrite every sealed record under it.
113
+ *
114
+ * Without this, "change my password" has no honest implementation for a
115
+ * locked cache - the app has to wipe and rebuild, which also throws away
116
+ * the durable destination's session, and a half-finished wipe leaves data
117
+ * no key can open. Requires the cache to be unlocked: what cannot be read
118
+ * cannot be re-sealed.
119
+ */
120
+ reseal(secret: string | CryptoKey): Promise<void>;
110
121
  /** Drop the in-memory key: reads and writes lock until unlock() is called. */
111
122
  lockNow(): void;
112
123
  /** Whether no seal key is currently held. */
@@ -1886,6 +1886,40 @@ function indexedDbCache(name, opts) {
1886
1886
  lockNow() {
1887
1887
  sealKey = null;
1888
1888
  },
1889
+ async reseal(secret) {
1890
+ const { keyFromPassword, freshCacheKdf } = await import('./cache-lock-NXIR6JRB.js');
1891
+ const previous = sealKey;
1892
+ if (previous === null) {
1893
+ throw new SelfstoreError(
1894
+ "PASSWORD_REQUIRED",
1895
+ "reseal(): the local cache is locked - unlock() it first."
1896
+ );
1897
+ }
1898
+ const d = await db();
1899
+ const kdf = typeof secret === "string" ? freshCacheKdf() : null;
1900
+ const next = kdf ? await keyFromPassword(secret, kdf) : secret;
1901
+ const rawCollections = await d.get("kv", COLLECTIONS_KEY);
1902
+ const collections = isEnvelope(rawCollections) ? await seal(next, await unseal(previous, rawCollections)) : null;
1903
+ const files = [];
1904
+ for (const fileKey of await d.getAllKeys("files")) {
1905
+ const v = await d.get("files", fileKey);
1906
+ if (!v) continue;
1907
+ const bytes = await unseal(previous, v);
1908
+ files.push({
1909
+ id: fileKey,
1910
+ rec: { name: v.name, mime: v.mime, ...await seal(next, bytes) }
1911
+ });
1912
+ }
1913
+ const tx = d.transaction(["kv", "files"], "readwrite");
1914
+ const store = tx.objectStore("kv");
1915
+ if (kdf) store.put(kdf, CACHE_KDF);
1916
+ else store.delete(CACHE_KDF);
1917
+ if (collections) store.put(collections, COLLECTIONS_KEY);
1918
+ const fileStore = tx.objectStore("files");
1919
+ for (const { id, rec } of files) fileStore.put(rec, id);
1920
+ await tx.done;
1921
+ sealKey = next;
1922
+ },
1889
1923
  async unlock(secret) {
1890
1924
  const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-NXIR6JRB.js');
1891
1925
  const d = await db();
@@ -1994,5 +2028,5 @@ function gisDriveAuth(opts) {
1994
2028
  }
1995
2029
 
1996
2030
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
1997
- //# sourceMappingURL=chunk-XI3MGK4M.js.map
1998
- //# sourceMappingURL=chunk-XI3MGK4M.js.map
2031
+ //# sourceMappingURL=chunk-FL66ACWR.js.map
2032
+ //# sourceMappingURL=chunk-FL66ACWR.js.map