@synonymdev/pubky 0.15.0 → 0.16.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 +115 -5
- package/index.cjs +390 -73
- package/index.js +392 -74
- package/package.json +2 -2
- package/pubky.d.ts +218 -22
- package/pubky_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -293,19 +293,31 @@ legacy `callback` parameter is accepted as an `xSuccess` fallback.
|
|
|
293
293
|
|
|
294
294
|
#### Resume an auth flow after page refresh
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
Save pending grant auth state before a refresh, then restore it afterwards.
|
|
297
|
+
The browser example below uses a delegated signing key stored in IndexedDB.
|
|
298
|
+
It requires a secure context with WebCrypto Ed25519 support and IndexedDB.
|
|
299
|
+
|
|
300
|
+
For signed approvals, saved flow state includes a temporary HPKE private key
|
|
301
|
+
that decrypts the signer's response. The authorization URL carries the matching
|
|
302
|
+
public key in `epk`; a domain-separated hash of that key identifies the relay
|
|
303
|
+
channel. Keep the saved state confidential and delete it when the flow
|
|
304
|
+
completes or is abandoned.
|
|
297
305
|
|
|
298
306
|
```js
|
|
299
|
-
|
|
307
|
+
import { GrantAuthFlow } from "@synonymdev/pubky";
|
|
308
|
+
|
|
309
|
+
// Before refreshing:
|
|
310
|
+
const flow = await GrantAuthFlow.startDelegated(caps, AuthFlowKind.signin(), {
|
|
300
311
|
clientId: "my-cool-app.example",
|
|
301
312
|
relay,
|
|
302
313
|
});
|
|
303
|
-
sessionStorage.setItem("pubky-grant-auth", flow.
|
|
314
|
+
sessionStorage.setItem("pubky-grant-auth", flow.saveDelegated());
|
|
304
315
|
|
|
316
|
+
// After reloading:
|
|
305
317
|
const saved = sessionStorage.getItem("pubky-grant-auth");
|
|
306
318
|
if (saved) {
|
|
307
319
|
try {
|
|
308
|
-
const resumed = pubky.
|
|
320
|
+
const resumed = await pubky.resumeDelegatedGrantAuthFlow(saved);
|
|
309
321
|
const session = await resumed.awaitApproval();
|
|
310
322
|
} finally {
|
|
311
323
|
sessionStorage.removeItem("pubky-grant-auth");
|
|
@@ -313,7 +325,96 @@ if (saved) {
|
|
|
313
325
|
}
|
|
314
326
|
```
|
|
315
327
|
|
|
316
|
-
|
|
328
|
+
For local signing keys, use `GrantAuthFlow.start()`, `flow.saveLocal()`, and
|
|
329
|
+
`pubky.resumeGrantAuthFlow(saved)`.
|
|
330
|
+
|
|
331
|
+
Legacy cookie auth flows can be resumed with
|
|
332
|
+
`pubky.resumeCookieAuthFlow(authorizationUrl)`. Store pending auth state in
|
|
333
|
+
`sessionStorage`, not `localStorage`, and delete it once the flow completes or
|
|
334
|
+
is abandoned.
|
|
335
|
+
|
|
336
|
+
#### Request encryption keys
|
|
337
|
+
|
|
338
|
+
Apps can also ask the signer for encryption keys scoped to storage paths. Set
|
|
339
|
+
`approvalFormat: "signedApprovalV1"` and add the `e` action to each scope that
|
|
340
|
+
needs keys. Storage actions `r` and `w` don't deliver keys.
|
|
341
|
+
|
|
342
|
+
Keys stay usable after the grant expires or is revoked, and older homeservers
|
|
343
|
+
reject grants containing `e`. Read the
|
|
344
|
+
[scoped encryption keys guide](../../../../docs/scoped-encryption-keys.md)
|
|
345
|
+
before you use them.
|
|
346
|
+
|
|
347
|
+
```js
|
|
348
|
+
const flow = await pubky.startGrantAuthFlow("/pub/chat/:rwe", AuthFlowKind.signin(), {
|
|
349
|
+
clientId: "chat.example",
|
|
350
|
+
approvalFormat: "signedApprovalV1",
|
|
351
|
+
});
|
|
352
|
+
renderQr(flow.authorizationUrl);
|
|
353
|
+
const session = await flow.awaitApproval();
|
|
354
|
+
|
|
355
|
+
const keys = session.grant.encryptionKeys; // undefined for bare grants
|
|
356
|
+
let key;
|
|
357
|
+
try {
|
|
358
|
+
// The signer may narrow scopes or decline `e`.
|
|
359
|
+
if (!keys.scopes.includes("/pub/chat/")) {
|
|
360
|
+
throw new Error("The signer didn't approve chat keys.");
|
|
361
|
+
}
|
|
362
|
+
key = await keys.deriveCryptoKeyForPath("/pub/chat/message");
|
|
363
|
+
} finally {
|
|
364
|
+
keys.free(); // The CryptoKey stays usable.
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const iv = crypto.getRandomValues(new Uint8Array(12)); // Fresh IV every time.
|
|
368
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
369
|
+
{ name: "AES-GCM", iv }, key, new TextEncoder().encode("Hello"),
|
|
370
|
+
);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`EncryptionKeys` has these members:
|
|
374
|
+
|
|
375
|
+
| API | Returns |
|
|
376
|
+
| --- | --- |
|
|
377
|
+
| `keys.scopes` | Approved scope paths as `string[]` |
|
|
378
|
+
| `keys.deriveForPath(path)` | Raw key bytes as `Uint8Array`. Clear them after use. |
|
|
379
|
+
| `await keys.deriveCryptoKeyForPath(path)` | Non-extractable AES-GCM-256 `CryptoKey`. Requires WebCrypto in a secure context. |
|
|
380
|
+
|
|
381
|
+
Both derivation methods take a canonical file path and reject directories and
|
|
382
|
+
paths outside the approved scopes. Each read of `session.grant.encryptionKeys`
|
|
383
|
+
returns a separate copy; call `free()` when you're done with it. Signed
|
|
384
|
+
approvals without `e` scopes return an object with empty scopes.
|
|
385
|
+
|
|
386
|
+
`exportLocalSecret()` and `browserSessionStore.save()` keep the keys. The
|
|
387
|
+
browser store saves them as plaintext in IndexedDB, readable by any script on
|
|
388
|
+
the same origin. You can recover keys offline, even after the grant expires or
|
|
389
|
+
is revoked. Recovery creates no session:
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
import { EncryptionKeys } from "@synonymdev/pubky";
|
|
393
|
+
|
|
394
|
+
const recovered = EncryptionKeys.fromLocalSecret(savedToken);
|
|
395
|
+
if (recovered) {
|
|
396
|
+
try {
|
|
397
|
+
if (recovered.publicKey !== expectedUser.z32() ||
|
|
398
|
+
recovered.grantId !== expectedGrantId) {
|
|
399
|
+
throw new Error("This backup belongs to a different account or grant");
|
|
400
|
+
}
|
|
401
|
+
const key = await recovered.keys.deriveCryptoKeyForPath("/pub/chat/message");
|
|
402
|
+
// Use key with WebCrypto for locally saved ciphertext.
|
|
403
|
+
} finally {
|
|
404
|
+
recovered.keys.free();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`fromLocalSecret()` returns `{ publicKey, grantId, keys }`, or `undefined` for
|
|
410
|
+
bare-grant tokens. `publicKey` is the authenticated account's z-base-32 key.
|
|
411
|
+
Compare it with the account you expect; also check `grantId` when restoring a
|
|
412
|
+
specific grant. Callers that previously received keys directly must now read
|
|
413
|
+
`recovered.keys` after checking the identity.
|
|
414
|
+
|
|
415
|
+
For a browser record, use
|
|
416
|
+
`await pubky.browserSessionStore.restoreEncryptionKeys(storedId)`. It checks
|
|
417
|
+
the account and grant against the record ID and returns the keys directly.
|
|
317
418
|
|
|
318
419
|
#### Validate and normalize capabilities
|
|
319
420
|
|
|
@@ -472,6 +573,15 @@ await signer.pkdns.publishHomeserverForce(/* optional override homeserver*/);
|
|
|
472
573
|
await signer.pkdns.getHomeserver();
|
|
473
574
|
```
|
|
474
575
|
|
|
576
|
+
### Custom proofs
|
|
577
|
+
|
|
578
|
+
Use `session.grant.createCustomPop(data)` to sign JSON and return both the proof
|
|
579
|
+
and its root-signed grant. Use `verifyCustomGrantPop(credentials)` to verify the bundle
|
|
580
|
+
offline.
|
|
581
|
+
|
|
582
|
+
See [Sign and verify custom data](https://github.com/pubky/pubky-homeserver/blob/main/docs/CUSTOM_POP.md) for Rust and
|
|
583
|
+
JavaScript examples and the verification contract.
|
|
584
|
+
|
|
475
585
|
## Logging
|
|
476
586
|
|
|
477
587
|
The SDK ships with a WASM logger that bridges Rust `log` output into the browser or Node console. Call `setLogLevel` **once at application start**, before constructing `Pubky` or other SDK actors, to choose how verbose the logs should be.
|