@enbox/auth 0.6.33 → 0.6.35

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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Seed phrase recovery helpers.
3
+ *
4
+ * When a vault is re-derived from a recovery phrase on a new device,
5
+ * the local DWN is empty. This module provides the building blocks
6
+ * for pulling identity metadata, keys, and user data from the remote
7
+ * DWN in a controlled two-phase sequence.
8
+ *
9
+ * @module
10
+ * @internal
11
+ */
12
+
13
+ import type { BearerIdentity, EnboxUserAgent } from '@enbox/agent';
14
+
15
+ import { IdentityProtocolDefinition, JwkProtocolDefinition } from '@enbox/agent';
16
+
17
+ import type { RegistrationOptions, StorageAdapter } from '../types.js';
18
+
19
+ import { registerWithDwnEndpoints } from '../registration.js';
20
+
21
+ /**
22
+ * Internal protocols that store recovery-critical data in the agent DID's DWN.
23
+ * Syncing these ensures that seed phrase recovery can pull identity metadata,
24
+ * portable DIDs, and encrypted private keys from the remote.
25
+ */
26
+ export const AGENT_DID_SYNC_PROTOCOLS: [string, ...string[]] = [
27
+ IdentityProtocolDefinition.protocol,
28
+ JwkProtocolDefinition.protocol,
29
+ ];
30
+
31
+ /**
32
+ * Register the agent DID for sync with the recovery-critical protocols.
33
+ *
34
+ * This is a prerequisite for both normal operation (pushing identity
35
+ * metadata to the remote) and seed phrase recovery (pulling it back).
36
+ * Silently succeeds if the agent DID is already registered.
37
+ */
38
+ export async function registerAgentDidForSync(userAgent: EnboxUserAgent): Promise<void> {
39
+ try {
40
+ await userAgent.sync.registerIdentity({
41
+ did : userAgent.agentDid.uri,
42
+ options : { protocols: AGENT_DID_SYNC_PROTOCOLS },
43
+ });
44
+ } catch {
45
+ // Already registered from a previous session.
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Recover identities and their data from remote DWN endpoints.
51
+ *
52
+ * Assumes the agent DID is already registered for sync (via
53
+ * {@link registerAgentDidForSync}) and as a DWN tenant.
54
+ *
55
+ * Phase 1 — pull identity metadata and DID private keys stored in the
56
+ * agent DID's DWN.
57
+ *
58
+ * Phase 2 — register each recovered identity DID as a tenant and for
59
+ * sync, then pull their profile data, protocol configurations, and
60
+ * records.
61
+ *
62
+ * Returns the recovered identities, or an empty array if the remote
63
+ * had nothing (e.g. first-ever setup with a pre-generated phrase).
64
+ */
65
+ export async function recoverIdentitiesFromRemote(params: {
66
+ userAgent: EnboxUserAgent;
67
+ dwnEndpoints: string[];
68
+ registration?: RegistrationOptions;
69
+ storage: StorageAdapter;
70
+ }): Promise<BearerIdentity[]> {
71
+ const { userAgent, dwnEndpoints, registration, storage } = params;
72
+ const agentDid = userAgent.agentDid.uri;
73
+
74
+ // Phase 1: pull identity metadata + encrypted DID keys.
75
+ await userAgent.sync.sync('pull');
76
+
77
+ const identities = await userAgent.identity.list();
78
+ if (identities.length === 0) {
79
+ return [];
80
+ }
81
+
82
+ // Register each recovered identity DID as a DWN tenant, then for sync.
83
+ // Tenant registration must come first — sync('pull') issues
84
+ // MessagesSync which requires the DID to be a recognised tenant.
85
+ for (const identity of identities) {
86
+ const did = identity.metadata.connectedDid ?? identity.did.uri;
87
+
88
+ if (registration) {
89
+ try {
90
+ await registerWithDwnEndpoints(
91
+ { userAgent, dwnEndpoints, agentDid, connectedDid: did, secretStore: userAgent.secrets, storage },
92
+ registration,
93
+ );
94
+ } catch {
95
+ // Best effort — the DID may already be registered, or the
96
+ // endpoint may be temporarily unreachable.
97
+ }
98
+ }
99
+
100
+ try {
101
+ await userAgent.sync.registerIdentity({ did, options: { protocols: 'all' } });
102
+ } catch {
103
+ // Already registered from a previous session.
104
+ }
105
+ }
106
+
107
+ // Phase 2: pull profile data, protocol configurations, and records.
108
+ await userAgent.sync.sync('pull');
109
+
110
+ return userAgent.identity.list();
111
+ }
@@ -1,34 +1,41 @@
1
1
  /**
2
- * Local DID connect flow.
2
+ * Vault connect flow.
3
3
  *
4
4
  * Creates or reconnects a local identity with vault-protected keys.
5
- * This replaces the "Mode D/E" paths in Enbox.connect().
5
+ * Used by wallets and CLI tools that own the HD identity vault directly
6
+ * (as opposed to handler-based connect, which delegates credential
7
+ * acquisition to an external wallet).
8
+ *
6
9
  * @module
7
10
  */
8
11
 
9
12
  import type { AuthSession } from '../identity-session.js';
10
13
  import type { FlowContext } from './lifecycle.js';
11
- import type { LocalConnectOptions } from '../types.js';
14
+ import type { VaultConnectOptions } from '../types.js';
12
15
 
13
16
  import { applyLocalDwnDiscovery } from '../discovery.js';
14
17
  import { DEFAULT_DWN_ENDPOINTS } from '../types.js';
15
18
  import { registerWithDwnEndpoints } from '../registration.js';
16
19
  import { createDefaultIdentity, ensureVaultReady, finalizeSession, resolveIdentityDids, resolvePassword, startSyncIfEnabled } from './lifecycle.js';
20
+ import { recoverIdentitiesFromRemote, registerAgentDidForSync } from './recovery.js';
17
21
 
18
22
  /**
19
- * Execute the local connect flow.
23
+ * Execute the vault connect flow.
20
24
  *
21
25
  * - On first launch: initializes the vault. Identity creation is opt-in via
22
26
  * `options.createIdentity: true`.
23
27
  * - On subsequent launches: unlocks the vault and reconnects to the existing identity.
28
+ * - On recovery: when `recoveryPhrase` is provided on a fresh vault, pulls
29
+ * identities and their data from the remote DWN before falling back to
30
+ * identity creation.
24
31
  *
25
32
  * When no identities exist and `createIdentity` is not `true`, the session
26
33
  * is returned with the **agent DID** as the connected DID. This allows apps to
27
34
  * manage identity creation separately from vault setup.
28
35
  */
29
- export async function localConnect(
36
+ export async function vaultConnect(
30
37
  ctx: FlowContext,
31
- options: LocalConnectOptions = {},
38
+ options: VaultConnectOptions = {},
32
39
  ): Promise<AuthSession> {
33
40
  const { userAgent, emitter, storage } = ctx;
34
41
 
@@ -56,11 +63,43 @@ export async function localConnect(
56
63
  await applyLocalDwnDiscovery(userAgent, storage, emitter);
57
64
  }
58
65
 
59
- // Find or create the user identity.
60
- const identities = await userAgent.identity.list();
66
+ // Register the agent DID as a DWN tenant and for sync early — both are
67
+ // prerequisites for seed phrase recovery and for normal push/pull of
68
+ // identity metadata after identity creation.
69
+ if (ctx.registration) {
70
+ await registerWithDwnEndpoints(
71
+ {
72
+ userAgent,
73
+ dwnEndpoints,
74
+ agentDid : userAgent.agentDid.uri,
75
+ connectedDid : userAgent.agentDid.uri,
76
+ secretStore : userAgent.secrets,
77
+ storage,
78
+ },
79
+ ctx.registration,
80
+ );
81
+ }
82
+ if (sync !== 'off') {
83
+ await registerAgentDidForSync(userAgent);
84
+ }
85
+
86
+ // Find existing identities.
87
+ let identities = await userAgent.identity.list();
61
88
  let identity = identities[0];
62
89
  let isNewIdentity = false;
63
90
 
91
+ // Seed phrase recovery: when a recovery phrase was provided on a fresh
92
+ // vault and no identities exist locally, pull them from the remote DWN.
93
+ if (!identity && isFirstLaunch && options.recoveryPhrase && sync !== 'off') {
94
+ try {
95
+ identities = await recoverIdentitiesFromRemote({ userAgent, dwnEndpoints, registration: ctx.registration, storage });
96
+ identity = identities[0];
97
+ } catch (err) {
98
+ console.warn('[@enbox/auth] Seed phrase recovery failed:', err);
99
+ }
100
+ }
101
+
102
+ // Create a default identity if none were found or recovered.
64
103
  if (!identity && shouldCreateIdentity) {
65
104
  isNewIdentity = true;
66
105
  identity = await createDefaultIdentity(userAgent, dwnEndpoints, options.metadata?.name ?? 'Default');
@@ -77,22 +116,23 @@ export async function localConnect(
77
116
  ? resolveIdentityDids(identity).delegateDid
78
117
  : undefined;
79
118
 
80
- // Register with DWN endpoints (if registration options are provided).
81
- if (ctx.registration) {
119
+ // Register the new identity DID as a tenant and for sync.
120
+ // Tenant registration must come before sync registration — with live
121
+ // sync active, registerIdentity hot-adds a subscription that needs
122
+ // the DID to be a recognised tenant on the remote DWN.
123
+ if (isNewIdentity && ctx.registration) {
82
124
  await registerWithDwnEndpoints(
83
125
  {
84
- userAgent : userAgent,
126
+ userAgent,
85
127
  dwnEndpoints,
86
128
  agentDid : userAgent.agentDid.uri,
87
129
  connectedDid,
88
130
  secretStore : userAgent.secrets,
89
- storage : storage,
131
+ storage,
90
132
  },
91
133
  ctx.registration,
92
134
  );
93
135
  }
94
-
95
- // Register sync for new identities.
96
136
  if (isNewIdentity && sync !== 'off') {
97
137
  await userAgent.sync.registerIdentity({
98
138
  did : connectedDid,
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * import { AuthManager } from '@enbox/auth';
11
11
  *
12
12
  * const auth = await AuthManager.create({ sync: '15s' });
13
- * const session = await auth.connectLocal({ password: userPin });
13
+ * const session = await auth.connectVault({ password: userPin });
14
14
  * ```
15
15
  *
16
16
  * @example Dapp with browser connect handler
@@ -90,6 +90,7 @@ export type {
90
90
  ImportFromPortableOptions,
91
91
  LocalConnectOptions,
92
92
  LocalDwnStrategy,
93
+ VaultConnectOptions,
93
94
  Permission,
94
95
  PortableIdentity,
95
96
  ProtocolRequest,
package/src/types.ts CHANGED
@@ -381,7 +381,7 @@ export interface AuthManagerOptions {
381
381
  * Default connect handler for delegated connect flows.
382
382
  *
383
383
  * Used by `connect()` when the caller provides `protocols` (or other
384
- * non-local-connect options) but does not pass a per-call handler.
384
+ * non-vault-connect options) but does not pass a per-call handler.
385
385
  *
386
386
  * @example
387
387
  * ```ts
@@ -398,8 +398,8 @@ export interface AuthManagerOptions {
398
398
  connectHandler?: ConnectHandler;
399
399
  }
400
400
 
401
- /** Options for {@link AuthManager.connect}. */
402
- export interface LocalConnectOptions {
401
+ /** Options for {@link AuthManager.connectVault}. */
402
+ export interface VaultConnectOptions {
403
403
  /** Vault password (overrides manager default). */
404
404
  password?: string;
405
405
 
@@ -434,6 +434,9 @@ export interface LocalConnectOptions {
434
434
  createIdentity?: boolean;
435
435
  }
436
436
 
437
+ /** @deprecated Use {@link VaultConnectOptions} instead. */
438
+ export type LocalConnectOptions = VaultConnectOptions;
439
+
437
440
  // ─── DWeb Connect ────────────────────────────────────────────────
438
441
 
439
442
  /**
@@ -509,14 +512,14 @@ export interface HandlerConnectOptions {
509
512
  * `connectHandler` is provided. Delegates to the connect handler
510
513
  * for credential acquisition.
511
514
  *
512
- * - **Local connect** (wallets / CLI): triggered when `password`,
515
+ * - **Vault connect** (wallets / CLI): triggered when `password`,
513
516
  * `createIdentity`, or `recoveryPhrase` is provided.
514
517
  *
515
518
  * In both cases, `connect()` first attempts to restore a previous session
516
519
  * from storage. If a valid session exists, it is returned immediately
517
520
  * without any user interaction.
518
521
  */
519
- export type ConnectOptions = HandlerConnectOptions | LocalConnectOptions;
522
+ export type ConnectOptions = HandlerConnectOptions | VaultConnectOptions;
520
523
 
521
524
  /** Options for {@link AuthManager.walletConnect}. */
522
525
  export interface WalletConnectOptions {
@@ -1 +0,0 @@
1
- {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/connect/local.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/connect/local.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOvD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,WAAW,EAChB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,WAAW,CAAC,CAqFtB"}