@enbox/auth 0.6.39 → 0.6.40
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/esm/auth-manager.js +30 -33
- package/dist/esm/auth-manager.js.map +1 -1
- package/dist/esm/connect/import.js +1 -100
- package/dist/esm/connect/import.js.map +1 -1
- package/dist/esm/connect/lifecycle.js +21 -3
- package/dist/esm/connect/lifecycle.js.map +1 -1
- package/dist/esm/connect/vault.js +17 -18
- package/dist/esm/connect/vault.js.map +1 -1
- package/dist/esm/errors.js +11 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/identity-session.js +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/types/auth-manager.d.ts +20 -27
- package/dist/types/auth-manager.d.ts.map +1 -1
- package/dist/types/connect/import.d.ts +1 -11
- package/dist/types/connect/import.d.ts.map +1 -1
- package/dist/types/connect/lifecycle.d.ts +5 -2
- package/dist/types/connect/lifecycle.d.ts.map +1 -1
- package/dist/types/connect/vault.d.ts +2 -2
- package/dist/types/connect/vault.d.ts.map +1 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/identity-session.d.ts +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +20 -19
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/auth-manager.ts +33 -33
- package/src/connect/import.ts +2 -114
- package/src/connect/lifecycle.ts +19 -3
- package/src/connect/vault.ts +15 -18
- package/src/errors.ts +12 -0
- package/src/identity-session.ts +1 -1
- package/src/index.ts +3 -3
- package/src/types.ts +21 -23
package/src/auth-manager.ts
CHANGED
|
@@ -29,9 +29,9 @@ import type {
|
|
|
29
29
|
DisconnectOptions,
|
|
30
30
|
HandlerConnectOptions,
|
|
31
31
|
HeadlessConnectOptions,
|
|
32
|
-
ImportFromPhraseOptions,
|
|
33
32
|
ImportFromPortableOptions,
|
|
34
33
|
RegistrationOptions,
|
|
34
|
+
RestoreFromPhraseOptions,
|
|
35
35
|
RestoreSessionOptions,
|
|
36
36
|
ShutdownOptions,
|
|
37
37
|
StorageAdapter,
|
|
@@ -48,13 +48,13 @@ import { AuthEventEmitter } from './events.js';
|
|
|
48
48
|
import { AuthSession } from './identity-session.js';
|
|
49
49
|
import { createDefaultStorage } from './storage/storage.js';
|
|
50
50
|
import { discoverLocalDwn } from './discovery.js';
|
|
51
|
+
import { importFromPortable } from './connect/import.js';
|
|
51
52
|
import { normalizeProtocolRequests } from './permissions.js';
|
|
52
53
|
import { restoreSession } from './connect/restore.js';
|
|
53
54
|
import { STORAGE_KEYS } from './types.js';
|
|
54
55
|
import { vaultConnect } from './connect/vault.js';
|
|
55
56
|
import { walletConnect } from './connect/wallet.js';
|
|
56
57
|
import { deriveActiveSyncScope, ensureVaultReady, finalizeDelegateSession, importDelegateAndSetupSync, resolveIdentityDids, resolvePassword, startSyncIfEnabled, toSyncIdentityProtocols } from './connect/lifecycle.js';
|
|
57
|
-
import { importFromPhrase, importFromPortable } from './connect/import.js';
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* The primary entry point for authentication and identity management.
|
|
@@ -71,7 +71,7 @@ import { importFromPhrase, importFromPortable } from './connect/import.js';
|
|
|
71
71
|
*
|
|
72
72
|
* // First time: creates a new identity
|
|
73
73
|
* // Subsequent times: restores the previous session
|
|
74
|
-
* const session = await auth.restoreSession() ?? await auth.
|
|
74
|
+
* const session = await auth.restoreSession() ?? await auth.connectVault({ createIdentity: true });
|
|
75
75
|
*
|
|
76
76
|
* // session.agent — the authenticated Enbox agent
|
|
77
77
|
* // session.did — the connected DID URI
|
|
@@ -206,17 +206,20 @@ export class AuthManager {
|
|
|
206
206
|
* This is the primary entry point for dapps. It routes to the
|
|
207
207
|
* appropriate flow based on the options:
|
|
208
208
|
*
|
|
209
|
+
* **Recovery phrase restore** (wallets / CLI): Explicitly restores or
|
|
210
|
+
* re-unlocks a vault. Triggered first when `recoveryPhrase` is provided.
|
|
211
|
+
*
|
|
209
212
|
* **Handler-based connect** (dapps): Delegates credential acquisition
|
|
210
213
|
* to a {@link ConnectHandler}. Triggered when `protocols` or
|
|
211
|
-
* `connectHandler` is provided.
|
|
214
|
+
* `connectHandler` is provided and no `recoveryPhrase` is present.
|
|
212
215
|
*
|
|
213
216
|
* **Local connect** (wallets / CLI): Creates or unlocks a local vault.
|
|
214
217
|
* Triggered when `password`, `createIdentity`, or `recoveryPhrase`
|
|
215
218
|
* is provided.
|
|
216
219
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
+
* `connect()` first attempts to restore a previous session unless a
|
|
221
|
+
* recovery phrase is provided. Recovery is an explicit user action and
|
|
222
|
+
* bypasses stored-session restore.
|
|
220
223
|
*
|
|
221
224
|
* @example Dapp (browser)
|
|
222
225
|
* ```ts
|
|
@@ -245,6 +248,11 @@ export class AuthManager {
|
|
|
245
248
|
*/
|
|
246
249
|
async connect(options?: ConnectOptions): Promise<AuthSession> {
|
|
247
250
|
return this._withConnect(async () => {
|
|
251
|
+
// Recovery is an explicit user action. Do not let a stale stored session intercept it.
|
|
252
|
+
if (this._isPhraseRestore(options)) {
|
|
253
|
+
return vaultConnect(this._flowContext(), options);
|
|
254
|
+
}
|
|
255
|
+
|
|
248
256
|
// 1. Try to restore a previous session first.
|
|
249
257
|
const restored = await restoreSession(this._flowContext());
|
|
250
258
|
if (restored) { return restored; }
|
|
@@ -261,32 +269,28 @@ export class AuthManager {
|
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
/**
|
|
264
|
-
* Create or reconnect
|
|
272
|
+
* Create or reconnect an identity via the local HD vault.
|
|
265
273
|
*
|
|
266
274
|
* Use this when you explicitly want the vault flow, bypassing
|
|
267
|
-
* auto-detection. This is the preferred method for wallet apps
|
|
275
|
+
* auto-detection. This is the preferred method for wallet apps
|
|
276
|
+
* and CLI tools that own the identity vault directly.
|
|
268
277
|
*
|
|
269
278
|
* @param options - Vault connect options.
|
|
270
279
|
* @returns An active AuthSession.
|
|
271
280
|
* @throws If a connection attempt is already in progress.
|
|
272
|
-
* @deprecated Use {@link connectVault} instead. Will be removed in 1.0.
|
|
273
281
|
*/
|
|
274
|
-
async
|
|
275
|
-
return this.
|
|
282
|
+
async connectVault(options?: VaultConnectOptions): Promise<AuthSession> {
|
|
283
|
+
return this._withConnect(() => vaultConnect(this._flowContext(), options));
|
|
276
284
|
}
|
|
277
285
|
|
|
278
286
|
/**
|
|
279
|
-
*
|
|
287
|
+
* Restore or re-unlock the local vault from a BIP-39 recovery phrase.
|
|
280
288
|
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* @param options - Vault connect options.
|
|
286
|
-
* @returns An active AuthSession.
|
|
287
|
-
* @throws If a connection attempt is already in progress.
|
|
289
|
+
* - Fresh device: initializes the vault from the phrase and recovers remote identities.
|
|
290
|
+
* - Same local vault: verifies the phrase matches, resets the local password, and preserves data.
|
|
291
|
+
* - Different local vault: throws without clearing or replacing the existing vault.
|
|
288
292
|
*/
|
|
289
|
-
async
|
|
293
|
+
async restoreFromPhrase(options: RestoreFromPhraseOptions): Promise<AuthSession> {
|
|
290
294
|
return this._withConnect(() => vaultConnect(this._flowContext(), options));
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -305,16 +309,6 @@ export class AuthManager {
|
|
|
305
309
|
return this._withConnect(() => walletConnect(this._flowContext(), options));
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
/**
|
|
309
|
-
* Import an identity from a BIP-39 recovery phrase.
|
|
310
|
-
*
|
|
311
|
-
* This re-derives the vault and agent DID from the mnemonic,
|
|
312
|
-
* recovering the identity on this device.
|
|
313
|
-
*/
|
|
314
|
-
async importFromPhrase(options: ImportFromPhraseOptions): Promise<AuthSession> {
|
|
315
|
-
return this._withConnect(() => importFromPhrase(this._flowContext(), options));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
312
|
/**
|
|
319
313
|
* Import an identity from a PortableIdentity JSON object.
|
|
320
314
|
*
|
|
@@ -1014,6 +1008,12 @@ export class AuthManager {
|
|
|
1014
1008
|
return true;
|
|
1015
1009
|
}
|
|
1016
1010
|
|
|
1011
|
+
private _isPhraseRestore(options?: ConnectOptions): options is RestoreFromPhraseOptions {
|
|
1012
|
+
return options !== undefined
|
|
1013
|
+
&& options !== null
|
|
1014
|
+
&& typeof (options as { recoveryPhrase?: unknown }).recoveryPhrase === 'string';
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
1017
|
/**
|
|
1018
1018
|
* Run a handler-based (delegated) connect flow.
|
|
1019
1019
|
*
|
|
@@ -1093,7 +1093,7 @@ export class AuthManager {
|
|
|
1093
1093
|
*
|
|
1094
1094
|
* Replaces the 5 manual inline context constructions that were
|
|
1095
1095
|
* previously duplicated across `connect()`, `walletConnect()`,
|
|
1096
|
-
* `
|
|
1096
|
+
* `restoreFromPhrase()`, `importFromPortable()`, and `restoreSession()`.
|
|
1097
1097
|
*/
|
|
1098
1098
|
private _flowContext(): FlowContext {
|
|
1099
1099
|
return {
|
|
@@ -1113,7 +1113,7 @@ export class AuthManager {
|
|
|
1113
1113
|
*
|
|
1114
1114
|
* Consolidates the duplicated concurrency guard, `_isConnecting` flag management,
|
|
1115
1115
|
* session assignment, and state transition across `connect()`, `walletConnect()`,
|
|
1116
|
-
* `
|
|
1116
|
+
* `restoreFromPhrase()`, and `importFromPortable()`.
|
|
1117
1117
|
*
|
|
1118
1118
|
* Also short-circuits if the manager has already been shut down — using
|
|
1119
1119
|
* a closed manager would otherwise fail deep inside sync/storage with an
|
package/src/connect/import.ts
CHANGED
|
@@ -1,129 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Identity import flows.
|
|
3
3
|
*
|
|
4
|
-
* - Import from BIP-39 recovery phrase (re-derive vault + identity).
|
|
5
4
|
* - Import from PortableIdentity JSON.
|
|
6
5
|
* @module
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import type { AuthSession } from '../identity-session.js';
|
|
10
9
|
import type { FlowContext } from './lifecycle.js';
|
|
11
|
-
import type {
|
|
10
|
+
import type { ImportFromPortableOptions } from '../types.js';
|
|
12
11
|
|
|
13
12
|
import { DEFAULT_DWN_ENDPOINTS } from '../types.js';
|
|
14
13
|
import { registerWithDwnEndpoints } from '../registration.js';
|
|
15
|
-
import {
|
|
16
|
-
import { recoverIdentitiesFromRemote, registerAgentDidForSync } from './recovery.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Import (or recover) an identity from a BIP-39 recovery phrase.
|
|
20
|
-
*
|
|
21
|
-
* This re-initializes the vault with the given phrase and password,
|
|
22
|
-
* recovering the agent DID and all derived keys. If the recovery phrase
|
|
23
|
-
* was previously used to create identities, they are pulled from the
|
|
24
|
-
* remote DWN. Otherwise a new default identity is created.
|
|
25
|
-
*/
|
|
26
|
-
export async function importFromPhrase(
|
|
27
|
-
ctx: FlowContext,
|
|
28
|
-
options: ImportFromPhraseOptions,
|
|
29
|
-
): Promise<AuthSession> {
|
|
30
|
-
const { userAgent, emitter, storage } = ctx;
|
|
31
|
-
const { recoveryPhrase, password } = options;
|
|
32
|
-
const sync = options.sync ?? ctx.defaultSync;
|
|
33
|
-
const dwnEndpoints = options.dwnEndpoints ?? ctx.defaultDwnEndpoints ?? DEFAULT_DWN_ENDPOINTS;
|
|
34
|
-
|
|
35
|
-
// Initialize the vault with the recovery phrase and start the agent.
|
|
36
|
-
const isFirstLaunch = await userAgent.firstLaunch();
|
|
37
|
-
await ensureVaultReady({
|
|
38
|
-
userAgent,
|
|
39
|
-
emitter,
|
|
40
|
-
password,
|
|
41
|
-
isFirstLaunch,
|
|
42
|
-
recoveryPhrase,
|
|
43
|
-
dwnEndpoints,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Register agent DID as tenant and for sync — prerequisites for recovery.
|
|
47
|
-
if (ctx.registration) {
|
|
48
|
-
await registerWithDwnEndpoints(
|
|
49
|
-
{
|
|
50
|
-
userAgent,
|
|
51
|
-
dwnEndpoints,
|
|
52
|
-
agentDid : userAgent.agentDid.uri,
|
|
53
|
-
connectedDid : userAgent.agentDid.uri,
|
|
54
|
-
secretStore : userAgent.secrets,
|
|
55
|
-
storage,
|
|
56
|
-
},
|
|
57
|
-
ctx.registration,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
if (sync !== 'off') {
|
|
61
|
-
await registerAgentDidForSync(userAgent);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Try to recover identities from the remote DWN before falling back
|
|
65
|
-
// to creating a new one.
|
|
66
|
-
let identities = await userAgent.identity.list();
|
|
67
|
-
let identity = identities[0];
|
|
68
|
-
let isNewIdentity = false;
|
|
69
|
-
|
|
70
|
-
if (!identity && sync !== 'off') {
|
|
71
|
-
try {
|
|
72
|
-
identities = await recoverIdentitiesFromRemote({ userAgent, dwnEndpoints, registration: ctx.registration, storage });
|
|
73
|
-
identity = identities[0];
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.warn('[@enbox/auth] Seed phrase recovery failed:', err);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!identity) {
|
|
80
|
-
isNewIdentity = true;
|
|
81
|
-
identity = await createDefaultIdentity(userAgent, dwnEndpoints);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const { connectedDid, delegateDid } = resolveIdentityDids(identity);
|
|
85
|
-
|
|
86
|
-
// Register sync for the identity DID.
|
|
87
|
-
if (isNewIdentity) {
|
|
88
|
-
// New identity: register as tenant first, then for sync.
|
|
89
|
-
if (ctx.registration) {
|
|
90
|
-
await registerWithDwnEndpoints(
|
|
91
|
-
{
|
|
92
|
-
userAgent,
|
|
93
|
-
dwnEndpoints,
|
|
94
|
-
agentDid : userAgent.agentDid.uri,
|
|
95
|
-
connectedDid,
|
|
96
|
-
secretStore : userAgent.secrets,
|
|
97
|
-
storage,
|
|
98
|
-
},
|
|
99
|
-
ctx.registration,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
if (delegateDid) {
|
|
103
|
-
await registerSyncScopeForIdentity({ userAgent, connectedDid, delegateDid });
|
|
104
|
-
} else if (sync !== 'off') {
|
|
105
|
-
await registerSyncScopeForIdentity({ userAgent, connectedDid });
|
|
106
|
-
}
|
|
107
|
-
} else if (delegateDid) {
|
|
108
|
-
// Pre-existing delegate identity: repair sync scope so revoked
|
|
109
|
-
// grants don't remain in a stale registration.
|
|
110
|
-
await registerSyncScopeForIdentity({ userAgent, connectedDid, delegateDid });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Start sync.
|
|
114
|
-
await startSyncIfEnabled(userAgent, sync);
|
|
115
|
-
|
|
116
|
-
// Persist session info, build AuthSession, and emit lifecycle events.
|
|
117
|
-
return finalizeSession({
|
|
118
|
-
userAgent,
|
|
119
|
-
emitter,
|
|
120
|
-
storage,
|
|
121
|
-
connectedDid,
|
|
122
|
-
delegateDid,
|
|
123
|
-
identityName : identity.metadata.name,
|
|
124
|
-
identityConnectedDid : identity.metadata.connectedDid,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
14
|
+
import { finalizeSession, registerSyncScopeForIdentity, resolveIdentityDids, startSyncIfEnabled } from './lifecycle.js';
|
|
127
15
|
|
|
128
16
|
/**
|
|
129
17
|
* Import an identity from a PortableIdentity JSON object.
|
package/src/connect/lifecycle.ts
CHANGED
|
@@ -25,9 +25,10 @@ import { Convert } from '@enbox/common';
|
|
|
25
25
|
import type { GenericMessage } from '@enbox/dwn-sdk-js';
|
|
26
26
|
|
|
27
27
|
import { DataStream, PermissionsProtocol } from '@enbox/dwn-sdk-js';
|
|
28
|
-
import { DwnInterface, DwnPermissionGrant, KeyDeliveryProtocolDefinition } from '@enbox/agent';
|
|
28
|
+
import { DwnInterface, DwnPermissionGrant, HdIdentityVaultRecoveryPhraseMismatchError, KeyDeliveryProtocolDefinition } from '@enbox/agent';
|
|
29
29
|
|
|
30
30
|
import { AuthSession } from '../identity-session.js';
|
|
31
|
+
import { RecoveryPhraseMismatchError } from '../errors.js';
|
|
31
32
|
import { DEFAULT_DWN_ENDPOINTS, INSECURE_DEFAULT_PASSWORD, STORAGE_KEYS } from '../types.js';
|
|
32
33
|
|
|
33
34
|
// ─── FlowContext ─────────────────────────────────────────────────
|
|
@@ -109,7 +110,7 @@ export async function resolvePassword(
|
|
|
109
110
|
// ─── ensureVaultReady ────────────────────────────────────────────
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
|
-
* Initialize
|
|
113
|
+
* Initialize or recover the vault, start the agent, then emit `vault-unlocked`.
|
|
113
114
|
*
|
|
114
115
|
* This consolidates the 5 copies of:
|
|
115
116
|
* ```ts
|
|
@@ -118,6 +119,9 @@ export async function resolvePassword(
|
|
|
118
119
|
* emitter.emit('vault-unlocked', {});
|
|
119
120
|
* ```
|
|
120
121
|
*
|
|
122
|
+
* When `recoveryPhrase` is supplied for an already-initialized vault, the phrase is verified
|
|
123
|
+
* against the stored agent DID and the vault password is reset without replacing local data.
|
|
124
|
+
*
|
|
121
125
|
* @returns The recovery phrase if the vault was just initialized, otherwise `undefined`.
|
|
122
126
|
*
|
|
123
127
|
* @internal
|
|
@@ -139,6 +143,18 @@ export async function ensureVaultReady(params: {
|
|
|
139
143
|
recoveryPhrase : params.recoveryPhrase,
|
|
140
144
|
dwnEndpoints : params.dwnEndpoints,
|
|
141
145
|
});
|
|
146
|
+
} else if (params.recoveryPhrase) {
|
|
147
|
+
try {
|
|
148
|
+
await userAgent.vault.resetPasswordWithRecoveryPhrase({
|
|
149
|
+
recoveryPhrase: params.recoveryPhrase,
|
|
150
|
+
password,
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof HdIdentityVaultRecoveryPhraseMismatchError) {
|
|
154
|
+
throw new RecoveryPhraseMismatchError();
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
142
158
|
}
|
|
143
159
|
|
|
144
160
|
await userAgent.start({ password });
|
|
@@ -184,7 +200,7 @@ export async function startSyncIfEnabled(
|
|
|
184
200
|
* encryption keys, and a DWN service endpoint.
|
|
185
201
|
*
|
|
186
202
|
* This consolidates the identical identity creation block that was
|
|
187
|
-
* duplicated
|
|
203
|
+
* duplicated across vault recovery and identity import flows.
|
|
188
204
|
*
|
|
189
205
|
* @internal
|
|
190
206
|
*/
|
package/src/connect/vault.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { VaultConnectOptions } from '../types.js';
|
|
|
16
16
|
import { applyLocalDwnDiscovery } from '../discovery.js';
|
|
17
17
|
import { DEFAULT_DWN_ENDPOINTS } from '../types.js';
|
|
18
18
|
import { registerWithDwnEndpoints } from '../registration.js';
|
|
19
|
-
import { createDefaultIdentity, ensureVaultReady, finalizeSession, resolveIdentityDids, resolvePassword, startSyncIfEnabled } from './lifecycle.js';
|
|
19
|
+
import { createDefaultIdentity, ensureVaultReady, finalizeSession, registerSyncScopeForIdentity, resolveIdentityDids, resolvePassword, startSyncIfEnabled } from './lifecycle.js';
|
|
20
20
|
import { recoverIdentitiesFromRemote, registerAgentDidForSync } from './recovery.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -26,8 +26,8 @@ import { recoverIdentitiesFromRemote, registerAgentDidForSync } from './recovery
|
|
|
26
26
|
* `options.createIdentity: true`.
|
|
27
27
|
* - On subsequent launches: unlocks the vault and reconnects to the existing identity.
|
|
28
28
|
* - On recovery: when `recoveryPhrase` is provided on a fresh vault, pulls
|
|
29
|
-
* identities and their data from the remote DWN before
|
|
30
|
-
* identity
|
|
29
|
+
* identities and their data from the remote DWN before optionally creating
|
|
30
|
+
* a default identity.
|
|
31
31
|
*
|
|
32
32
|
* When no identities exist and `createIdentity` is not `true`, the session
|
|
33
33
|
* is returned with the **agent DID** as the connected DID. This allows apps to
|
|
@@ -88,9 +88,9 @@ export async function vaultConnect(
|
|
|
88
88
|
let identity = identities[0];
|
|
89
89
|
let isNewIdentity = false;
|
|
90
90
|
|
|
91
|
-
// Seed phrase recovery: when a recovery phrase was provided
|
|
92
|
-
//
|
|
93
|
-
if (!identity &&
|
|
91
|
+
// Seed phrase recovery: when a recovery phrase was provided and no identities exist locally,
|
|
92
|
+
// pull them from the remote DWN before deciding whether to create a new identity.
|
|
93
|
+
if (!identity && options.recoveryPhrase && sync !== 'off') {
|
|
94
94
|
try {
|
|
95
95
|
identities = await recoverIdentitiesFromRemote({ userAgent, dwnEndpoints, registration: ctx.registration, storage });
|
|
96
96
|
identity = identities[0];
|
|
@@ -99,7 +99,7 @@ export async function vaultConnect(
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// Create a default identity if none were found or recovered.
|
|
102
|
+
// Create a default identity if none were found or recovered and the caller asked for one.
|
|
103
103
|
if (!identity && shouldCreateIdentity) {
|
|
104
104
|
isNewIdentity = true;
|
|
105
105
|
identity = await createDefaultIdentity(userAgent, dwnEndpoints, options.metadata?.name ?? 'Default');
|
|
@@ -108,13 +108,9 @@ export async function vaultConnect(
|
|
|
108
108
|
// When no identity exists (createIdentity: false on first launch), use the
|
|
109
109
|
// agent DID as the session's connected DID. The session is still valid but
|
|
110
110
|
// operates in the agent's context rather than a user identity's context.
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const delegateDid = identity
|
|
116
|
-
? resolveIdentityDids(identity).delegateDid
|
|
117
|
-
: undefined;
|
|
111
|
+
const identityDids = identity ? resolveIdentityDids(identity) : undefined;
|
|
112
|
+
const connectedDid = identityDids?.connectedDid ?? userAgent.agentDid.uri;
|
|
113
|
+
const delegateDid = identityDids?.delegateDid;
|
|
118
114
|
|
|
119
115
|
// Register the new identity DID as a tenant and for sync.
|
|
120
116
|
// Tenant registration must come before sync registration — with live
|
|
@@ -134,10 +130,11 @@ export async function vaultConnect(
|
|
|
134
130
|
);
|
|
135
131
|
}
|
|
136
132
|
if (isNewIdentity && sync !== 'off') {
|
|
137
|
-
await
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
await registerSyncScopeForIdentity({ userAgent, connectedDid, delegateDid });
|
|
134
|
+
} else if (!isNewIdentity && delegateDid) {
|
|
135
|
+
// Persisted delegate identities need their sync scope refreshed from
|
|
136
|
+
// current grants so revoked protocols do not keep syncing after restore.
|
|
137
|
+
await registerSyncScopeForIdentity({ userAgent, connectedDid, delegateDid });
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
// Start sync.
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class RecoveryPhraseMismatchError extends Error {
|
|
2
|
+
public readonly code = 'RECOVERY_PHRASE_MISMATCH';
|
|
3
|
+
|
|
4
|
+
constructor(message = 'Recovery phrase does not match the initialized vault.') {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'RecoveryPhraseMismatchError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isRecoveryPhraseMismatchError(error: unknown): error is RecoveryPhraseMismatchError {
|
|
11
|
+
return error instanceof RecoveryPhraseMismatchError;
|
|
12
|
+
}
|
package/src/identity-session.ts
CHANGED
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.connectVault({ password: userPin });
|
|
13
|
+
* const session = await auth.connectVault({ password: userPin, createIdentity: true });
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
16
|
* @example Dapp with browser connect handler
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
export { AuthManager } from './auth-manager.js';
|
|
32
32
|
export { AuthSession } from './identity-session.js';
|
|
33
33
|
export { AuthEventEmitter } from './events.js';
|
|
34
|
+
export { RecoveryPhraseMismatchError, isRecoveryPhraseMismatchError } from './errors.js';
|
|
34
35
|
|
|
35
36
|
// Password providers
|
|
36
37
|
export { PasswordProvider } from './password-provider.js';
|
|
@@ -86,9 +87,7 @@ export type {
|
|
|
86
87
|
HeadlessConnectOptions,
|
|
87
88
|
IdentityInfo,
|
|
88
89
|
IdentityVaultBackup,
|
|
89
|
-
ImportFromPhraseOptions,
|
|
90
90
|
ImportFromPortableOptions,
|
|
91
|
-
LocalConnectOptions,
|
|
92
91
|
LocalDwnStrategy,
|
|
93
92
|
VaultConnectOptions,
|
|
94
93
|
Permission,
|
|
@@ -98,6 +97,7 @@ export type {
|
|
|
98
97
|
ProviderAuthResult,
|
|
99
98
|
RegistrationOptions,
|
|
100
99
|
RegistrationTokenData,
|
|
100
|
+
RestoreFromPhraseOptions,
|
|
101
101
|
RestoreSessionOptions,
|
|
102
102
|
ShutdownOptions,
|
|
103
103
|
StorageAdapter,
|
package/src/types.ts
CHANGED
|
@@ -403,7 +403,7 @@ export interface VaultConnectOptions {
|
|
|
403
403
|
/** Vault password (overrides manager default). */
|
|
404
404
|
password?: string;
|
|
405
405
|
|
|
406
|
-
/**
|
|
406
|
+
/** Initialize or recover the vault from an existing BIP-39 recovery phrase. */
|
|
407
407
|
recoveryPhrase?: string;
|
|
408
408
|
|
|
409
409
|
/** Override manager default sync interval. */
|
|
@@ -434,8 +434,18 @@ export interface VaultConnectOptions {
|
|
|
434
434
|
createIdentity?: boolean;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
/**
|
|
438
|
-
export
|
|
437
|
+
/** Options for {@link AuthManager.restoreFromPhrase}. */
|
|
438
|
+
export interface RestoreFromPhraseOptions extends Omit<VaultConnectOptions, 'password' | 'recoveryPhrase'> {
|
|
439
|
+
/** The BIP-39 recovery phrase for the existing or remote wallet vault. */
|
|
440
|
+
recoveryPhrase: string;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Password to protect the restored vault locally.
|
|
444
|
+
*
|
|
445
|
+
* If the local vault already belongs to this phrase, this becomes the new local unlock password.
|
|
446
|
+
*/
|
|
447
|
+
password: string;
|
|
448
|
+
}
|
|
439
449
|
|
|
440
450
|
// ─── DWeb Connect ────────────────────────────────────────────────
|
|
441
451
|
|
|
@@ -508,16 +518,19 @@ export interface HandlerConnectOptions {
|
|
|
508
518
|
*
|
|
509
519
|
* `connect()` routes to the appropriate flow based on the options:
|
|
510
520
|
*
|
|
521
|
+
* - **Recovery phrase restore** (wallets / CLI): triggered first when
|
|
522
|
+
* `recoveryPhrase` is provided.
|
|
523
|
+
*
|
|
511
524
|
* - **Handler-based connect** (dapps): triggered when `protocols` or
|
|
512
|
-
* `connectHandler` is provided
|
|
513
|
-
* for credential acquisition.
|
|
525
|
+
* `connectHandler` is provided and no `recoveryPhrase` is present.
|
|
526
|
+
* Delegates to the connect handler for credential acquisition.
|
|
514
527
|
*
|
|
515
528
|
* - **Vault connect** (wallets / CLI): triggered when `password`,
|
|
516
529
|
* `createIdentity`, or `recoveryPhrase` is provided.
|
|
517
530
|
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
531
|
+
* `connect()` first attempts to restore a previous session from storage
|
|
532
|
+
* unless `recoveryPhrase` is provided. Recovery is treated as an explicit
|
|
533
|
+
* user action and routes directly to the phrase restore flow.
|
|
521
534
|
*/
|
|
522
535
|
export type ConnectOptions = HandlerConnectOptions | VaultConnectOptions;
|
|
523
536
|
|
|
@@ -552,21 +565,6 @@ export interface WalletConnectOptions {
|
|
|
552
565
|
sync?: SyncOption;
|
|
553
566
|
}
|
|
554
567
|
|
|
555
|
-
/** Options for {@link AuthManager.importFromPhrase}. */
|
|
556
|
-
export interface ImportFromPhraseOptions {
|
|
557
|
-
/** The BIP-39 recovery phrase. */
|
|
558
|
-
recoveryPhrase: string;
|
|
559
|
-
|
|
560
|
-
/** Password to protect the vault. */
|
|
561
|
-
password: string;
|
|
562
|
-
|
|
563
|
-
/** Override manager default sync interval. */
|
|
564
|
-
sync?: SyncOption;
|
|
565
|
-
|
|
566
|
-
/** Override manager default DWN endpoints. */
|
|
567
|
-
dwnEndpoints?: string[];
|
|
568
|
-
}
|
|
569
|
-
|
|
570
568
|
/** Options for {@link AuthManager.importFromPortable}. */
|
|
571
569
|
export interface ImportFromPortableOptions {
|
|
572
570
|
/** The portable identity JSON to import. */
|