@abraca/dabra 1.1.2 → 1.2.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/dist/index.d.ts CHANGED
@@ -531,72 +531,100 @@ declare class AbracadabraClient {
531
531
  /**
532
532
  * CryptoIdentityKeystore
533
533
  *
534
- * Per-device Ed25519 keypair, private key protected by WebAuthn PRF + AES-256-GCM.
535
- * Stored in IndexedDB under "abracadabra:identity" / "identity" / key "current".
534
+ * Per-user Ed25519 keypair derived deterministically from a synced WebAuthn
535
+ * passkey's PRF extension output. The same passkey on any device produces the
536
+ * same identity — no private key storage needed.
536
537
  *
537
- * No private key is ever shared between devices. Each device generates its own
538
- * keypair, encrypts the private key with the PRF output from its own WebAuthn
539
- * credential, and stores the ciphertext in IndexedDB.
538
+ * Derivation chain:
539
+ * Synced Passkey PRF(constant salt) HKDF-SHA256 Ed25519 seed keypair
540
540
  *
541
- * Dependencies: @noble/ed25519, @noble/hashes (for HKDF)
541
+ * IndexedDB is used only as a lightweight cache for the public key and
542
+ * credential ID. Loss of IndexedDB is non-catastrophic — a passkey assertion
543
+ * re-derives everything.
544
+ *
545
+ * Dependencies: @noble/ed25519, @noble/hashes (for HKDF), @noble/curves (for X25519)
542
546
  */
543
547
  declare class CryptoIdentityKeystore {
544
548
  /**
545
- * One-time setup for a device: generates an Ed25519 keypair, creates a
546
- * WebAuthn credential with PRF extension, encrypts the private key, and
547
- * stores everything in IndexedDB.
548
- *
549
- * Returns the base64url-encoded public key. The caller must register this
550
- * key with the server via POST /auth/register (first device) or
551
- * POST /auth/keys (additional device).
549
+ * Check whether the platform supports WebAuthn with PRF extension.
550
+ * Call this before offering the "Secure with Passkey" option.
551
+ */
552
+ static isPrfAvailable(): Promise<boolean>;
553
+ /**
554
+ * Create a synced discoverable passkey and derive the Ed25519 identity from
555
+ * its PRF output. The passkey is stored in the platform credential manager
556
+ * (e.g. iCloud Keychain) and syncs across devices automatically.
552
557
  *
553
- * @param username - The user's account name.
554
- * @param rpId - WebAuthn relying party ID (e.g. "example.com").
555
- * @param rpName - Human-readable relying party name.
558
+ * Returns the base64url-encoded public key, X25519 public key, and credential ID.
559
+ * The caller must register the public key with the server.
556
560
  */
557
561
  register(username: string, rpId: string, rpName: string): Promise<{
558
562
  publicKey: string;
559
563
  x25519PublicKey: string;
564
+ credentialId: string;
560
565
  }>;
561
566
  /**
562
- * Sign a base64url-encoded challenge using the stored Ed25519 private key.
563
- *
564
- * This triggers a WebAuthn assertion (biometric / PIN prompt) to unlock the
565
- * private key via PRF → HKDF → AES-GCM decryption. The private key is
566
- * wiped from memory after signing.
567
+ * Sign a base64url-encoded challenge using the Ed25519 key derived from
568
+ * a passkey assertion. Triggers a WebAuthn prompt (biometric / PIN).
567
569
  *
568
570
  * @param challengeB64 - base64url-encoded challenge bytes from the server.
571
+ * @param credentialIdHint - optional credential ID to select a specific passkey.
569
572
  * @returns base64url-encoded Ed25519 signature (64 bytes).
570
573
  */
571
- sign(challengeB64: string): Promise<string>;
572
- /** Returns the stored base64url public key, or null if no identity exists. */
573
- getPublicKey(): Promise<string | null>;
574
+ sign(challengeB64: string, credentialIdHint?: string): Promise<string>;
574
575
  /**
575
- * Returns the locally-stored internal username label, or null if no identity exists.
576
+ * Returns the cached base64url public key, or null if no identity is cached.
576
577
  *
577
- * This is NOT the auth identifier (the public key is). It can be used as a
578
- * hint when calling POST /auth/register, or displayed before the user sets
579
- * a real display name via PATCH /users/me.
578
+ * Does NOT trigger a WebAuthn prompt. If the cache is empty (e.g. IndexedDB
579
+ * cleared), returns null the identity can be recovered via sign() or
580
+ * a fresh register() with the same synced passkey.
580
581
  */
581
- getUsername(): Promise<string | null>;
582
- /** Returns true if an identity is stored in IndexedDB. */
582
+ getPublicKey(credentialIdHint?: string): Promise<string | null>;
583
+ /**
584
+ * Returns the locally-cached username label, or null if no identity is cached.
585
+ */
586
+ getUsername(credentialIdHint?: string): Promise<string | null>;
587
+ /** Returns true if an identity is cached in IndexedDB. */
583
588
  hasIdentity(): Promise<boolean>;
584
- /** Remove the stored identity from IndexedDB. */
589
+ /** Remove cached identity record(s) from IndexedDB. The passkey itself
590
+ * remains in the platform credential store. */
585
591
  clear(): Promise<void>;
586
592
  /**
587
- * Returns the X25519 public key derived from the stored Ed25519 private key.
588
- * Does NOT require WebAuthn — computed from the stored encrypted key... actually
589
- * we derive from the Ed25519 public key directly (Montgomery form), no decryption needed
590
- * since nobleEd25519Curves.utils.toMontgomery only needs the public key.
591
- * Returns null if no identity is stored.
593
+ * Returns the X25519 public key derived from the cached Ed25519 public key.
594
+ * Does NOT require WebAuthn — computed from the cached public key only.
595
+ * Returns null if no identity is cached.
592
596
  */
593
597
  getX25519PublicKey(): Promise<Uint8Array | null>;
594
598
  /**
595
- * Returns the X25519 private key derived from the stored Ed25519 private key.
596
- * Requires WebAuthn assertion to decrypt the private key.
599
+ * Returns the X25519 private key derived from the Ed25519 seed.
600
+ * Requires a WebAuthn assertion to get the PRF output.
597
601
  * The caller MUST wipe the returned Uint8Array after use.
598
602
  */
599
- getX25519PrivateKey(): Promise<Uint8Array>;
603
+ getX25519PrivateKey(credentialIdHint?: string): Promise<Uint8Array>;
604
+ /**
605
+ * Trigger a WebAuthn assertion to derive (or re-derive) the identity and
606
+ * update the IndexedDB cache. Returns the public key and credential ID.
607
+ *
608
+ * Use this when the cache is empty but you need the public key before
609
+ * signing (e.g. to send it to the server for the challenge request).
610
+ */
611
+ deriveIdentity(credentialIdHint?: string): Promise<{
612
+ publicKey: string;
613
+ credentialId: string;
614
+ }>;
615
+ /**
616
+ * List all cached credential IDs. Useful for account switching UI.
617
+ */
618
+ listCachedIdentities(): Promise<{
619
+ credentialId: string;
620
+ publicKey: string;
621
+ username: string;
622
+ }[]>;
623
+ /**
624
+ * Perform a WebAuthn assertion with PRF, derive the Ed25519 seed, and
625
+ * update the IndexedDB cache. Returns the seed (caller MUST wipe it).
626
+ */
627
+ private _assertAndDerive;
600
628
  }
601
629
  //#endregion
602
630
  //#region packages/provider/src/DocKeyManager.d.ts
@@ -1725,7 +1753,7 @@ interface BackgroundSyncManagerOptions {
1725
1753
  syncTimeout?: number;
1726
1754
  /** Pre-cache file blobs after syncing a doc. Default: true. */
1727
1755
  prefetchFiles?: boolean;
1728
- /** Delay (ms) between starting each doc sync to avoid server pressure. Default: 50. */
1756
+ /** Delay (ms) between starting each doc sync to avoid server pressure. Default: 200. */
1729
1757
  throttleMs?: number;
1730
1758
  /** Max retries for failed docs within a single syncAll() run. Default: 2. */
1731
1759
  maxRetries?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -38,7 +38,7 @@ export interface BackgroundSyncManagerOptions {
38
38
  syncTimeout?: number;
39
39
  /** Pre-cache file blobs after syncing a doc. Default: true. */
40
40
  prefetchFiles?: boolean;
41
- /** Delay (ms) between starting each doc sync to avoid server pressure. Default: 50. */
41
+ /** Delay (ms) between starting each doc sync to avoid server pressure. Default: 200. */
42
42
  throttleMs?: number;
43
43
  /** Max retries for failed docs within a single syncAll() run. Default: 2. */
44
44
  maxRetries?: number;
@@ -100,7 +100,7 @@ export class BackgroundSyncManager extends EventEmitter {
100
100
  concurrency: opts?.concurrency ?? 2,
101
101
  syncTimeout: opts?.syncTimeout ?? 15_000,
102
102
  prefetchFiles: opts?.prefetchFiles ?? true,
103
- throttleMs: opts?.throttleMs ?? 50,
103
+ throttleMs: opts?.throttleMs ?? 200,
104
104
  maxRetries: opts?.maxRetries ?? 2,
105
105
  };
106
106