selfstore 1.8.35 → 1.8.36
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 +6 -2
- package/dist/advanced.d.ts +9 -9
- package/dist/advanced.js +1 -1
- package/dist/backups.d.ts +4 -4
- package/dist/{cache-C1edLncd.d.ts → cache-DBIn_Uo8.d.ts} +6 -0
- package/dist/{chunk-SHX36MIE.js → chunk-JE3BLKXO.js} +60 -17
- package/dist/chunk-JE3BLKXO.js.map +1 -0
- package/dist/{chunk-VRWAV3GI.js → chunk-UU4PS3UH.js} +19 -9
- package/dist/chunk-UU4PS3UH.js.map +1 -0
- package/dist/{connect-D7Yzdi9N.d.ts → connect-CdlhiTBy.d.ts} +4 -4
- package/dist/{drive-3K6pBAmG.d.ts → drive-BCRil91Q.d.ts} +2 -2
- package/dist/flows.d.ts +4 -4
- package/dist/flows.js +1 -1
- package/dist/groups.d.ts +2 -2
- package/dist/households.d.ts +7 -0
- package/dist/households.js +13 -3
- package/dist/households.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +2 -2
- package/dist/{local-Cr5cCaQ2.d.ts → local-CpoBeJWk.d.ts} +2 -2
- package/dist/{manager-Bl16FR-e.d.ts → manager-0puQEpZc.d.ts} +2 -2
- package/dist/passkey.d.ts +6 -4
- package/dist/passkey.js +5 -4
- package/dist/passkey.js.map +1 -1
- package/dist/proof.d.ts +6 -6
- package/dist/{s3-C2PrgMpD.d.ts → s3-kikEHI-g.d.ts} +2 -2
- package/dist/{simple-Dp1tbrwo.d.ts → simple-BmUQpsBM.d.ts} +6 -6
- package/dist/{store-B4Zo0f6G.d.ts → store-bp1VRW7R.d.ts} +1 -1
- package/dist/widgets.d.ts +5 -5
- package/dist/widgets.js +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/dist/chunk-SHX36MIE.js.map +0 -1
- package/dist/chunk-VRWAV3GI.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export { C as CacheUnlock, a as ConnectOutcome, F as FileBytes, P as PutFileInput, b as SimpleOptions, S as SimpleRecord, c as SimpleStore, s as selfstore } from './simple-
|
|
2
|
-
export { h as changePassword, g as gisDriveAuth, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-
|
|
3
|
-
export { a as DriveAuth } from './drive-
|
|
4
|
-
export { S as S3Config, W as WebdavConfig } from './s3-
|
|
5
|
-
import { a as Snapshot, H as Header } from './store-
|
|
1
|
+
export { C as CacheUnlock, a as ConnectOutcome, F as FileBytes, P as PutFileInput, b as SimpleOptions, S as SimpleRecord, c as SimpleStore, s as selfstore } from './simple-BmUQpsBM.js';
|
|
2
|
+
export { h as changePassword, g as gisDriveAuth, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-CpoBeJWk.js';
|
|
3
|
+
export { a as DriveAuth } from './drive-BCRil91Q.js';
|
|
4
|
+
export { S as S3Config, W as WebdavConfig } from './s3-kikEHI-g.js';
|
|
5
|
+
import { a as Snapshot, H as Header } from './store-bp1VRW7R.js';
|
|
6
6
|
import { EncodeOptions } from './advanced.js';
|
|
7
|
-
export { L as LocalStore, e as LocalStoreState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, t as StorageState, S as StoreError, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-
|
|
7
|
+
export { L as LocalStore, e as LocalStoreState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, t as StorageState, S as StoreError, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-bp1VRW7R.js';
|
|
8
8
|
export { SyncConfig } from './sync.js';
|
|
9
|
-
import './cache-
|
|
10
|
-
import './connect-
|
|
9
|
+
import './cache-DBIn_Uo8.js';
|
|
10
|
+
import './connect-CdlhiTBy.js';
|
|
11
11
|
import './machine-Dhbx3Eut.js';
|
|
12
|
-
import './manager-
|
|
12
|
+
import './manager-0puQEpZc.js';
|
|
13
13
|
|
|
14
14
|
declare const BACKUP_EXTENSION = ".zip";
|
|
15
15
|
declare const BACKUP_MIME = "application/zip";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-
|
|
2
|
-
export { gisDriveAuth, preloadGoogleIdentity } from './chunk-
|
|
1
|
+
import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-JE3BLKXO.js';
|
|
2
|
+
export { gisDriveAuth, preloadGoogleIdentity } from './chunk-JE3BLKXO.js';
|
|
3
3
|
import { idOf } from './chunk-3P2W3XQD.js';
|
|
4
4
|
import { saveToDisk } from './chunk-XAONTUEC.js';
|
|
5
5
|
export { pickFromDisk, saveToDisk } from './chunk-XAONTUEC.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as Snapshot, E as EncodeOptions, H as Header } from './store-
|
|
2
|
-
import { a as DriveAuth } from './drive-
|
|
1
|
+
import { a as Snapshot, E as EncodeOptions, H as Header } from './store-bp1VRW7R.js';
|
|
2
|
+
import { a as DriveAuth } from './drive-BCRil91Q.js';
|
|
3
3
|
|
|
4
4
|
/** Serialize a snapshot to a portable backup blob (compressed; encrypted if a password is given). */
|
|
5
5
|
declare function exportSnapshot(snapshot: Snapshot, opts: EncodeOptions): Promise<Blob>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { K as KV } from './cache-
|
|
2
|
-
import { B as BackupTarget, L as LocalStore } from './store-
|
|
1
|
+
import { K as KV } from './cache-DBIn_Uo8.js';
|
|
2
|
+
import { B as BackupTarget, L as LocalStore } from './store-bp1VRW7R.js';
|
|
3
3
|
import { F as FlowStore } from './machine-Dhbx3Eut.js';
|
|
4
4
|
|
|
5
5
|
/** What the destination reports about a listed file (one cheap metadata call). */
|
package/dist/passkey.d.ts
CHANGED
|
@@ -63,8 +63,9 @@ interface PasskeyUnlock {
|
|
|
63
63
|
enrolled(): boolean;
|
|
64
64
|
/**
|
|
65
65
|
* Create a platform passkey and seal `secret` behind it. `false` when the
|
|
66
|
-
* user cancelled,
|
|
67
|
-
*
|
|
66
|
+
* user cancelled, when PRF did not yield a key here, or when this device
|
|
67
|
+
* refused to store the sealed secret - fail closed, the host keeps its
|
|
68
|
+
* password prompt and should say so rather than pretend.
|
|
68
69
|
*/
|
|
69
70
|
enroll(secret: string): Promise<boolean>;
|
|
70
71
|
/**
|
|
@@ -77,9 +78,10 @@ interface PasskeyUnlock {
|
|
|
77
78
|
* Drop the sealed secret from this device. The platform credential itself
|
|
78
79
|
* is NOT deleted - the operating system owns it, and only the user can
|
|
79
80
|
* remove it from their passkey manager. Say that in the interface rather
|
|
80
|
-
* than implying a full erasure.
|
|
81
|
+
* than implying a full erasure. `false` when the storage refused the
|
|
82
|
+
* removal: the enrolment is still there, and the host must not say it is off.
|
|
81
83
|
*/
|
|
82
|
-
forget():
|
|
84
|
+
forget(): boolean;
|
|
83
85
|
}
|
|
84
86
|
declare function passkeyUnlock(options: PasskeyUnlockOptions): PasskeyUnlock;
|
|
85
87
|
|
package/dist/passkey.js
CHANGED
|
@@ -45,10 +45,12 @@ function passkeyUnlock(options) {
|
|
|
45
45
|
}
|
|
46
46
|
function write(entry) {
|
|
47
47
|
try {
|
|
48
|
-
if (typeof localStorage === "undefined") return;
|
|
48
|
+
if (typeof localStorage === "undefined") return entry === null;
|
|
49
49
|
if (entry) localStorage.setItem(storageKey, JSON.stringify(entry));
|
|
50
50
|
else localStorage.removeItem(storageKey);
|
|
51
|
+
return true;
|
|
51
52
|
} catch {
|
|
53
|
+
return false;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
async function derive(credentialId) {
|
|
@@ -118,8 +120,7 @@ function passkeyUnlock(options) {
|
|
|
118
120
|
await sealKey(created.secret),
|
|
119
121
|
new TextEncoder().encode(secret)
|
|
120
122
|
);
|
|
121
|
-
write({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });
|
|
122
|
-
return true;
|
|
123
|
+
return write({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });
|
|
123
124
|
} catch {
|
|
124
125
|
return false;
|
|
125
126
|
}
|
|
@@ -142,7 +143,7 @@ function passkeyUnlock(options) {
|
|
|
142
143
|
}
|
|
143
144
|
},
|
|
144
145
|
forget() {
|
|
145
|
-
write(null);
|
|
146
|
+
return write(null);
|
|
146
147
|
}
|
|
147
148
|
};
|
|
148
149
|
}
|
package/dist/passkey.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/passkey/passkey.ts"],"names":[],"mappings":"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","file":"passkey.js","sourcesContent":["/**\n * Open a store with the device instead of the keyboard: Face, fingerprint or\n * Windows Hello in place of typing the password.\n *\n * WHAT IT IS. A platform passkey carrying the WebAuthn PRF extension (FIDO2\n * hmac-secret) yields a stable 32-byte secret - deterministic for that\n * credential and a fixed per-app salt, and it never leaves the authenticator.\n * That secret becomes an AES-256-GCM key, under which this module seals the\n * app's own secret (typically the backup password). Unlocking is then: user\n * verification -> PRF secret -> the password in the clear, in memory.\n *\n * WHAT IT IS NOT. It does not replace the password, it carries it. The\n * password keeps opening everything, on every device, forever; this is a\n * convenience bound to ONE browser profile on ONE machine. Losing the passkey\n * costs a typed password, never data. That asymmetry is deliberate: a second\n * independent key to the data would widen the blast radius of a stolen device,\n * which is the opposite of what a locked cache is for.\n *\n * THE PROPERTY IT PRESERVES. The sealed blob is useless on its own: a copy of\n * the browser profile does not open it, because the PRF secret lives in the\n * authenticator and demands user verification. That is exactly the guarantee a\n * lock-mode cache exists for, so enrolling does not weaken it.\n *\n * OPT-IN BY CONSTRUCTION. Nothing here runs unless an app calls\n * `passkeyUnlock()`. An app that never does simply has no such feature, and an\n * app that does still decides when to offer it - the recommended shape is an\n * explicit control the user turns on, and can turn off again with `forget()`.\n *\n * Requires the PRF extension: recent Chrome/Edge, Safari 18+, recent Android;\n * inside a native WebView it depends on the system WebView version. Nothing\n * trusts that blindly - `available()` and `enroll()` feature-detect at runtime\n * and FAIL CLOSED, so the option is only ever usable where a real secret comes\n * back. Dependency-free, and guarded for SSR and no-WebAuthn environments.\n */\n\n/** How the passkey is presented by the platform, and what it protects. */\nexport interface PasskeyUnlockOptions {\n\t/**\n\t * Shown by the operating system's passkey prompt and stored with the\n\t * credential. The application name, as a person would read it.\n\t */\n\tappName: string;\n\t/**\n\t * Domain separator for the PRF evaluation - NOT a secret, and it may be a\n\t * constant: the credential's own secret is what makes the derived key\n\t * unique. Use a stable per-app string. Changing it rotates every\n\t * passkey-derived key at once, which orphans every existing enrolment.\n\t */\n\tsalt: string;\n\t/** Where the sealed secret is kept on this device (default `selfstore.passkey`). */\n\tstorageKey?: string;\n}\n\n/** The device-unlock facade. Every method is safe to call anywhere: outside a\n * browser, or without WebAuthn, they report unavailable rather than throw. */\nexport interface PasskeyUnlock {\n\t/**\n\t * Whether a user-verifying platform authenticator exists at all. Necessary\n\t * but NOT sufficient: only an actual `enroll()` proves the PRF extension\n\t * works here. Use it to decide whether to show the control, not to promise\n\t * that it will succeed.\n\t */\n\tavailable(): Promise<boolean>;\n\t/** Whether this device currently holds a sealed secret to reveal. */\n\tenrolled(): boolean;\n\t/**\n\t * Create a platform passkey and seal `secret` behind it. `false` when the\n\t * user cancelled, or when PRF did not yield a key here - fail closed, the\n\t * host keeps its password prompt and should say so rather than pretend.\n\t */\n\tenroll(secret: string): Promise<boolean>;\n\t/**\n\t * Ask for Face / fingerprint / Hello and return the sealed secret. `null`\n\t * on cancel, on a missing enrolment, or on any failure; the host then falls\n\t * back to asking for the password. Never throws.\n\t */\n\treveal(): Promise<string | null>;\n\t/**\n\t * Drop the sealed secret from this device. The platform credential itself\n\t * is NOT deleted - the operating system owns it, and only the user can\n\t * remove it from their passkey manager. Say that in the interface rather\n\t * than implying a full erasure.\n\t */\n\tforget(): void;\n}\n\n/** PRF extension shapes are absent from some TS DOM libs; describe what we use. */\ntype PrfInputs = { prf?: { eval?: { first: BufferSource } } };\ntype PrfOutputs = { prf?: { results?: { first?: ArrayBuffer } } };\n\n/** What one device remembers: which credential to prefer, and the sealed secret. */\ninterface StoredEnrolment {\n\tcredentialId: string;\n\tiv: string;\n\tsealed: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'selfstore.passkey';\nconst PRF_BYTES = 32;\n\nfunction toB64(bytes: ArrayBuffer | Uint8Array): string {\n\tconst b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet s = '';\n\tfor (const byte of b) s += String.fromCharCode(byte);\n\treturn btoa(s);\n}\n\nfunction fromB64(s: string): Uint8Array<ArrayBuffer> {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n\nfunction webauthnUsable(): boolean {\n\treturn (\n\t\ttypeof window !== 'undefined' &&\n\t\t!!window.PublicKeyCredential &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\t!!navigator.credentials &&\n\t\ttypeof crypto !== 'undefined' &&\n\t\t!!crypto.subtle\n\t);\n}\n\nfunction prfSecret(credential: PublicKeyCredential | null): Uint8Array | null {\n\tconst first = (credential?.getClientExtensionResults() as PrfOutputs | undefined)?.prf?.results\n\t\t?.first;\n\tif (!first) return null;\n\tconst bytes = new Uint8Array(first);\n\treturn bytes.length === PRF_BYTES ? bytes : null;\n}\n\n/**\n * The 32 bytes coming out of PRF are an HMAC output, so they are already\n * uniform: import them straight as an AES-GCM key rather than running a KDF\n * over them. Stretching high-entropy material buys nothing and would only make\n * every unlock slower.\n */\nfunction sealKey(secret: Uint8Array): Promise<CryptoKey> {\n\treturn crypto.subtle.importKey('raw', secret as BufferSource, 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\nexport function passkeyUnlock(options: PasskeyUnlockOptions): PasskeyUnlock {\n\tconst storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n\tconst prfSalt = new TextEncoder().encode(options.salt);\n\n\tfunction read(): StoredEnrolment | null {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return null;\n\t\t\tconst raw = localStorage.getItem(storageKey);\n\t\t\tif (!raw) return null;\n\t\t\tconst parsed = JSON.parse(raw) as StoredEnrolment;\n\t\t\tconst complete =\n\t\t\t\ttypeof parsed?.credentialId === 'string' &&\n\t\t\t\ttypeof parsed?.iv === 'string' &&\n\t\t\t\ttypeof parsed?.sealed === 'string';\n\t\t\treturn complete ? parsed : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tfunction write(entry: StoredEnrolment | null): void {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return;\n\t\t\tif (entry) localStorage.setItem(storageKey, JSON.stringify(entry));\n\t\t\telse localStorage.removeItem(storageKey);\n\t\t} catch {\n\t\t\t/* storage blocked: the enrolment is simply not remembered */\n\t\t}\n\t}\n\n\t/**\n\t * Re-derive the PRF secret. With a credential id it targets that one; with\n\t * none, the platform offers the user's discoverable passkeys for this site.\n\t */\n\tasync function derive(credentialId?: string): Promise<Uint8Array | null> {\n\t\ttry {\n\t\t\tconst assertion = (await navigator.credentials.get({\n\t\t\t\tpublicKey: {\n\t\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\t\tallowCredentials: credentialId\n\t\t\t\t\t\t? [{ type: 'public-key', id: fromB64(credentialId) as BufferSource }]\n\t\t\t\t\t\t: [],\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\t\ttimeout: 60_000\n\t\t\t\t}\n\t\t\t})) as PublicKeyCredential | null;\n\t\t\treturn prfSecret(assertion);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function create(): Promise<{ credentialId: string; secret: Uint8Array } | null> {\n\t\tconst credential = (await navigator.credentials.create({\n\t\t\tpublicKey: {\n\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\trp: { name: options.appName, id: location.hostname },\n\t\t\t\tuser: {\n\t\t\t\t\tid: crypto.getRandomValues(new Uint8Array(16)),\n\t\t\t\t\tname: options.appName,\n\t\t\t\t\tdisplayName: options.appName\n\t\t\t\t},\n\t\t\t\tpubKeyCredParams: [\n\t\t\t\t\t{ type: 'public-key', alg: -7 },\n\t\t\t\t\t{ type: 'public-key', alg: -257 }\n\t\t\t\t],\n\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\tauthenticatorAttachment: 'platform',\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\tresidentKey: 'required'\n\t\t\t\t},\n\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\ttimeout: 60_000\n\t\t\t}\n\t\t})) as PublicKeyCredential | null;\n\t\tif (!credential) return null;\n\t\tconst credentialId = toB64(credential.rawId);\n\t\t// Some platforms hand the PRF result back from create(); most need a\n\t\t// second get(). Try the cheap path first to save the user one prompt.\n\t\tconst secret = prfSecret(credential) ?? (await derive(credentialId));\n\t\treturn secret ? { credentialId, secret } : null;\n\t}\n\n\treturn {\n\t\tasync available(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tif (!webauthnUsable()) return false;\n\t\t\t\treturn await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tenrolled(): boolean {\n\t\t\treturn read() !== null;\n\t\t},\n\n\t\tasync enroll(secret: string): Promise<boolean> {\n\t\t\tif (!webauthnUsable()) return false;\n\t\t\ttry {\n\t\t\t\tconst created = await create();\n\t\t\t\tif (!created) return false;\n\t\t\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\t\t\tconst sealed = await crypto.subtle.encrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv },\n\t\t\t\t\tawait sealKey(created.secret),\n\t\t\t\t\tnew TextEncoder().encode(secret)\n\t\t\t\t);\n\t\t\t\twrite({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync reveal(): Promise<string | null> {\n\t\t\tconst entry = read();\n\t\t\tif (!entry || !webauthnUsable()) return null;\n\t\t\tconst secret = await derive(entry.credentialId);\n\t\t\tif (!secret) return null;\n\t\t\ttry {\n\t\t\t\tconst opened = await crypto.subtle.decrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv: fromB64(entry.iv) as BufferSource },\n\t\t\t\t\tawait sealKey(secret),\n\t\t\t\t\tfromB64(entry.sealed) as BufferSource\n\t\t\t\t);\n\t\t\t\treturn new TextDecoder().decode(opened);\n\t\t\t} catch {\n\t\t\t\t// A wrong key or a tampered blob: an unopenable enrolment is dead\n\t\t\t\t// weight that would fail at every attempt, so drop it and let the\n\t\t\t\t// host fall back to the password.\n\t\t\t\twrite(null);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tforget(): void {\n\t\t\twrite(null);\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/passkey/passkey.ts"],"names":[],"mappings":"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","file":"passkey.js","sourcesContent":["/**\n * Open a store with the device instead of the keyboard: Face, fingerprint or\n * Windows Hello in place of typing the password.\n *\n * WHAT IT IS. A platform passkey carrying the WebAuthn PRF extension (FIDO2\n * hmac-secret) yields a stable 32-byte secret - deterministic for that\n * credential and a fixed per-app salt, and it never leaves the authenticator.\n * That secret becomes an AES-256-GCM key, under which this module seals the\n * app's own secret (typically the backup password). Unlocking is then: user\n * verification -> PRF secret -> the password in the clear, in memory.\n *\n * WHAT IT IS NOT. It does not replace the password, it carries it. The\n * password keeps opening everything, on every device, forever; this is a\n * convenience bound to ONE browser profile on ONE machine. Losing the passkey\n * costs a typed password, never data. That asymmetry is deliberate: a second\n * independent key to the data would widen the blast radius of a stolen device,\n * which is the opposite of what a locked cache is for.\n *\n * THE PROPERTY IT PRESERVES. The sealed blob is useless on its own: a copy of\n * the browser profile does not open it, because the PRF secret lives in the\n * authenticator and demands user verification. That is exactly the guarantee a\n * lock-mode cache exists for, so enrolling does not weaken it.\n *\n * OPT-IN BY CONSTRUCTION. Nothing here runs unless an app calls\n * `passkeyUnlock()`. An app that never does simply has no such feature, and an\n * app that does still decides when to offer it - the recommended shape is an\n * explicit control the user turns on, and can turn off again with `forget()`.\n *\n * Requires the PRF extension: recent Chrome/Edge, Safari 18+, recent Android;\n * inside a native WebView it depends on the system WebView version. Nothing\n * trusts that blindly - `available()` and `enroll()` feature-detect at runtime\n * and FAIL CLOSED, so the option is only ever usable where a real secret comes\n * back. Dependency-free, and guarded for SSR and no-WebAuthn environments.\n */\n\n/** How the passkey is presented by the platform, and what it protects. */\nexport interface PasskeyUnlockOptions {\n\t/**\n\t * Shown by the operating system's passkey prompt and stored with the\n\t * credential. The application name, as a person would read it.\n\t */\n\tappName: string;\n\t/**\n\t * Domain separator for the PRF evaluation - NOT a secret, and it may be a\n\t * constant: the credential's own secret is what makes the derived key\n\t * unique. Use a stable per-app string. Changing it rotates every\n\t * passkey-derived key at once, which orphans every existing enrolment.\n\t */\n\tsalt: string;\n\t/** Where the sealed secret is kept on this device (default `selfstore.passkey`). */\n\tstorageKey?: string;\n}\n\n/** The device-unlock facade. Every method is safe to call anywhere: outside a\n * browser, or without WebAuthn, they report unavailable rather than throw. */\nexport interface PasskeyUnlock {\n\t/**\n\t * Whether a user-verifying platform authenticator exists at all. Necessary\n\t * but NOT sufficient: only an actual `enroll()` proves the PRF extension\n\t * works here. Use it to decide whether to show the control, not to promise\n\t * that it will succeed.\n\t */\n\tavailable(): Promise<boolean>;\n\t/** Whether this device currently holds a sealed secret to reveal. */\n\tenrolled(): boolean;\n\t/**\n\t * Create a platform passkey and seal `secret` behind it. `false` when the\n\t * user cancelled, when PRF did not yield a key here, or when this device\n\t * refused to store the sealed secret - fail closed, the host keeps its\n\t * password prompt and should say so rather than pretend.\n\t */\n\tenroll(secret: string): Promise<boolean>;\n\t/**\n\t * Ask for Face / fingerprint / Hello and return the sealed secret. `null`\n\t * on cancel, on a missing enrolment, or on any failure; the host then falls\n\t * back to asking for the password. Never throws.\n\t */\n\treveal(): Promise<string | null>;\n\t/**\n\t * Drop the sealed secret from this device. The platform credential itself\n\t * is NOT deleted - the operating system owns it, and only the user can\n\t * remove it from their passkey manager. Say that in the interface rather\n\t * than implying a full erasure. `false` when the storage refused the\n\t * removal: the enrolment is still there, and the host must not say it is off.\n\t */\n\tforget(): boolean;\n}\n\n/** PRF extension shapes are absent from some TS DOM libs; describe what we use. */\ntype PrfInputs = { prf?: { eval?: { first: BufferSource } } };\ntype PrfOutputs = { prf?: { results?: { first?: ArrayBuffer } } };\n\n/** What one device remembers: which credential to prefer, and the sealed secret. */\ninterface StoredEnrolment {\n\tcredentialId: string;\n\tiv: string;\n\tsealed: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'selfstore.passkey';\nconst PRF_BYTES = 32;\n\nfunction toB64(bytes: ArrayBuffer | Uint8Array): string {\n\tconst b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet s = '';\n\tfor (const byte of b) s += String.fromCharCode(byte);\n\treturn btoa(s);\n}\n\nfunction fromB64(s: string): Uint8Array<ArrayBuffer> {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n\nfunction webauthnUsable(): boolean {\n\treturn (\n\t\ttypeof window !== 'undefined' &&\n\t\t!!window.PublicKeyCredential &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\t!!navigator.credentials &&\n\t\ttypeof crypto !== 'undefined' &&\n\t\t!!crypto.subtle\n\t);\n}\n\nfunction prfSecret(credential: PublicKeyCredential | null): Uint8Array | null {\n\tconst first = (credential?.getClientExtensionResults() as PrfOutputs | undefined)?.prf?.results\n\t\t?.first;\n\tif (!first) return null;\n\tconst bytes = new Uint8Array(first);\n\treturn bytes.length === PRF_BYTES ? bytes : null;\n}\n\n/**\n * The 32 bytes coming out of PRF are an HMAC output, so they are already\n * uniform: import them straight as an AES-GCM key rather than running a KDF\n * over them. Stretching high-entropy material buys nothing and would only make\n * every unlock slower.\n */\nfunction sealKey(secret: Uint8Array): Promise<CryptoKey> {\n\treturn crypto.subtle.importKey('raw', secret as BufferSource, 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\nexport function passkeyUnlock(options: PasskeyUnlockOptions): PasskeyUnlock {\n\tconst storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n\tconst prfSalt = new TextEncoder().encode(options.salt);\n\n\tfunction read(): StoredEnrolment | null {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return null;\n\t\t\tconst raw = localStorage.getItem(storageKey);\n\t\t\tif (!raw) return null;\n\t\t\tconst parsed = JSON.parse(raw) as StoredEnrolment;\n\t\t\tconst complete =\n\t\t\t\ttypeof parsed?.credentialId === 'string' &&\n\t\t\t\ttypeof parsed?.iv === 'string' &&\n\t\t\t\ttypeof parsed?.sealed === 'string';\n\t\t\treturn complete ? parsed : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/** Whether the storage took the write. A blocked or full storage used to be\n\t * swallowed, so `enroll()` announced an enrolment nothing remembered. */\n\tfunction write(entry: StoredEnrolment | null): boolean {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return entry === null;\n\t\t\tif (entry) localStorage.setItem(storageKey, JSON.stringify(entry));\n\t\t\telse localStorage.removeItem(storageKey);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Re-derive the PRF secret. With a credential id it targets that one; with\n\t * none, the platform offers the user's discoverable passkeys for this site.\n\t */\n\tasync function derive(credentialId?: string): Promise<Uint8Array | null> {\n\t\ttry {\n\t\t\tconst assertion = (await navigator.credentials.get({\n\t\t\t\tpublicKey: {\n\t\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\t\tallowCredentials: credentialId\n\t\t\t\t\t\t? [{ type: 'public-key', id: fromB64(credentialId) as BufferSource }]\n\t\t\t\t\t\t: [],\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\t\ttimeout: 60_000\n\t\t\t\t}\n\t\t\t})) as PublicKeyCredential | null;\n\t\t\treturn prfSecret(assertion);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function create(): Promise<{ credentialId: string; secret: Uint8Array } | null> {\n\t\tconst credential = (await navigator.credentials.create({\n\t\t\tpublicKey: {\n\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\trp: { name: options.appName, id: location.hostname },\n\t\t\t\tuser: {\n\t\t\t\t\tid: crypto.getRandomValues(new Uint8Array(16)),\n\t\t\t\t\tname: options.appName,\n\t\t\t\t\tdisplayName: options.appName\n\t\t\t\t},\n\t\t\t\tpubKeyCredParams: [\n\t\t\t\t\t{ type: 'public-key', alg: -7 },\n\t\t\t\t\t{ type: 'public-key', alg: -257 }\n\t\t\t\t],\n\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\tauthenticatorAttachment: 'platform',\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\tresidentKey: 'required'\n\t\t\t\t},\n\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\ttimeout: 60_000\n\t\t\t}\n\t\t})) as PublicKeyCredential | null;\n\t\tif (!credential) return null;\n\t\tconst credentialId = toB64(credential.rawId);\n\t\t// Some platforms hand the PRF result back from create(); most need a\n\t\t// second get(). Try the cheap path first to save the user one prompt.\n\t\tconst secret = prfSecret(credential) ?? (await derive(credentialId));\n\t\treturn secret ? { credentialId, secret } : null;\n\t}\n\n\treturn {\n\t\tasync available(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tif (!webauthnUsable()) return false;\n\t\t\t\treturn await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tenrolled(): boolean {\n\t\t\treturn read() !== null;\n\t\t},\n\n\t\tasync enroll(secret: string): Promise<boolean> {\n\t\t\tif (!webauthnUsable()) return false;\n\t\t\ttry {\n\t\t\t\tconst created = await create();\n\t\t\t\tif (!created) return false;\n\t\t\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\t\t\tconst sealed = await crypto.subtle.encrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv },\n\t\t\t\t\tawait sealKey(created.secret),\n\t\t\t\t\tnew TextEncoder().encode(secret)\n\t\t\t\t);\n\t\t\t\treturn write({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync reveal(): Promise<string | null> {\n\t\t\tconst entry = read();\n\t\t\tif (!entry || !webauthnUsable()) return null;\n\t\t\tconst secret = await derive(entry.credentialId);\n\t\t\tif (!secret) return null;\n\t\t\ttry {\n\t\t\t\tconst opened = await crypto.subtle.decrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv: fromB64(entry.iv) as BufferSource },\n\t\t\t\t\tawait sealKey(secret),\n\t\t\t\t\tfromB64(entry.sealed) as BufferSource\n\t\t\t\t);\n\t\t\t\treturn new TextDecoder().decode(opened);\n\t\t\t} catch {\n\t\t\t\t// A wrong key or a tampered blob: an unopenable enrolment is dead\n\t\t\t\t// weight that would fail at every attempt, so drop it and let the\n\t\t\t\t// host fall back to the password.\n\t\t\t\twrite(null);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tforget(): boolean {\n\t\t\treturn write(null);\n\t\t}\n\t};\n}\n"]}
|
package/dist/proof.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SimpleRecord, FileBytes } from './index.js';
|
|
2
|
-
import './store-
|
|
3
|
-
import './cache-
|
|
2
|
+
import './store-bp1VRW7R.js';
|
|
3
|
+
import './cache-DBIn_Uo8.js';
|
|
4
4
|
import './sync.js';
|
|
5
|
-
import './connect-
|
|
6
|
-
import './drive-
|
|
7
|
-
import './s3-
|
|
5
|
+
import './connect-CdlhiTBy.js';
|
|
6
|
+
import './drive-BCRil91Q.js';
|
|
7
|
+
import './s3-kikEHI-g.js';
|
|
8
8
|
import './machine-Dhbx3Eut.js';
|
|
9
|
-
import './manager-
|
|
9
|
+
import './manager-0puQEpZc.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* What a notary hands back for one digest. `evidence` is the notary's own
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BackupTarget, P as PeerSource } from './store-
|
|
2
|
-
import { K as KV } from './cache-
|
|
1
|
+
import { B as BackupTarget, P as PeerSource } from './store-bp1VRW7R.js';
|
|
2
|
+
import { K as KV } from './cache-DBIn_Uo8.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* WebDAV backup target: the store re-writes one .selfstore file on a WebDAV
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as Snapshot, b as PasswordPolicy, c as SnapshotFile, B as BackupTarget, d as StatusDescriptor, S as StoreError, e as LocalStoreState, L as LocalStore } from './store-
|
|
2
|
-
import { L as LocalCache, K as KV } from './cache-
|
|
1
|
+
import { a as Snapshot, b as PasswordPolicy, c as SnapshotFile, B as BackupTarget, d as StatusDescriptor, S as StoreError, e as LocalStoreState, L as LocalStore } from './store-bp1VRW7R.js';
|
|
2
|
+
import { L as LocalCache, K as KV } from './cache-DBIn_Uo8.js';
|
|
3
3
|
import { SyncConfig } from './sync.js';
|
|
4
|
-
import { R as ResumeOffer, C as ConnectTargets } from './connect-
|
|
5
|
-
import { f as BackupsManager } from './manager-
|
|
6
|
-
import { a as DriveAuth } from './drive-
|
|
7
|
-
import { W as WebdavConfig, S as S3Config } from './s3-
|
|
4
|
+
import { R as ResumeOffer, C as ConnectTargets } from './connect-CdlhiTBy.js';
|
|
5
|
+
import { f as BackupsManager } from './manager-0puQEpZc.js';
|
|
6
|
+
import { a as DriveAuth } from './drive-BCRil91Q.js';
|
|
7
|
+
import { W as WebdavConfig, S as S3Config } from './s3-kikEHI-g.js';
|
|
8
8
|
|
|
9
9
|
/** What a file body may be handed in as. */
|
|
10
10
|
type FileBytes = Uint8Array | ArrayBuffer | Blob;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as GroupIdentity, S as SignedManifest, L as LocalCache } from './cache-
|
|
1
|
+
import { G as GroupIdentity, S as SignedManifest, L as LocalCache } from './cache-DBIn_Uo8.js';
|
|
2
2
|
import { CollectionChanges, Conflict, SyncConfig } from './sync.js';
|
|
3
3
|
|
|
4
4
|
/** A binary attachment (image, PDF, any bytes). */
|
package/dist/widgets.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { S as StoreLike } from './connect-
|
|
1
|
+
import { S as StoreLike } from './connect-CdlhiTBy.js';
|
|
2
2
|
import { FlowHost, ConnectTargets, ConnectFlowOptions, ConnectKind, ConnectFlow } from './flows.js';
|
|
3
3
|
import { ShareEngine, ShareLevel, ShareFlow, JoinEngine, JoinFlow, ReplicaFlow } from './flows.js';
|
|
4
4
|
import { BackupsManager } from './backups.js';
|
|
5
|
-
import './store-
|
|
6
|
-
import './cache-
|
|
5
|
+
import './store-bp1VRW7R.js';
|
|
6
|
+
import './cache-DBIn_Uo8.js';
|
|
7
7
|
import './sync.js';
|
|
8
|
-
import './drive-
|
|
9
|
-
import './s3-
|
|
8
|
+
import './drive-BCRil91Q.js';
|
|
9
|
+
import './s3-kikEHI-g.js';
|
|
10
10
|
import './machine-Dhbx3Eut.js';
|
|
11
11
|
|
|
12
12
|
/** Flat key -> string map; widgets merge a partial override over EN defaults. */
|
package/dist/widgets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { saveToDisk, datedName } from './chunk-XAONTUEC.js';
|
|
2
|
-
import { connectFlow, shareFlow, joinFlow } from './chunk-
|
|
2
|
+
import { connectFlow, shareFlow, joinFlow } from './chunk-UU4PS3UH.js';
|
|
3
3
|
import './chunk-TNYL6FNW.js';
|
|
4
4
|
import { isOpenSupported } from './chunk-HVHQLPP6.js';
|
|
5
5
|
import './chunk-2BNTCXQN.js';
|
package/llms.txt
CHANGED
|
@@ -422,7 +422,8 @@ MANIFEST_ROLLBACK. Needs WebCrypto Ed25519+X25519 (evergreen browsers, Node
|
|
|
422
422
|
## Household sharing (mirror model), 'selfstore/households'
|
|
423
423
|
|
|
424
424
|
Read-write sharing without re-keying anyone's backup: `createHouseholdGroup({
|
|
425
|
-
store, kv, backend, storageKey? })
|
|
425
|
+
store, kv, sealedKv?, backend, storageKey? })` - pass `cache.sealedKv` so K and the
|
|
426
|
+
invitation capability rest sealed, not in the clear kv. The admin calls
|
|
426
427
|
startShare() (refused on an encrypted store - the honest rule), publishes an
|
|
427
428
|
invitation via the backend; a visitor opens it (openIncoming) and join()s:
|
|
428
429
|
their own file stays theirs, a dedicated copy file mirrors it under K, and the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selfstore",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.36",
|
|
4
4
|
"description": "Local-first storage for browser apps: an IndexedDB working copy, portable encrypted ZIP backups (AES-256-GCM over Argon2id), durable homes (disk, Google Drive, WebDAV) and a serverless multi-device merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|