@enbox/auth 0.3.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.
Files changed (66) hide show
  1. package/dist/esm/auth-manager.js +496 -0
  2. package/dist/esm/auth-manager.js.map +1 -0
  3. package/dist/esm/events.js +65 -0
  4. package/dist/esm/events.js.map +1 -0
  5. package/dist/esm/flows/dwn-discovery.js +281 -0
  6. package/dist/esm/flows/dwn-discovery.js.map +1 -0
  7. package/dist/esm/flows/dwn-registration.js +122 -0
  8. package/dist/esm/flows/dwn-registration.js.map +1 -0
  9. package/dist/esm/flows/import-identity.js +175 -0
  10. package/dist/esm/flows/import-identity.js.map +1 -0
  11. package/dist/esm/flows/local-connect.js +141 -0
  12. package/dist/esm/flows/local-connect.js.map +1 -0
  13. package/dist/esm/flows/session-restore.js +109 -0
  14. package/dist/esm/flows/session-restore.js.map +1 -0
  15. package/dist/esm/flows/wallet-connect.js +199 -0
  16. package/dist/esm/flows/wallet-connect.js.map +1 -0
  17. package/dist/esm/identity-session.js +33 -0
  18. package/dist/esm/identity-session.js.map +1 -0
  19. package/dist/esm/index.js +50 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/storage/storage.js +152 -0
  22. package/dist/esm/storage/storage.js.map +1 -0
  23. package/dist/esm/types.js +30 -0
  24. package/dist/esm/types.js.map +1 -0
  25. package/dist/esm/vault/vault-manager.js +95 -0
  26. package/dist/esm/vault/vault-manager.js.map +1 -0
  27. package/dist/types/auth-manager.d.ts +176 -0
  28. package/dist/types/auth-manager.d.ts.map +1 -0
  29. package/dist/types/events.d.ts +36 -0
  30. package/dist/types/events.d.ts.map +1 -0
  31. package/dist/types/flows/dwn-discovery.d.ts +157 -0
  32. package/dist/types/flows/dwn-discovery.d.ts.map +1 -0
  33. package/dist/types/flows/dwn-registration.d.ts +39 -0
  34. package/dist/types/flows/dwn-registration.d.ts.map +1 -0
  35. package/dist/types/flows/import-identity.d.ts +35 -0
  36. package/dist/types/flows/import-identity.d.ts.map +1 -0
  37. package/dist/types/flows/local-connect.d.ts +29 -0
  38. package/dist/types/flows/local-connect.d.ts.map +1 -0
  39. package/dist/types/flows/session-restore.d.ts +27 -0
  40. package/dist/types/flows/session-restore.d.ts.map +1 -0
  41. package/dist/types/flows/wallet-connect.d.ts +44 -0
  42. package/dist/types/flows/wallet-connect.d.ts.map +1 -0
  43. package/dist/types/identity-session.d.ts +52 -0
  44. package/dist/types/identity-session.d.ts.map +1 -0
  45. package/dist/types/index.d.ts +45 -0
  46. package/dist/types/index.d.ts.map +1 -0
  47. package/dist/types/storage/storage.d.ts +54 -0
  48. package/dist/types/storage/storage.d.ts.map +1 -0
  49. package/dist/types/types.d.ts +312 -0
  50. package/dist/types/types.d.ts.map +1 -0
  51. package/dist/types/vault/vault-manager.d.ts +57 -0
  52. package/dist/types/vault/vault-manager.d.ts.map +1 -0
  53. package/package.json +71 -0
  54. package/src/auth-manager.ts +569 -0
  55. package/src/events.ts +66 -0
  56. package/src/flows/dwn-discovery.ts +300 -0
  57. package/src/flows/dwn-registration.ts +157 -0
  58. package/src/flows/import-identity.ts +217 -0
  59. package/src/flows/local-connect.ts +171 -0
  60. package/src/flows/session-restore.ts +135 -0
  61. package/src/flows/wallet-connect.ts +225 -0
  62. package/src/identity-session.ts +65 -0
  63. package/src/index.ts +89 -0
  64. package/src/storage/storage.ts +136 -0
  65. package/src/types.ts +388 -0
  66. package/src/vault/vault-manager.ts +89 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Storage adapter implementations for session persistence.
