@synonymdev/pubky 0.15.0 → 0.16.1

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/index.cjs CHANGED
@@ -5,10 +5,27 @@ globalThis.fetch = makeFetchCookie(originalFetch);
5
5
 
6
6
  /* @ts-self-types="./pubky.d.ts" */
7
7
 
8
+ function __pubkyRecoveredEncryptionKeys(publicKey, grantId, keys) {
9
+ return { publicKey, grantId, keys };
10
+ }
11
+
12
+ async function __pubkyImportEncryptionCryptoKey(bytes) {
13
+ try {
14
+ if (!globalThis.crypto?.subtle) {
15
+ throw new Error("WebCrypto is unavailable; use a secure context.");
16
+ }
17
+ return await globalThis.crypto.subtle.importKey(
18
+ "raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
19
+ } finally {
20
+ bytes.fill(0);
21
+ }
22
+ }
23
+
8
24
  const PUBKY_SESSIONS_DB_NAME = "pubky-auth";
9
25
  const PUBKY_SESSIONS_DB_VERSION = 1;
10
26
  const PUBKY_SESSIONS_STORE_NAME = "storedSessions";
11
27
  const PUBKY_SESSIONS_DELEGATED_KEYS_STORE_NAME = "delegatedGrantKeys";
28
+ const PUBKY_APPROVAL_SESSION_VERSION = "pubky-session-v2";
12
29
 
13
30
  /** Assert that IndexedDB is available for browser session persistence. */
14
31
  function requireIndexedDb() {
@@ -53,25 +70,27 @@ function openSessionStoreDb() {
53
70
  }
54
71
 
55
72
  /**
56
- * Run an IndexedDB operation against the stored-session object store.
73
+ * Run an IndexedDB operation across session records and browser keys.
57
74
  *
58
- * The callback receives the object store and returns a request. This wrapper
75
+ * The callback receives the transaction and returns a request. This wrapper
59
76
  * resolves only after the transaction commits, not when the request succeeds.
60
77
  */
61
- async function withSessionStore(mode, operation) {
78
+ async function withSessionStores(mode, operation) {
62
79
  const db = await openSessionStoreDb();
63
80
  try {
64
81
  return await new Promise((resolve, reject) => {
65
- const tx = db.transaction(PUBKY_SESSIONS_STORE_NAME, mode);
66
- const store = tx.objectStore(PUBKY_SESSIONS_STORE_NAME);
82
+ const tx = db.transaction(
83
+ [PUBKY_SESSIONS_STORE_NAME, PUBKY_SESSIONS_DELEGATED_KEYS_STORE_NAME], mode,
84
+ );
67
85
  let result;
68
86
  try {
69
- const request = operation(store);
87
+ const request = operation(tx);
70
88
  request.onsuccess = () => {
71
89
  result = request.result;
72
90
  };
73
91
  request.onerror = () => reject(request.error ?? new Error("Pubky session store request failed."));
74
92
  } catch (error) {
93
+ tx.abort();
75
94
  reject(error);
76
95
  }
77
96
  tx.onerror = () => reject(tx.error ?? new Error("Pubky session store transaction failed."));
@@ -101,21 +120,44 @@ async function __pubkySessionStorePut(record, lease) {
101
120
  requireIndexedDb();
102
121
  try {
103
122
  const previous = await __pubkySessionStoreGet(record.id);
104
- if (previous?.sharedSession) record.sharedSession = previous.sharedSession;
105
- await withSessionStore("readwrite", (store) => {
106
- requireSessionLease(lease, true);
107
- return store.put(record);
123
+ if (previous?.version === PUBKY_APPROVAL_SESSION_VERSION && record.version !== previous.version) {
124
+ throw new Error("Cannot replace a stored approval with an authentication-only record.");
125
+ }
126
+ const stored = { ...record };
127
+ if (previous?.sharedSession) stored.sharedSession = previous.sharedSession;
128
+ await withSessionStores("readwrite", tx => {
129
+ const held = requireSessionLease(lease, true);
130
+ if (held.id !== record.id || held.homeserver !== record.homeserver) {
131
+ throw new Error("Session record does not match its browser lock.");
132
+ }
133
+ return tx.objectStore(PUBKY_SESSIONS_STORE_NAME).put(stored);
108
134
  });
109
135
  } catch (error) {
110
136
  throw contextualSessionStoreError("Saving Pubky session failed.", error);
111
137
  }
112
138
  }
113
139
 
140
+ function sessionMetadata(record) {
141
+ if (!record) return record;
142
+ const { version, id, storageMode, publicKey, homeserver, grantId, clientId,
143
+ capabilities, grantExpiresAt, createdAt } = record;
144
+ return { version, id, storageMode, publicKey, homeserver, grantId, clientId,
145
+ capabilities, grantExpiresAt, createdAt,
146
+ hasStoredApproval: record.signedApproval !== undefined,
147
+ };
148
+ }
149
+
150
+ async function __pubkySessionStoreMetadata(id) {
151
+ return sessionMetadata(await __pubkySessionStoreGet(id));
152
+ }
153
+
114
154
  /** Load a browser session record by id. */
115
155
  async function __pubkySessionStoreGet(id) {
116
156
  requireIndexedDb();
117
157
  try {
118
- return await withSessionStore("readonly", (store) => store.get(id));
158
+ return await withSessionStores("readonly", tx =>
159
+ tx.objectStore(PUBKY_SESSIONS_STORE_NAME).get(id),
160
+ );
119
161
  } catch (error) {
120
162
  throw contextualSessionStoreError("Reading Pubky session failed.", error);
121
163
  }
@@ -125,7 +167,10 @@ async function __pubkySessionStoreGet(id) {
125
167
  async function __pubkySessionStoreList() {
126
168
  if (!globalThis.indexedDB) return [];
127
169
  try {
128
- return (await withSessionStore("readonly", (store) => store.getAll())) ?? [];
170
+ const records = await withSessionStores("readonly", tx =>
171
+ tx.objectStore(PUBKY_SESSIONS_STORE_NAME).getAll(),
172
+ );
173
+ return records.map(sessionMetadata);
129
174
  } catch (_error) {
130
175
  return [];
131
176
  }
@@ -223,9 +268,9 @@ async function __pubkySharedSessionStore(token, sharedSession) {
223
268
  const lease = requireSessionLease(token, true);
224
269
  const record = await __pubkySessionStoreGet(lease.id);
225
270
  if (!record || record.homeserver !== lease.homeserver) throw new Error("Browser session was removed or changed.");
226
- await withSessionStore("readwrite", store => {
271
+ await withSessionStores("readwrite", tx => {
227
272
  requireSessionLease(token, true);
228
- return store.put({ ...record, sharedSession });
273
+ return tx.objectStore(PUBKY_SESSIONS_STORE_NAME).put({ ...record, sharedSession });
229
274
  });
230
275
  }
231
276
  async function __pubkySharedSessionRemove(token) {
@@ -504,9 +549,21 @@ async function __pubkyGrantLoadDelegatedPublicKey(keyId) {
504
549
  * bytes are returned to Rust, which finishes compact JWS serialization.
505
550
  */
506
551
  async function __pubkyGrantDelegatedSign(keyId, signingInput) {
507
- requireBrowserCrypto();
508
- const existing = await getRecord(keyId);
509
- if (!existing?.privateKey) throw new Error(`Delegated grant key not found: ${keyId}`);
552
+ let existing;
553
+ try {
554
+ requireBrowserCrypto();
555
+ existing = await getRecord(keyId);
556
+ if (!existing?.privateKey) throw new Error(`Delegated grant key not found: ${keyId}`);
557
+ } catch (cause) {
558
+ // getRecord wraps IndexedDB failures; retain the underlying diagnostic.
559
+ const detail = cause?.cause ?? cause;
560
+ const error = contextualError(
561
+ `Delegated grant signing key unavailable: ${detail?.message ?? String(detail)}`,
562
+ cause,
563
+ );
564
+ error.code = "SigningKeyUnavailable";
565
+ throw error;
566
+ }
510
567
  const data = new TextEncoder().encode(signingInput);
511
568
  return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, existing.privateKey, data));
512
569
  }
@@ -967,6 +1024,23 @@ class BrowserSessionStore {
967
1024
  const ret = wasm.browsersessionstore_remove(this.__wbg_ptr, ptr0, len0);
968
1025
  return ret;
969
1026
  }
1027
+ /**
1028
+ * Recover a stored session's encryption keys without network access.
1029
+ * Verifies the signed approval, exact grant binding, and requested session
1030
+ * identity. Works after grant expiry/revocation and without the delegated
1031
+ * WebCrypto signing key.
1032
+ * Restore material is stored as plaintext in IndexedDB.
1033
+ * Bare-grant records return undefined; signed approvals without `e` scopes
1034
+ * return an empty bundle. Creates no session.
1035
+ * @param {string} id
1036
+ * @returns {Promise<EncryptionKeys | undefined>}
1037
+ */
1038
+ restoreEncryptionKeys(id) {
1039
+ const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1040
+ const len0 = WASM_VECTOR_LEN;
1041
+ const ret = wasm.browsersessionstore_restoreEncryptionKeys(this.__wbg_ptr, ptr0, len0);
1042
+ return ret;
1043
+ }
970
1044
  /**
971
1045
  * Restore a specific stored session by id.
972
1046
  *
@@ -982,7 +1056,11 @@ class BrowserSessionStore {
982
1056
  return ret;
983
1057
  }
984
1058
  /**
985
- * Persist a completed grant session in IndexedDB.
1059
+ * Persist a completed grant session and its optional signed approval.
1060
+ * Confidential restore material is stored as plaintext in IndexedDB.
1061
+ * Delegated signing keys remain non-extractable WebCrypto keys.
1062
+ * Records are identified by user and grant ID. Reauthorizing the same
1063
+ * client creates a separate record; use the returned ID to restore it.
986
1064
  * @param {Session} session
987
1065
  * @returns {Promise<StoredSessionInfo>}
988
1066
  */
@@ -1257,6 +1335,90 @@ class DirectSignupDeepLink {
1257
1335
  if (Symbol.dispose) DirectSignupDeepLink.prototype[Symbol.dispose] = DirectSignupDeepLink.prototype.free;
1258
1336
  exports.DirectSignupDeepLink = DirectSignupDeepLink;
1259
1337
 
1338
+ /**
1339
+ * Verified scoped encryption keys obtained from a session or offline recovery.
1340
+ * This object owns its secrets independently of the session and grants no storage
1341
+ * access. Call `free()` when finished; secret bytes are zeroized on free/drop.
1342
+ */
1343
+ class EncryptionKeys {
1344
+ static __wrap(ptr) {
1345
+ const obj = Object.create(EncryptionKeys.prototype);
1346
+ obj.__wbg_ptr = ptr;
1347
+ EncryptionKeysFinalization.register(obj, obj.__wbg_ptr, obj);
1348
+ return obj;
1349
+ }
1350
+ __destroy_into_raw() {
1351
+ const ptr = this.__wbg_ptr;
1352
+ this.__wbg_ptr = 0;
1353
+ EncryptionKeysFinalization.unregister(this);
1354
+ return ptr;
1355
+ }
1356
+ free() {
1357
+ const ptr = this.__destroy_into_raw();
1358
+ wasm.__wbg_encryptionkeys_free(ptr, 0);
1359
+ }
1360
+ /**
1361
+ * Derive a non-extractable AES-GCM-256 key for WebCrypto encryption/decryption.
1362
+ * Rejects directories and files outside approved scopes. Requires WebCrypto.
1363
+ * Temporary JS key bytes are cleared after import, including on failure.
1364
+ * @param {string} path
1365
+ * @returns {Promise<CryptoKey>}
1366
+ */
1367
+ deriveCryptoKeyForPath(path) {
1368
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1369
+ const len0 = WASM_VECTOR_LEN;
1370
+ const ret = wasm.encryptionkeys_deriveCryptoKeyForPath(this.__wbg_ptr, ptr0, len0);
1371
+ return ret;
1372
+ }
1373
+ /**
1374
+ * Derive a 32-byte content key for a canonical decoded file path.
1375
+ * Rejects directories and files outside the approved scopes. The caller
1376
+ * owns the JS byte copy and should clear it after use.
1377
+ * @param {string} path
1378
+ * @returns {Uint8Array}
1379
+ */
1380
+ deriveForPath(path) {
1381
+ const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1382
+ const len0 = WASM_VECTOR_LEN;
1383
+ const ret = wasm.encryptionkeys_deriveForPath(this.__wbg_ptr, ptr0, len0);
1384
+ if (ret[2]) {
1385
+ throw takeFromExternrefTable0(ret[1]);
1386
+ }
1387
+ return takeFromExternrefTable0(ret[0]);
1388
+ }
1389
+ /**
1390
+ * Recover authenticated identity and keys from an exportLocalSecret() token offline.
1391
+ * Verifies the signed approval and grant binding even if the grant expired
1392
+ * or was revoked. Bare-grant tokens return undefined; signed approvals
1393
+ * without `e` scopes return a result with an empty key bundle.
1394
+ * Check publicKey (z-base-32) against the expected account and grantId
1395
+ * against the expected grant before using keys. Call keys.free() when done.
1396
+ * @param {string} token
1397
+ * @returns {RecoveredEncryptionKeys | undefined}
1398
+ */
1399
+ static fromLocalSecret(token) {
1400
+ const ptr0 = passStringToWasm0(token, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1401
+ const len0 = WASM_VECTOR_LEN;
1402
+ const ret = wasm.encryptionkeys_fromLocalSecret(ptr0, len0);
1403
+ if (ret[2]) {
1404
+ throw takeFromExternrefTable0(ret[1]);
1405
+ }
1406
+ return takeFromExternrefTable0(ret[0]);
1407
+ }
1408
+ /**
1409
+ * Approved scopes; directory scopes cover descendant files.
1410
+ * @returns {string[]}
1411
+ */
1412
+ get scopes() {
1413
+ const ret = wasm.encryptionkeys_scopes(this.__wbg_ptr);
1414
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
1415
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
1416
+ return v1;
1417
+ }
1418
+ }
1419
+ if (Symbol.dispose) EncryptionKeys.prototype[Symbol.dispose] = EncryptionKeys.prototype.free;
1420
+ exports.EncryptionKeys = EncryptionKeys;
1421
+
1260
1422
  /**
1261
1423
  * A single event from the event stream.
1262
1424
  *
@@ -1678,7 +1840,8 @@ class GrantAuthFlow {
1678
1840
  * Resume a previously saved pending grant auth flow (standalone).
1679
1841
  * Prefer `pubky.resumeGrantAuthFlow()` to reuse a facade client.
1680
1842
  *
1681
- * **Security:** `savedState` contains the relay secret and PoP client private key.
1843
+ * **Security:** `savedState` contains the PoP client private key and either
1844
+ * a shared relay secret or a temporary HPKE recipient secret.
1682
1845
  * Store it only temporarily and delete it once the flow completes or is abandoned.
1683
1846
  *
1684
1847
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
@@ -1698,9 +1861,9 @@ class GrantAuthFlow {
1698
1861
  /**
1699
1862
  * Save sensitive state required to resume this delegated pending grant flow.
1700
1863
  *
1701
- * This does not export the delegated private key, but it includes the relay
1702
- * secret in the authorization URL. Store it only temporarily and delete it
1703
- * once the flow completes or is abandoned.
1864
+ * This includes either a shared relay secret or a temporary HPKE recipient
1865
+ * secret. Store it only temporarily and delete it once the flow completes
1866
+ * or is abandoned.
1704
1867
  * @returns {string}
1705
1868
  */
1706
1869
  saveDelegated() {
@@ -1988,11 +2151,40 @@ class GrantSession {
1988
2151
  const ptr = this.__destroy_into_raw();
1989
2152
  wasm.__wbg_grantsession_free(ptr, 0);
1990
2153
  }
2154
+ /**
2155
+ * Sign JSON data and return both the custom proof and its root-signed grant.
2156
+ * Data follows `JSON.stringify` semantics, e.g. `undefined` properties are omitted.
2157
+ * Each proof carries its signing time (`iat`) and a random `nonce`.
2158
+ * Makes no network requests. Your applications handle freshness and replay protection.
2159
+ * @param {JsonValue} data
2160
+ * @returns {Promise<CustomPop>}
2161
+ */
2162
+ createCustomPop(data) {
2163
+ const ret = wasm.grantsession_createCustomPop(this.__wbg_ptr, data);
2164
+ return ret;
2165
+ }
2166
+ /**
2167
+ * Return scoped keys with the same derivation API as offline recovered keys.
2168
+ * Bare grants return `undefined`; signed approvals without `e` scopes
2169
+ * return an object with empty scopes.
2170
+ * Each access creates an owned copy. Keep it for repeated use and call
2171
+ * `free()` when finished. It remains usable after this view or its session
2172
+ * is freed, signed out, or revoked; freeing it does not affect the session.
2173
+ * @returns {EncryptionKeys | undefined}
2174
+ */
2175
+ get encryptionKeys() {
2176
+ const ret = wasm.grantsession_encryptionKeys(this.__wbg_ptr);
2177
+ if (ret[2]) {
2178
+ throw takeFromExternrefTable0(ret[1]);
2179
+ }
2180
+ return ret[0] === 0 ? undefined : EncryptionKeys.__wrap(ret[0]);
2181
+ }
1991
2182
  /**
1992
2183
  * Export the portable local secret material needed to restore this grant session.
1993
2184
  *
1994
2185
  * Treat the returned string as bearer-equivalent secret material until the
1995
- * grant expires or is revoked.
2186
+ * grant expires or is revoked. Included scoped keys remain sensitive after
2187
+ * expiry or revocation.
1996
2188
  *
1997
2189
  * @returns {Promise<string>}
1998
2190
  * @returns {Promise<string>}
@@ -2829,8 +3021,9 @@ class Pubky {
2829
3021
  /**
2830
3022
  * Resume a previously saved pending grant auth flow.
2831
3023
  *
2832
- * **Security:** `savedState` contains the relay secret and PoP client private key.
2833
- * Delete it from storage as soon as the resumed flow completes.
3024
+ * **Security:** `savedState` contains the PoP client private key and either
3025
+ * a shared relay secret or a temporary HPKE private key. Delete it from
3026
+ * storage when the flow completes or is abandoned.
2834
3027
  *
2835
3028
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
2836
3029
  * @returns {GrantAuthFlow} A flow reconnected to the original relay channel.
@@ -2922,11 +3115,13 @@ class Pubky {
2922
3115
  *
2923
3116
  * Grant auth uses a user-signed grant JWS plus Proof-of-Possession and
2924
3117
  * returns a self-refreshing session.
3118
+ * Select `approvalFormat: "signedApprovalV1"` and include `e` on scopes that need content
3119
+ * keys. Storage `r` and `w` alone do not deliver encryption keys.
2925
3120
  *
2926
3121
  * @param {string} capabilities Comma-separated caps, e.g. `"/pub/app/:rw,/pub/foo/file:r"`.
2927
3122
  * @param {AuthFlowKind} kind The kind of authentication flow to perform.
2928
3123
  * @param {GrantAuthFlowOptions} options Options for the grant flow:
2929
- * `{ clientId, relay?, xCallback? }`.
3124
+ * `{ clientId, relay?, xCallback?, approvalFormat? }`.
2930
3125
  * @returns {Promise<GrantAuthFlow>}
2931
3126
  * A running grant auth flow. Show `authorizationUrl` as QR/deeplink,
2932
3127
  * then `awaitApproval()` to obtain a grant-backed `Session`.
@@ -3975,7 +4170,7 @@ class Signer {
3975
4170
  wasm.__wbg_signer_free(ptr, 0);
3976
4171
  }
3977
4172
  /**
3978
- * Approve a `pubkyauth://` request URL (encrypts & POSTs the signed AuthToken).
4173
+ * Approve all capabilities in a `pubkyauth://` auth request URL.
3979
4174
  * @param {string} pubkyauth_url
3980
4175
  * @returns {Promise<void>}
3981
4176
  */
@@ -3985,6 +4180,28 @@ class Signer {
3985
4180
  const ret = wasm.signer_approveAuthRequest(this.__wbg_ptr, ptr0, len0);
3986
4181
  return ret;
3987
4182
  }
4183
+ /**
4184
+ * Approve selected capabilities in a `pubkyauth://` auth request URL.
4185
+ *
4186
+ * Scopes and actions must be covered by the request. An empty string
4187
+ * approves no capabilities. V1 grants deliver keys only for approved `e`
4188
+ * scopes. Supports grant and legacy signin/signup requests.
4189
+ * Invalid capabilities or approvals exceeding the request fail before POST.
4190
+ *
4191
+ * @example
4192
+ * await signer.approveAuthWithCapabilities(authUrl, "/pub/example.app/:r");
4193
+ * @param {string} pubkyauth_url
4194
+ * @param {string} approved_capabilities
4195
+ * @returns {Promise<void>}
4196
+ */
4197
+ approveAuthWithCapabilities(pubkyauth_url, approved_capabilities) {
4198
+ const ptr0 = passStringToWasm0(pubkyauth_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
4199
+ const len0 = WASM_VECTOR_LEN;
4200
+ const ptr1 = passStringToWasm0(approved_capabilities, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
4201
+ const len1 = WASM_VECTOR_LEN;
4202
+ const ret = wasm.signer_approveAuthWithCapabilities(this.__wbg_ptr, ptr0, len0, ptr1, len1);
4203
+ return ret;
4204
+ }
3988
4205
  /**
3989
4206
  * Create a signer from a `Keypair` (prefer `pubky.signer(kp)`).
3990
4207
  *
@@ -4041,7 +4258,8 @@ class Signer {
4041
4258
  /**
4042
4259
  * Blocking sign-in. Waits for PKDNS publish to complete (slower; safer).
4043
4260
  *
4044
- * Creates a valid grant-backed homeserver Session with root capabilities.
4261
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
4262
+ * Encryption keys require an explicit `e` request through a signed approval flow.
4045
4263
  * `clientId` is shown in the user's grant/session list.
4046
4264
  * @param {string} clientId App identifier, typically a domain.
4047
4265
  *
@@ -4078,7 +4296,8 @@ class Signer {
4078
4296
  /**
4079
4297
  * Fast sign-in for a returning user. Publishes PKDNS in the background.
4080
4298
  *
4081
- * Creates a valid grant-backed homeserver Session with root capabilities.
4299
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
4300
+ * Encryption keys require an explicit `e` request through a signed approval flow.
4082
4301
  * `clientId` is shown in the user's grant/session list.
4083
4302
  * @param {string} clientId App identifier, typically a domain.
4084
4303
  *
@@ -4256,6 +4475,22 @@ class SigninGrantDeepLink {
4256
4475
  const ptr = this.__destroy_into_raw();
4257
4476
  wasm.__wbg_signingrantdeeplink_free(ptr, 0);
4258
4477
  }
4478
+ /**
4479
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
4480
+ * @returns {string}
4481
+ */
4482
+ get approvalFormat() {
4483
+ let deferred1_0;
4484
+ let deferred1_1;
4485
+ try {
4486
+ const ret = wasm.signingrantdeeplink_approvalFormat(this.__wbg_ptr);
4487
+ deferred1_0 = ret[0];
4488
+ deferred1_1 = ret[1];
4489
+ return getStringFromWasm0(ret[0], ret[1]);
4490
+ } finally {
4491
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
4492
+ }
4493
+ }
4259
4494
  /**
4260
4495
  * Base HTTP relay inbox URL used by this auth request.
4261
4496
  *
@@ -4320,6 +4555,19 @@ class SigninGrantDeepLink {
4320
4555
  const ret = wasm.signingrantdeeplink_clientPublicKey(this.__wbg_ptr);
4321
4556
  return PublicKey.__wrap(ret);
4322
4557
  }
4558
+ /**
4559
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
4560
+ * for a shared-secret link. The relay channel ID is the unpadded
4561
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
4562
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
4563
+ *
4564
+ * @returns {Uint8Array|undefined}
4565
+ * @returns {Uint8Array | undefined}
4566
+ */
4567
+ get ephemeralPublicKey() {
4568
+ const ret = wasm.signingrantdeeplink_ephemeralPublicKey(this.__wbg_ptr);
4569
+ return ret;
4570
+ }
4323
4571
  /**
4324
4572
  * Parse a grant signin deeplink URL.
4325
4573
  *
@@ -4339,12 +4587,11 @@ class SigninGrantDeepLink {
4339
4587
  return SigninGrantDeepLink.__wrap(ret[0]);
4340
4588
  }
4341
4589
  /**
4342
- * Relay channel secret embedded in the deeplink.
4590
+ * Shared relay secret, or `undefined` for an HPKE link.
4591
+ * Keep this secret confidential until the flow completes or is abandoned.
4343
4592
  *
4344
- * Treat this as sensitive temporary auth-flow material.
4345
- *
4346
- * @returns {Uint8Array}
4347
- * @returns {Uint8Array}
4593
+ * @returns {Uint8Array|undefined}
4594
+ * @returns {Uint8Array | undefined}
4348
4595
  */
4349
4596
  get secret() {
4350
4597
  const ret = wasm.signingrantdeeplink_secret(this.__wbg_ptr);
@@ -4522,6 +4769,22 @@ class SignupGrantDeepLink {
4522
4769
  const ptr = this.__destroy_into_raw();
4523
4770
  wasm.__wbg_signupgrantdeeplink_free(ptr, 0);
4524
4771
  }
4772
+ /**
4773
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
4774
+ * @returns {string}
4775
+ */
4776
+ get approvalFormat() {
4777
+ let deferred1_0;
4778
+ let deferred1_1;
4779
+ try {
4780
+ const ret = wasm.signupgrantdeeplink_approvalFormat(this.__wbg_ptr);
4781
+ deferred1_0 = ret[0];
4782
+ deferred1_1 = ret[1];
4783
+ return getStringFromWasm0(ret[0], ret[1]);
4784
+ } finally {
4785
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
4786
+ }
4787
+ }
4525
4788
  /**
4526
4789
  * Base HTTP relay inbox URL used by this auth request.
4527
4790
  *
@@ -4586,6 +4849,19 @@ class SignupGrantDeepLink {
4586
4849
  const ret = wasm.signupgrantdeeplink_clientPublicKey(this.__wbg_ptr);
4587
4850
  return PublicKey.__wrap(ret);
4588
4851
  }
4852
+ /**
4853
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
4854
+ * for a shared-secret link. The relay channel ID is the unpadded
4855
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
4856
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
4857
+ *
4858
+ * @returns {Uint8Array|undefined}
4859
+ * @returns {Uint8Array | undefined}
4860
+ */
4861
+ get ephemeralPublicKey() {
4862
+ const ret = wasm.signupgrantdeeplink_ephemeralPublicKey(this.__wbg_ptr);
4863
+ return ret;
4864
+ }
4589
4865
  /**
4590
4866
  * Homeserver public key requested for account creation.
4591
4867
  *
@@ -4615,12 +4891,11 @@ class SignupGrantDeepLink {
4615
4891
  return SignupGrantDeepLink.__wrap(ret[0]);
4616
4892
  }
4617
4893
  /**
4618
- * Relay channel secret embedded in the deeplink.
4894
+ * Shared relay secret, or `undefined` for an HPKE link.
4895
+ * Keep this secret confidential until the flow completes or is abandoned.
4619
4896
  *
4620
- * Treat this as sensitive temporary auth-flow material.
4621
- *
4622
- * @returns {Uint8Array}
4623
- * @returns {Uint8Array}
4897
+ * @returns {Uint8Array|undefined}
4898
+ * @returns {Uint8Array | undefined}
4624
4899
  */
4625
4900
  get secret() {
4626
4901
  const ret = wasm.signupgrantdeeplink_secret(this.__wbg_ptr);
@@ -4984,7 +5259,8 @@ exports.setLogLevel = setLogLevel;
4984
5259
  /**
4985
5260
  * Validate and normalize a capabilities string.
4986
5261
  *
4987
- * - Normalizes action order (`wr` -> `rw`)
5262
+ * - Normalizes action order (`wr` -> `rw`, `ewr` -> `rwe`).
5263
+ * - `e` requests scoped encryption/decryption keys in V1 grant approvals.
4988
5264
  * - Throws `InvalidInput` identifying the first malformed entry.
4989
5265
  *
4990
5266
  * @param {string} input
@@ -5015,16 +5291,27 @@ function validateCapabilities(input) {
5015
5291
  }
5016
5292
  }
5017
5293
  exports.validateCapabilities = validateCapabilities;
5294
+
5295
+ /**
5296
+ * Verify both signatures, grant binding, and grant validity against the local clock.
5297
+ * Allows 30 seconds of future clock skew by default; never extends grant expiry.
5298
+ * Does not check replay, application authorization, or homeserver revocation.
5299
+ * @param {CustomPop} credentials
5300
+ * @param {CustomPopVerificationOptions | null} [options]
5301
+ * @returns {VerifiedCustomPop}
5302
+ */
5303
+ function verifyCustomGrantPop(credentials, options) {
5304
+ const ret = wasm.verifyCustomGrantPop(credentials, isLikeNone(options) ? 0 : addToExternrefTable0(options));
5305
+ if (ret[2]) {
5306
+ throw takeFromExternrefTable0(ret[1]);
5307
+ }
5308
+ return takeFromExternrefTable0(ret[0]);
5309
+ }
5310
+ exports.verifyCustomGrantPop = verifyCustomGrantPop;
5018
5311
  const import1 = {
5019
- __pubkyGrantIsDelegationAvailable,
5020
- __pubkyGrantCanUseDelegation,
5021
- __pubkyGrantEnsureDelegatedKey,
5022
- __pubkyGrantLoadDelegatedPublicKey,
5023
- __pubkyGrantDelegatedSign,
5024
- };
5025
- const import2 = {
5026
5312
  __pubkySessionStoreIsAvailable,
5027
5313
  __pubkySessionStorePut,
5314
+ __pubkySessionStoreMetadata,
5028
5315
  __pubkySessionStoreGet,
5029
5316
  __pubkySessionStoreList,
5030
5317
  __pubkySessionAcquire,
@@ -5036,9 +5323,10 @@ const import2 = {
5036
5323
  __pubkySessionStoreClear,
5037
5324
  __pubkySessionStoreClearAll,
5038
5325
  };
5039
- const import3 = {
5326
+ const import2 = {
5040
5327
  __pubkySessionStoreIsAvailable,
5041
5328
  __pubkySessionStorePut,
5329
+ __pubkySessionStoreMetadata,
5042
5330
  __pubkySessionStoreGet,
5043
5331
  __pubkySessionStoreList,
5044
5332
  __pubkySessionAcquire,
@@ -5050,9 +5338,10 @@ const import3 = {
5050
5338
  __pubkySessionStoreClear,
5051
5339
  __pubkySessionStoreClearAll,
5052
5340
  };
5053
- const import4 = {
5341
+ const import3 = {
5054
5342
  __pubkySessionStoreIsAvailable,
5055
5343
  __pubkySessionStorePut,
5344
+ __pubkySessionStoreMetadata,
5056
5345
  __pubkySessionStoreGet,
5057
5346
  __pubkySessionStoreList,
5058
5347
  __pubkySessionAcquire,
@@ -5064,9 +5353,10 @@ const import4 = {
5064
5353
  __pubkySessionStoreClear,
5065
5354
  __pubkySessionStoreClearAll,
5066
5355
  };
5067
- const import5 = {
5356
+ const import4 = {
5068
5357
  __pubkySessionStoreIsAvailable,
5069
5358
  __pubkySessionStorePut,
5359
+ __pubkySessionStoreMetadata,
5070
5360
  __pubkySessionStoreGet,
5071
5361
  __pubkySessionStoreList,
5072
5362
  __pubkySessionAcquire,
@@ -5078,7 +5368,18 @@ const import5 = {
5078
5368
  __pubkySessionStoreClear,
5079
5369
  __pubkySessionStoreClearAll,
5080
5370
  };
5371
+ const import5 = {
5372
+ __pubkyGrantIsDelegationAvailable,
5373
+ __pubkyGrantCanUseDelegation,
5374
+ __pubkyGrantEnsureDelegatedKey,
5375
+ __pubkyGrantLoadDelegatedPublicKey,
5376
+ __pubkyGrantDelegatedSign,
5377
+ };
5081
5378
  const import6 = {
5379
+ __pubkyRecoveredEncryptionKeys,
5380
+ __pubkyImportEncryptionCryptoKey,
5381
+ };
5382
+ const import7 = {
5082
5383
  __pubkyGrantIsDelegationAvailable,
5083
5384
  __pubkyGrantCanUseDelegation,
5084
5385
  __pubkyGrantEnsureDelegatedKey,
@@ -5104,7 +5405,7 @@ function __wbg_get_imports() {
5104
5405
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
5105
5406
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
5106
5407
  },
5107
- __wbg___pubkyGrantDelegatedSign_3145fede89a925f4: function(arg0, arg1, arg2, arg3) {
5408
+ __wbg___pubkyGrantDelegatedSign_78f7f9da0ae4d1e7: function(arg0, arg1, arg2, arg3) {
5108
5409
  let deferred0_0;
5109
5410
  let deferred0_1;
5110
5411
  let deferred1_0;
@@ -5121,7 +5422,7 @@ function __wbg_get_imports() {
5121
5422
  wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
5122
5423
  }
5123
5424
  },
5124
- __wbg___pubkyGrantEnsureDelegatedKey_1067334e491ee176: function(arg0, arg1) {
5425
+ __wbg___pubkyGrantEnsureDelegatedKey_8db39436266230bf: function(arg0, arg1) {
5125
5426
  let v0;
5126
5427
  if (arg0 !== 0) {
5127
5428
  v0 = getStringFromWasm0(arg0, arg1);
@@ -5130,7 +5431,7 @@ function __wbg_get_imports() {
5130
5431
  const ret = __pubkyGrantEnsureDelegatedKey(v0);
5131
5432
  return ret;
5132
5433
  },
5133
- __wbg___pubkyGrantLoadDelegatedPublicKey_162cec02a5e4b65f: function(arg0, arg1) {
5434
+ __wbg___pubkyGrantLoadDelegatedPublicKey_e68eaea44047aa67: function(arg0, arg1) {
5134
5435
  let deferred0_0;
5135
5436
  let deferred0_1;
5136
5437
  try {
@@ -5142,14 +5443,18 @@ function __wbg_get_imports() {
5142
5443
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5143
5444
  }
5144
5445
  },
5145
- __wbg___pubkySessionAcquire_9cbf23c9cb4de2dd: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
5446
+ __wbg___pubkyRecoveredEncryptionKeys_b1fcbbfa114eb3ca: function(arg0, arg1, arg2, arg3, arg4) {
5447
+ const ret = __pubkyRecoveredEncryptionKeys(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3), EncryptionKeys.__wrap(arg4));
5448
+ return ret;
5449
+ },
5450
+ __wbg___pubkySessionAcquire_de9717d53c8b9829: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
5146
5451
  const ret = __pubkySessionAcquire(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3), arg4 !== 0);
5147
5452
  return ret;
5148
5453
  }, arguments); },
5149
- __wbg___pubkySessionRelease_61113613898f8369: function(arg0) {
5454
+ __wbg___pubkySessionRelease_0cc80a30918c48e4: function(arg0) {
5150
5455
  __pubkySessionRelease(arg0 >>> 0);
5151
5456
  },
5152
- __wbg___pubkySessionStoreGet_bf3d995400545463: function(arg0, arg1) {
5457
+ __wbg___pubkySessionStoreGet_4212c4496bdd7e95: function(arg0, arg1) {
5153
5458
  let deferred0_0;
5154
5459
  let deferred0_1;
5155
5460
  try {
@@ -5161,23 +5466,35 @@ function __wbg_get_imports() {
5161
5466
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5162
5467
  }
5163
5468
  },
5164
- __wbg___pubkySessionStorePut_a1073209d38242cc: function(arg0, arg1) {
5469
+ __wbg___pubkySessionStoreMetadata_1ca42b0124ac7bda: function(arg0, arg1) {
5470
+ let deferred0_0;
5471
+ let deferred0_1;
5472
+ try {
5473
+ deferred0_0 = arg0;
5474
+ deferred0_1 = arg1;
5475
+ const ret = __pubkySessionStoreMetadata(getStringFromWasm0(arg0, arg1));
5476
+ return ret;
5477
+ } finally {
5478
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
5479
+ }
5480
+ },
5481
+ __wbg___pubkySessionStorePut_950dc07537c9b99b: function(arg0, arg1) {
5165
5482
  const ret = __pubkySessionStorePut(arg0, arg1 >>> 0);
5166
5483
  return ret;
5167
5484
  },
5168
- __wbg___pubkySessionWait_21c5e8e3be5de6ff: function(arg0) {
5485
+ __wbg___pubkySessionWait_0b154edd2f3697be: function(arg0) {
5169
5486
  const ret = __pubkySessionWait(arg0 >>> 0);
5170
5487
  return ret;
5171
5488
  },
5172
- __wbg___pubkySharedSessionLoad_5cd9c74d47bf2afd: function(arg0) {
5489
+ __wbg___pubkySharedSessionLoad_e99bffd1e2cbc147: function(arg0) {
5173
5490
  const ret = __pubkySharedSessionLoad(arg0 >>> 0);
5174
5491
  return ret;
5175
5492
  },
5176
- __wbg___pubkySharedSessionRemove_56831ee1430cf7ad: function(arg0) {
5493
+ __wbg___pubkySharedSessionRemove_ba5487e63035a9a9: function(arg0) {
5177
5494
  const ret = __pubkySharedSessionRemove(arg0 >>> 0);
5178
5495
  return ret;
5179
5496
  },
5180
- __wbg___pubkySharedSessionStore_beb384d555af114b: function(arg0, arg1) {
5497
+ __wbg___pubkySharedSessionStore_507e41f8a033dcc1: function(arg0, arg1) {
5181
5498
  const ret = __pubkySharedSessionStore(arg0 >>> 0, arg1);
5182
5499
  return ret;
5183
5500
  },
@@ -5330,6 +5647,10 @@ function __wbg_get_imports() {
5330
5647
  const ret = arg0.done;
5331
5648
  return ret;
5332
5649
  },
5650
+ __wbg_encryptionkeys_new: function(arg0) {
5651
+ const ret = EncryptionKeys.__wrap(arg0);
5652
+ return ret;
5653
+ },
5333
5654
  __wbg_enqueue_6cb545d22db14f33: function() { return handleError(function (arg0, arg1) {
5334
5655
  arg0.enqueue(arg1);
5335
5656
  }, arguments); },
@@ -5348,7 +5669,7 @@ function __wbg_get_imports() {
5348
5669
  const ret = Event.__wrap(arg0);
5349
5670
  return ret;
5350
5671
  },
5351
- __wbg_fetch_1182c189ace424aa: function(arg0) {
5672
+ __wbg_fetch_66895227032f0bdf: function(arg0) {
5352
5673
  const ret = fetch(arg0);
5353
5674
  return ret;
5354
5675
  },
@@ -5435,6 +5756,16 @@ function __wbg_get_imports() {
5435
5756
  const ret = result;
5436
5757
  return ret;
5437
5758
  },
5759
+ __wbg_instanceof_CryptoKey_98e09fab6f03bfea: function(arg0) {
5760
+ let result;
5761
+ try {
5762
+ result = arg0 instanceof CryptoKey;
5763
+ } catch (_) {
5764
+ result = false;
5765
+ }
5766
+ const ret = result;
5767
+ return ret;
5768
+ },
5438
5769
  __wbg_instanceof_Error_fe6fa771c78ee4cf: function(arg0) {
5439
5770
  let result;
5440
5771
  try {
@@ -5508,6 +5839,10 @@ function __wbg_get_imports() {
5508
5839
  __wbg_log_d4082f66a52944c3: function(arg0, arg1, arg2, arg3) {
5509
5840
  console.log(arg0, arg1, arg2, arg3);
5510
5841
  },
5842
+ __wbg_message_1cbc5bc03dcf1dee: function(arg0) {
5843
+ const ret = arg0.message;
5844
+ return ret;
5845
+ },
5511
5846
  __wbg_msCrypto_bd5a034af96bcba6: function(arg0) {
5512
5847
  const ret = arg0.msCrypto;
5513
5848
  return ret;
@@ -5798,17 +6133,17 @@ function __wbg_get_imports() {
5798
6133
  console.warn(arg0, arg1, arg2, arg3);
5799
6134
  },
5800
6135
  __wbindgen_generic_0000000000000001: function(arg0, arg1) {
5801
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 129, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
6136
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 125, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
5802
6137
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke___wasm_bindgen_6fb33c0245079662___JsValue__core_608f92abc48d28da___result__Result_____wasm_bindgen_6fb33c0245079662___JsError___true_);
5803
6138
  return ret;
5804
6139
  },
5805
6140
  __wbindgen_generic_0000000000000002: function(arg0, arg1) {
5806
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 267, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
6141
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 281, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
5807
6142
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke___wasm_bindgen_6fb33c0245079662___JsValue______true_);
5808
6143
  return ret;
5809
6144
  },
5810
6145
  __wbindgen_generic_0000000000000003: function(arg0, arg1) {
5811
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 269, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
6146
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 279, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
5812
6147
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen_6fb33c0245079662___convert__closures_____invoke_______true_);
5813
6148
  return ret;
5814
6149
  },
@@ -5847,14 +6182,14 @@ function __wbg_get_imports() {
5847
6182
  __wbindgen_generic_000000000000000a: function(arg0, arg1) {
5848
6183
  var v0 = getArrayJsValueFromWasm0(arg0, arg1);
5849
6184
  wasm.__wbindgen_free(arg0, arg1 * 4, 4);
5850
- // Cast intrinsic for `Vector(RustStruct { name: "GrantInfo", unique_crate_identifier: "pubky-wasm-7d6e91dad805c24f" }) -> Externref`.
6185
+ // Cast intrinsic for `Vector(RustStruct { name: "GrantInfo", unique_crate_identifier: "pubky-wasm-6e41d7b4dc13a4fc" }) -> Externref`.
5851
6186
  const ret = v0;
5852
6187
  return ret;
5853
6188
  },
5854
6189
  __wbindgen_generic_000000000000000b: function(arg0, arg1) {
5855
6190
  var v0 = getArrayJsValueFromWasm0(arg0, arg1);
5856
6191
  wasm.__wbindgen_free(arg0, arg1 * 4, 4);
5857
- // Cast intrinsic for `Vector(RustStruct { name: "StoredSessionInfo", unique_crate_identifier: "pubky-wasm-7d6e91dad805c24f" }) -> Externref`.
6192
+ // Cast intrinsic for `Vector(RustStruct { name: "StoredSessionInfo", unique_crate_identifier: "pubky-wasm-6e41d7b4dc13a4fc" }) -> Externref`.
5858
6193
  const ret = v0;
5859
6194
  return ret;
5860
6195
  },
@@ -5871,12 +6206,13 @@ function __wbg_get_imports() {
5871
6206
  return {
5872
6207
  __proto__: null,
5873
6208
  "./pubky_bg.js": import0,
5874
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline1.js": import1,
5875
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import2,
5876
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import3,
5877
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import4,
5878
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline0.js": import5,
5879
- "./snippets/pubky-wasm-7d6e91dad805c24f/inline1.js": import6,
6209
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline1.js": import1,
6210
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline1.js": import2,
6211
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline1.js": import3,
6212
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline1.js": import4,
6213
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline2.js": import5,
6214
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline0.js": import6,
6215
+ "./snippets/pubky-wasm-6e41d7b4dc13a4fc/inline2.js": import7,
5880
6216
  };
5881
6217
  }
5882
6218
 
@@ -5937,6 +6273,9 @@ const CookieSessionFinalization = (typeof FinalizationRegistry === 'undefined')
5937
6273
  const DirectSignupDeepLinkFinalization = (typeof FinalizationRegistry === 'undefined')
5938
6274
  ? { register: () => {}, unregister: () => {} }
5939
6275
  : new FinalizationRegistry(ptr => wasm.__wbg_directsignupdeeplink_free(ptr, 1));
6276
+ const EncryptionKeysFinalization = (typeof FinalizationRegistry === 'undefined')
6277
+ ? { register: () => {}, unregister: () => {} }
6278
+ : new FinalizationRegistry(ptr => wasm.__wbg_encryptionkeys_free(ptr, 1));
5940
6279
  const EventFinalization = (typeof FinalizationRegistry === 'undefined')
5941
6280
  ? { register: () => {}, unregister: () => {} }
5942
6281
  : new FinalizationRegistry(ptr => wasm.__wbg_event_free(ptr, 1));