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