3
+ * @module
4
+ */
5
+
6
+ import { Level } from 'level';
7
+
8
+ import type { StorageAdapter } from '../types.js';
9
+
10
+ /**
11
+ * Browser storage adapter backed by `localStorage`.
12
+ *
13
+ * All keys are prefixed to avoid collisions with other localStorage users.
14
+ */
15
+ export class BrowserStorage implements StorageAdapter {
16
+ private readonly _prefix: string;
17
+
18
+ constructor(prefix = 'enbox:') {
19
+ this._prefix = prefix;
20
+ }
21
+
22
+ async get(key: string): Promise<string | null> {
23
+ return globalThis.localStorage.getItem(this._prefix + key);
24
+ }
25
+
26
+ async set(key: string, value: string): Promise<void> {
27
+ globalThis.localStorage.setItem(this._prefix + key, value);
28
+ }
29
+
30
+ async remove(key: string): Promise<void> {
31
+ globalThis.localStorage.removeItem(this._prefix + key);
32
+ }
33
+
34
+ async clear(): Promise<void> {
35
+ const keysToRemove: string[] = [];
36
+ for (let i = 0; i < globalThis.localStorage.length; i++) {
37
+ const key = globalThis.localStorage.key(i);
38
+ if (key?.startsWith(this._prefix)) {
39
+ keysToRemove.push(key);
40
+ }
41
+ }
42
+ for (const key of keysToRemove) {
43
+ globalThis.localStorage.removeItem(key);
44
+ }
45
+ }
46
+ }
47
+
48
+ /**
49
+ * In-memory storage adapter for testing or environments without persistence.
50
+ */
51
+ export class MemoryStorage implements StorageAdapter {
52
+ private readonly _store = new Map<string, string>();
53
+
54
+ async get(key: string): Promise<string | null> {
55
+ return this._store.get(key) ?? null;
56
+ }
57
+
58
+ async set(key: string, value: string): Promise<void> {
59
+ this._store.set(key, value);
60
+ }
61
+
62
+ async remove(key: string): Promise<void> {
63
+ this._store.delete(key);
64
+ }
65
+
66
+ async clear(): Promise<void> {
67
+ this._store.clear();
68
+ }
69
+ }
70
+
71
+ /**
72
+ * LevelDB-backed storage adapter for Node / CLI environments.
73
+ *
74
+ * Uses the `level` package (v8), which selects `classic-level` (LevelDB)
75
+ * in Node and `browser-level` (IndexedDB) in browsers. Level auto-opens
76
+ * on construction and queues operations until ready, so no explicit
77
+ * `open()` call is required.
78
+ */
79
+ export class LevelStorage implements StorageAdapter {
80
+ private readonly _db: Level<string, string>;
81
+
82
+ constructor(dataPath = 'DATA/AGENT/AUTH_STORE') {
83
+ this._db = new Level<string, string>(dataPath);
84
+ }
85
+
86
+ async get(key: string): Promise<string | null> {
87
+ try {
88
+ return await this._db.get(key);
89
+ } catch (error) {
90
+ const e = error as { code?: string };
91
+ if (e.code === 'LEVEL_NOT_FOUND') {
92
+ return null;
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ async set(key: string, value: string): Promise<void> {
99
+ await this._db.put(key, value);
100
+ }
101
+
102
+ async remove(key: string): Promise<void> {
103
+ try {
104
+ await this._db.del(key);
105
+ } catch (error) {
106
+ const e = error as { code?: string };
107
+ // Silently ignore deleting a key that doesn't exist.
108
+ if (e.code !== 'LEVEL_NOT_FOUND') {
109
+ throw error;
110
+ }
111
+ }
112
+ }
113
+
114
+ async clear(): Promise<void> {
115
+ await this._db.clear();
116
+ }
117
+
118
+ /** Close the underlying LevelDB database. */
119
+ async close(): Promise<void> {
120
+ await this._db.close();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Detect the runtime environment and return an appropriate default storage adapter.
126
+ *
127
+ * - If `localStorage` is available → `BrowserStorage`
128
+ * - Otherwise → `LevelStorage` (persistent on disk via LevelDB)
129
+ */
130
+ export function createDefaultStorage(): StorageAdapter {
131
+ if (typeof globalThis.localStorage !== 'undefined') {
132
+ return new BrowserStorage();
133
+ }
134
+
135
+ return new LevelStorage();
136
+ }
package/src/types.ts ADDED
@@ -0,0 +1,388 @@
1
+ /**
2
+ * @module @enbox/auth
3
+ * Public types for the authentication and identity management SDK.
4
+ */
5
+
6
+ import type { ConnectPermissionRequest, EnboxUserAgent, HdIdentityVault, LocalDwnStrategy, PortableIdentity } from '@enbox/agent';
7
+
8
+ // Re-export types that consumers will need
9
+ export type { ConnectPermissionRequest, HdIdentityVault, IdentityVaultBackup, LocalDwnStrategy, PortableIdentity } from '@enbox/agent';
10
+
11
+ // Re-export EnboxUserAgent so consumers don't need a direct @enbox/agent dep
12
+ export type { EnboxUserAgent } from '@enbox/agent';
13
+
14
+ // ─── Sync ────────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Controls DWN synchronisation behaviour.
18
+ *
19
+ * - `'off'` — Sync disabled entirely.
20
+ * - An interval string such as `'30s'`, `'2m'`, `'1h'` — Poll mode at the
21
+ * specified interval.
22
+ * - `undefined` (omitted) — Live WebSocket sync (default).
23
+ */
24
+ export type SyncOption = 'off' | `${number}${'s' | 'm' | 'h'}`;
25
+
26
+ // ─── Auth State Machine ─────────────────────────────────────────
27
+
28
+ /**
29
+ * The possible states of the auth manager.
30
+ *
31
+ * State transitions:
32
+ * ```
33
+ * uninitialized → locked → unlocked → connected
34
+ * ↑ ↑ │
35
+ * └──────────┴──────────┘ (disconnect / lock)
36
+ * ```
37
+ */
38
+ export type AuthState =
39
+ | 'uninitialized' // No vault exists, no identities
40
+ | 'locked' // Vault exists but is locked (password required)
41
+ | 'unlocked' // Vault unlocked, no active session
42
+ | 'connected'; // Active session with an identity
43
+
44
+ // ─── Events ──────────────────────────────────────────────────────
45
+
46
+ /** All event names emitted by the auth manager. */
47
+ export type AuthEvent =
48
+ | 'state-change'
49
+ | 'session-start'
50
+ | 'session-end'
51
+ | 'identity-added'
52
+ | 'identity-removed'
53
+ | 'vault-locked'
54
+ | 'vault-unlocked'
55
+ | 'local-dwn-available'
56
+ | 'local-dwn-unavailable';
57
+
58
+ /** Payload type for each event, keyed by event name. */
59
+ export interface AuthEventMap {
60
+ 'state-change': { previous: AuthState; current: AuthState };
61
+ 'session-start': { session: AuthSessionInfo };
62
+ 'session-end': { did: string };
63
+ 'identity-added': { identity: IdentityInfo };
64
+ 'identity-removed': { didUri: string };
65
+ 'vault-locked': Record<string, never>;
66
+ 'vault-unlocked': Record<string, never>;
67
+ /** Emitted when a local DWN server is discovered and validated. */
68
+ 'local-dwn-available': { endpoint: string };
69
+ /** Emitted when no local DWN server could be discovered or a previously known one is no longer reachable. */
70
+ 'local-dwn-unavailable': Record<string, never>;
71
+ }
72
+
73
+ /** A type-safe event handler for a specific event. */
74
+ export type AuthEventHandler<E extends AuthEvent = AuthEvent> =
75
+ (payload: AuthEventMap[E]) => void;
76
+
77
+ // ─── Identity ────────────────────────────────────────────────────
78
+
79
+ /** Lightweight metadata about a stored identity. */
80
+ export interface IdentityInfo {
81
+ /** The DID URI for this identity. */
82
+ didUri: string;
83
+
84
+ /** Human-readable name. */
85
+ name: string;
86
+
87
+ /**
88
+ * Present when this identity is a delegate of another DID
89
+ * (i.e. connected via wallet connect).
90
+ */
91
+ connectedDid?: string;
92
+ }
93
+
94
+ /** Serializable session info for the `session-start` event. */
95
+ export interface AuthSessionInfo {
96
+ did: string;
97
+ delegateDid?: string;
98
+ identity: IdentityInfo;
99
+ }
100
+
101
+ // ─── Registration ────────────────────────────────────────────────
102
+
103
+ /** Parameters passed to the onProviderAuthRequired callback. */
104
+ export interface ProviderAuthParams {
105
+ /** Full authorize URL to open in a browser (query params already appended). */
106
+ authorizeUrl: string;
107
+ /** The DWN endpoint URL this auth is for (informational). */
108
+ dwnEndpoint: string;
109
+ /** CSRF nonce — the provider will return this unchanged in the redirect. */
110
+ state: string;
111
+ }
112
+
113
+ /** Result returned by the app after the user completes provider auth. */
114
+ export interface ProviderAuthResult {
115
+ /** Authorization code from the provider's redirect. */
116
+ code: string;
117
+ /** Must match the state from ProviderAuthParams (CSRF validation). */
118
+ state: string;
119
+ }
120
+
121
+ /** Persisted registration token data for a DWN endpoint. */
122
+ export interface RegistrationTokenData {
123
+ /** Opaque registration token for POST /registration. */
124
+ registrationToken: string;
125
+ /** Refresh token for obtaining new registration tokens. */
126
+ refreshToken?: string;
127
+ /** Unix timestamp (ms) when the token expires. Undefined = never expires. */
128
+ expiresAt?: number;
129
+ /** Provider's token exchange URL (needed for code exchange). */
130
+ tokenUrl: string;
131
+ /** Provider's refresh URL (needed for token refresh). */
132
+ refreshUrl?: string;
133
+ }
134
+
135
+ // ─── Options ─────────────────────────────────────────────────────
136
+
137
+ /**
138
+ * DWN registration configuration.
139
+ *
140
+ * When provided, the agent DID and connected DID will be registered with
141
+ * DWN endpoints after identity creation. Supports two paths:
142
+ *
143
+ * 1. **Provider auth** (`provider-auth-v0`) — the DWN endpoint requires
144
+ * OAuth-style auth. If {@link onProviderAuthRequired} is provided and
145
+ * the server advertises provider auth, the app handles the auth flow.
146
+ * 2. **Proof of Work** (default fallback) — the DWN endpoint requires
147
+ * solving a PoW challenge to register.
148
+ */
149
+ export interface RegistrationOptions {
150
+ /** Called when all DWN registrations complete successfully. */
151
+ onSuccess: () => void;
152
+
153
+ /** Called when any DWN registration fails. */
154
+ onFailure: (error: unknown) => void;
155
+
156
+ /**
157
+ * Called when a DWN endpoint requires provider auth (`'provider-auth-v0'`).
158
+ *
159
+ * The app should open the `authorizeUrl` in a browser, capture the
160
+ * redirect with the auth code, and return the result. If not provided,
161
+ * endpoints requiring provider auth fall back to PoW registration.
162
+ */
163
+ onProviderAuthRequired?: (params: ProviderAuthParams) => Promise<ProviderAuthResult>;
164
+
165
+ /**
166
+ * Pre-existing registration tokens from a previous session, keyed by
167
+ * DWN endpoint URL. If a valid (non-expired) token exists for an
168
+ * endpoint, it is used directly without re-running the auth flow.
169
+ */
170
+ registrationTokens?: Record<string, RegistrationTokenData>;
171
+
172
+ /**
173
+ * Called when new or refreshed registration tokens are obtained.
174
+ * The app should persist these for future sessions.
175
+ */
176
+ onRegistrationTokens?: (tokens: Record<string, RegistrationTokenData>) => void;
177
+ }
178
+
179
+ /** Options for {@link AuthManager.create}. */
180
+ export interface AuthManagerOptions {
181
+ /**
182
+ * Provide a pre-built {@link EnboxUserAgent} instance.
183
+ *
184
+ * When provided, `dataPath`, `agentVault`, and `localDwnStrategy` are
185
+ * ignored — the agent is used as-is. This is the escape hatch for
186
+ * advanced scenarios like custom DWN stores (e.g., SQLite-backed DWN).
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const agent = await EnboxUserAgent.create({ dwnApi: myCustomDwnApi });
191
+ * const auth = await AuthManager.create({ agent });
192
+ * ```
193
+ */
194
+ agent?: EnboxUserAgent;
195
+
196
+ /**
197
+ * Provide a custom {@link HdIdentityVault} implementation.
198
+ * Defaults to a LevelDB-backed vault with PBES2-HS512+A256KW encryption.
199
+ * Ignored when `agent` is provided.
200
+ */
201
+ agentVault?: HdIdentityVault;
202
+
203
+ /**
204
+ * Controls local DWN discovery behavior for remote-target DWN sends/sync.
205
+ * `'off'` (default) disables local probing, `'prefer'` tries local first
206
+ * then falls back to DID-document endpoints, `'only'` requires a local server.
207
+ * Ignored when `agent` is provided.
208
+ */
209
+ localDwnStrategy?: LocalDwnStrategy;
210
+
211
+ /**
212
+ * Data path for agent storage.
213
+ * - Browser default: `'DATA/AGENT'`
214
+ * - CLI default: `'~/.enbox'`
215
+ *
216
+ * Ignored when `agent` is provided.
217
+ */
218
+ dataPath?: string;
219
+
220
+ /** Storage adapter for session persistence. Auto-detected if not provided. */
221
+ storage?: StorageAdapter;
222
+
223
+ /**
224
+ * Default password for vault operations.
225
+ * If not provided, an insecure default is used (with a console warning).
226
+ */
227
+ password?: string;
228
+
229
+ /**
230
+ * Sync interval for DWN synchronization.
231
+ * - `'off'` — disable sync
232
+ * - `'15s'`, `'1m'`, etc. — poll at interval
233
+ * - `undefined` — live WebSocket sync
234
+ */
235
+ sync?: SyncOption;
236
+
237
+ /** Default DWN endpoints for new identities. */
238
+ dwnEndpoints?: string[];
239
+
240
+ /** DWN registration configuration. */
241
+ registration?: RegistrationOptions;
242
+ }
243
+
244
+ /** Options for {@link AuthManager.connect}. */
245
+ export interface LocalConnectOptions {
246
+ /** Vault password (overrides manager default). */
247
+ password?: string;
248
+
249
+ /** Re-derive identity from an existing BIP-39 recovery phrase. */
250
+ recoveryPhrase?: string;
251
+
252
+ /** Override manager default sync interval. */
253
+ sync?: SyncOption;
254
+
255
+ /** Override manager default DWN endpoints. */
256
+ dwnEndpoints?: string[];
257
+
258
+ /** Identity metadata. */
259
+ metadata?: { name?: string };
260
+ }
261
+
262
+ /** Options for {@link AuthManager.walletConnect}. */
263
+ export interface WalletConnectOptions {
264
+ /** Display name shown in the wallet during the connect flow. */
265
+ displayName: string;
266
+
267
+ /** URL of the connect relay server. */
268
+ connectServerUrl: string;
269
+
270
+ /** Wallet URI scheme. Defaults to `'web5://connect'`. */
271
+ walletUri?: string;
272
+
273
+ /**
274
+ * Protocol permission requests for the wallet connect flow.
275
+ *
276
+ * Each entry is a `ConnectPermissionRequest` from `@enbox/agent` containing
277
+ * a `protocolDefinition` and `permissionScopes`. Use
278
+ * `WalletConnect.createPermissionRequestForProtocol()` to build these.
279
+ */
280
+ permissionRequests: ConnectPermissionRequest[];
281
+
282
+ /** Called when the wallet URI is ready (render as QR code). */
283
+ onWalletUriReady: (uri: string) => void;
284
+
285
+ /** Called to collect the PIN from the user. */
286
+ validatePin: () => Promise<string>;
287
+
288
+ /** Override manager default sync interval. */
289
+ sync?: SyncOption;
290
+ }
291
+
292
+ /** Options for {@link AuthManager.importFromPhrase}. */
293
+ export interface ImportFromPhraseOptions {
294
+ /** The BIP-39 recovery phrase. */
295
+ recoveryPhrase: string;
296
+
297
+ /** Password to protect the vault. */
298
+ password: string;
299
+
300
+ /** Override manager default sync interval. */
301
+ sync?: SyncOption;
302
+
303
+ /** Override manager default DWN endpoints. */
304
+ dwnEndpoints?: string[];
305
+ }
306
+
307
+ /** Options for {@link AuthManager.importFromPortable}. */
308
+ export interface ImportFromPortableOptions {
309
+ /** The portable identity JSON to import. */
310
+ portableIdentity: PortableIdentity;
311
+
312
+ /** Override manager default sync interval. */
313
+ sync?: SyncOption;
314
+ }
315
+
316
+ /** Options for {@link AuthManager.restoreSession}. */
317
+ export interface RestoreSessionOptions {
318
+ /** Password to unlock the vault (needed if vault is locked). */
319
+ password?: string;
320
+ }
321
+
322
+ /** Options for {@link AuthManager.disconnect}. */
323
+ export interface DisconnectOptions {
324
+ /**
325
+ * If `true`, performs a nuclear wipe: clears all localStorage keys,
326
+ * deletes all IndexedDB databases, and removes persisted session data.
327
+ * Default: `false` (clean disconnect — keeps vault and identities).
328
+ */
329
+ clearStorage?: boolean;
330
+
331
+ /**
332
+ * Milliseconds to wait for pending sync operations before disconnecting.
333
+ * Default: `2000`.
334
+ */
335
+ timeout?: number;
336
+ }
337
+
338
+ // ─── Storage ─────────────────────────────────────────────────────
339
+
340
+ /**
341
+ * Platform-agnostic key-value storage adapter for session persistence.
342
+ * Implementations are provided for browser (localStorage) and CLI (file system).
343
+ */
344
+ export interface StorageAdapter {
345
+ /** Get a value by key. Returns `null` if not found. */
346
+ get(key: string): Promise<string | null>;
347
+
348
+ /** Set a key-value pair. */
349
+ set(key: string, value: string): Promise<void>;
350
+
351
+ /** Remove a key. */
352
+ remove(key: string): Promise<void>;
353
+
354
+ /** Clear all stored data. */
355
+ clear(): Promise<void>;
356
+ }
357
+
358
+ // ─── Internal helpers ────────────────────────────────────────────
359
+
360
+ /** The insecure default password used when none is provided. */
361
+ export const INSECURE_DEFAULT_PASSWORD = 'insecure-static-phrase';
362
+
363
+ /**
364
+ * Storage keys used by the auth manager for session persistence.
365
+ * @internal
366
+ */
367
+ export const STORAGE_KEYS = {
368
+ /** Whether a session was previously established. */
369
+ PREVIOUSLY_CONNECTED: 'enbox:auth:previouslyConnected',
370
+
371
+ /** The DID URI of the last active identity. */
372
+ ACTIVE_IDENTITY: 'enbox:auth:activeIdentity',
373
+
374
+ /** The delegate DID URI (for wallet-connected sessions). */
375
+ DELEGATE_DID: 'enbox:auth:delegateDid',
376
+
377
+ /** The connected DID (for wallet-connected sessions). */
378
+ CONNECTED_DID: 'enbox:auth:connectedDid',
379
+
380
+ /**
381
+ * The base URL of the local DWN server discovered via the `dwn://register`
382
+ * browser redirect flow. Persisted so subsequent page loads can skip the
383
+ * redirect and inject the endpoint directly.
384
+ *
385
+ * @see https://github.com/enboxorg/enbox/issues/589
386
+ */
387
+ LOCAL_DWN_ENDPOINT: 'enbox:auth:localDwnEndpoint',
388
+ } as const;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * VaultManager wraps {@link HdIdentityVault} with a high-level API
3
+ * and emits events on lock/unlock.
4
+ * @module
5
+ */
6
+
7
+ import type { HdIdentityVault, IdentityVaultBackup } from '@enbox/agent';
8
+
9
+ import type { AuthEventEmitter } from '../events.js';
10
+
11
+ /**
12
+ * Manages the encrypted identity vault lifecycle.
13
+ *
14
+ * The vault stores the agent's DID and content encryption key (CEK),
15
+ * protected by a user password using PBES2-HS512+A256KW with a 210K
16
+ * iteration work factor. The vault supports HD key derivation from
17
+ * a BIP-39 mnemonic for recovery.
18
+ */
19
+ export class VaultManager {
20
+ private readonly _vault: HdIdentityVault;
21
+ private readonly _emitter: AuthEventEmitter;
22
+
23
+ constructor(vault: HdIdentityVault, emitter: AuthEventEmitter) {
24
+ this._vault = vault;
25
+ this._emitter = emitter;
26
+ }
27
+
28
+ /** The underlying vault instance (for advanced usage). */
29
+ get raw(): HdIdentityVault {
30
+ return this._vault;
31
+ }
32
+
33
+ /** Whether the vault has been initialized (has encrypted data). */
34
+ async isInitialized(): Promise<boolean> {
35
+ return this._vault.isInitialized();
36
+ }
37
+
38
+ /** Whether the vault is currently locked (synchronous check). */
39
+ get isLocked(): boolean {
40
+ return this._vault.isLocked();
41
+ }
42
+
43
+ /**
44
+ * Unlock the vault with the given password.
45
+ * Decrypts the CEK into memory so the agent DID can be retrieved.
46
+ *
47
+ * @throws If the password is incorrect or vault is not initialized.
48
+ */
49
+ async unlock(password: string): Promise<void> {
50
+ await this._vault.unlock({ password });
51
+ this._emitter.emit('vault-unlocked', {});
52
+ }
53
+
54
+ /**
55
+ * Lock the vault, clearing the CEK from memory.
56
+ * After locking, the password must be provided again to unlock.
57
+ */
58
+ async lock(): Promise<void> {
59
+ await this._vault.lock();
60
+ this._emitter.emit('vault-locked', {});
61
+ }
62
+
63
+ /**
64
+ * Change the vault password. Re-encrypts the CEK with the new password.
65
+ *
66
+ * @throws If the old password is incorrect or vault is locked.
67
+ */
68
+ async changePassword(oldPassword: string, newPassword: string): Promise<void> {
69
+ await this._vault.changePassword({ oldPassword, newPassword });
70
+ }
71
+
72
+ /**
73
+ * Create a backup of the vault.
74
+ *
75
+ * @throws If the vault is not initialized or is locked.
76
+ */
77
+ async backup(): Promise<IdentityVaultBackup> {
78
+ return this._vault.backup();
79
+ }
80
+
81
+ /**
82
+ * Restore the vault from a backup.
83
+ *
84
+ * @throws If the password doesn't match the backup's encryption.
85
+ */
86
+ async restore(backup: IdentityVaultBackup, password: string): Promise<void> {
87
+ await this._vault.restore({ backup, password });
88
+ }
89
+ }