@metamask-previews/seedless-onboarding-controller 0.0.0-preview-c4b7ea2e → 0.0.0-preview-e9f8bc88

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 (38) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/SecretMetadata.cjs.map +1 -1
  3. package/dist/SecretMetadata.d.cts +0 -5
  4. package/dist/SecretMetadata.d.cts.map +1 -1
  5. package/dist/SecretMetadata.d.mts +0 -5
  6. package/dist/SecretMetadata.d.mts.map +1 -1
  7. package/dist/SecretMetadata.mjs.map +1 -1
  8. package/dist/SeedlessOnboardingController.cjs +328 -117
  9. package/dist/SeedlessOnboardingController.cjs.map +1 -1
  10. package/dist/SeedlessOnboardingController.d.cts +47 -1
  11. package/dist/SeedlessOnboardingController.d.cts.map +1 -1
  12. package/dist/SeedlessOnboardingController.d.mts +47 -1
  13. package/dist/SeedlessOnboardingController.d.mts.map +1 -1
  14. package/dist/SeedlessOnboardingController.mjs +330 -119
  15. package/dist/SeedlessOnboardingController.mjs.map +1 -1
  16. package/dist/constants.cjs +5 -1
  17. package/dist/constants.cjs.map +1 -1
  18. package/dist/constants.d.cts +5 -1
  19. package/dist/constants.d.cts.map +1 -1
  20. package/dist/constants.d.mts +5 -1
  21. package/dist/constants.d.mts.map +1 -1
  22. package/dist/constants.mjs +4 -0
  23. package/dist/constants.mjs.map +1 -1
  24. package/dist/errors.cjs +26 -1
  25. package/dist/errors.cjs.map +1 -1
  26. package/dist/errors.d.cts +13 -0
  27. package/dist/errors.d.cts.map +1 -1
  28. package/dist/errors.d.mts +13 -0
  29. package/dist/errors.d.mts.map +1 -1
  30. package/dist/errors.mjs +24 -0
  31. package/dist/errors.mjs.map +1 -1
  32. package/dist/types.cjs.map +1 -1
  33. package/dist/types.d.cts +16 -1
  34. package/dist/types.d.cts.map +1 -1
  35. package/dist/types.d.mts +16 -1
  36. package/dist/types.d.mts.map +1 -1
  37. package/dist/types.mjs.map +1 -1
  38. package/package.json +1 -1
@@ -10,13 +10,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _SeedlessOnboardingController_instances, _SeedlessOnboardingController_vaultEncryptor, _SeedlessOnboardingController_vaultOperationMutex, _SeedlessOnboardingController_isUnlocked, _SeedlessOnboardingController_setUnlocked, _SeedlessOnboardingController_persistOprfKey, _SeedlessOnboardingController_recoverEncKey, _SeedlessOnboardingController_changeEncryptionKey, _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup, _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey, _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState, _SeedlessOnboardingController_filterDupesAndUpdateSocialBackupsMetadata, _SeedlessOnboardingController_createNewVaultWithAuthData, _SeedlessOnboardingController_updateVault, _SeedlessOnboardingController_withVaultLock, _SeedlessOnboardingController_serializeKeyData, _SeedlessOnboardingController_parseVaultData, _SeedlessOnboardingController_assertIsUnlocked, _SeedlessOnboardingController_assertIsAuthenticatedUser, _SeedlessOnboardingController_assertIsValidVaultData;
13
+ var _SeedlessOnboardingController_instances, _SeedlessOnboardingController_vaultEncryptor, _SeedlessOnboardingController_controllerOperationMutex, _SeedlessOnboardingController_vaultOperationMutex, _SeedlessOnboardingController_isUnlocked, _SeedlessOnboardingController_recoverPassword, _SeedlessOnboardingController_setUnlocked, _SeedlessOnboardingController_persistOprfKey, _SeedlessOnboardingController_persistAuthPubKey, _SeedlessOnboardingController_recoverAuthPubKey, _SeedlessOnboardingController_recoverEncKey, _SeedlessOnboardingController_changeEncryptionKey, _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup, _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey, _SeedlessOnboardingController_withPersistedSeedPhraseBackupsState, _SeedlessOnboardingController_filterDupesAndUpdateSocialBackupsMetadata, _SeedlessOnboardingController_createNewVaultWithAuthData, _SeedlessOnboardingController_updateVault, _SeedlessOnboardingController_withControllerLock, _SeedlessOnboardingController_withVaultLock, _SeedlessOnboardingController_serializeKeyData, _SeedlessOnboardingController_parseVaultData, _SeedlessOnboardingController_assertIsUnlocked, _SeedlessOnboardingController_assertIsAuthenticatedUser, _SeedlessOnboardingController_assertIsSRPBackedUpUser, _SeedlessOnboardingController_assertPasswordInSync, _SeedlessOnboardingController_resetPasswordOutdatedCache, _SeedlessOnboardingController_assertIsValidVaultData;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.SeedlessOnboardingController = exports.getDefaultSeedlessOnboardingControllerState = void 0;
16
16
  const auth_network_utils_1 = require("@metamask/auth-network-utils");
17
17
  const base_controller_1 = require("@metamask/base-controller");
18
18
  const toprf_secure_backup_1 = require("@metamask/toprf-secure-backup");
19
19
  const utils_1 = require("@metamask/utils");
20
+ const secp256k1_1 = require("@noble/curves/secp256k1");
20
21
  const async_mutex_1 = require("async-mutex");
21
22
  const constants_1 = require("./constants.cjs");
22
23
  const errors_1 = require("./errors.cjs");
@@ -51,7 +52,7 @@ const seedlessOnboardingMetadata = {
51
52
  anonymous: true,
52
53
  },
53
54
  nodeAuthTokens: {
54
- persist: false,
55
+ persist: true,
55
56
  anonymous: true,
56
57
  },
57
58
  authConnection: {
@@ -82,6 +83,14 @@ const seedlessOnboardingMetadata = {
82
83
  persist: false,
83
84
  anonymous: true,
84
85
  },
86
+ authPubKey: {
87
+ persist: true,
88
+ anonymous: true,
89
+ },
90
+ passwordOutdatedCache: {
91
+ persist: true,
92
+ anonymous: true,
93
+ },
85
94
  };
86
95
  class SeedlessOnboardingController extends base_controller_1.BaseController {
87
96
  /**
@@ -105,6 +114,7 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
105
114
  });
106
115
  _SeedlessOnboardingController_instances.add(this);
107
116
  _SeedlessOnboardingController_vaultEncryptor.set(this, void 0);
117
+ _SeedlessOnboardingController_controllerOperationMutex.set(this, new async_mutex_1.Mutex());
108
118
  _SeedlessOnboardingController_vaultOperationMutex.set(this, new async_mutex_1.Mutex());
109
119
  /**
110
120
  * Controller lock state.
@@ -140,35 +150,37 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
140
150
  * @returns A promise that resolves to the authentication result.
141
151
  */
142
152
  async authenticate(params) {
143
- try {
144
- const { idTokens, authConnectionId, groupedAuthConnectionId, userId, authConnection, socialLoginEmail, } = params;
145
- const hashedIdTokenHexes = idTokens.map((idToken) => {
146
- return (0, utils_1.remove0x)((0, auth_network_utils_1.keccak256AndHexify)((0, utils_1.stringToBytes)(idToken)));
147
- });
148
- const authenticationResult = await this.toprfClient.authenticate({
149
- authConnectionId: groupedAuthConnectionId || authConnectionId,
150
- userId,
151
- idTokens: hashedIdTokenHexes,
152
- groupedAuthConnectionParams: {
153
- authConnectionId,
154
- idTokens,
155
- },
156
- });
157
- // update the state with the authenticated user info
158
- this.update((state) => {
159
- state.nodeAuthTokens = authenticationResult.nodeAuthTokens;
160
- state.authConnectionId = authConnectionId;
161
- state.groupedAuthConnectionId = groupedAuthConnectionId;
162
- state.userId = userId;
163
- state.authConnection = authConnection;
164
- state.socialLoginEmail = socialLoginEmail;
165
- });
166
- return authenticationResult;
167
- }
168
- catch (error) {
169
- log('Error authenticating user', error);
170
- throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.AuthenticationError);
171
- }
153
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
154
+ try {
155
+ const { idTokens, authConnectionId, groupedAuthConnectionId, userId, authConnection, socialLoginEmail, } = params;
156
+ const hashedIdTokenHexes = idTokens.map((idToken) => {
157
+ return (0, utils_1.remove0x)((0, auth_network_utils_1.keccak256AndHexify)((0, utils_1.stringToBytes)(idToken)));
158
+ });
159
+ const authenticationResult = await this.toprfClient.authenticate({
160
+ authConnectionId: groupedAuthConnectionId || authConnectionId,
161
+ userId,
162
+ idTokens: hashedIdTokenHexes,
163
+ groupedAuthConnectionParams: {
164
+ authConnectionId,
165
+ idTokens,
166
+ },
167
+ });
168
+ // update the state with the authenticated user info
169
+ this.update((state) => {
170
+ state.nodeAuthTokens = authenticationResult.nodeAuthTokens;
171
+ state.authConnectionId = authConnectionId;
172
+ state.groupedAuthConnectionId = groupedAuthConnectionId;
173
+ state.userId = userId;
174
+ state.authConnection = authConnection;
175
+ state.socialLoginEmail = socialLoginEmail;
176
+ });
177
+ return authenticationResult;
178
+ }
179
+ catch (error) {
180
+ log('Error authenticating user', error);
181
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.AuthenticationError);
182
+ }
183
+ });
172
184
  }
173
185
  /**
174
186
  * Create a new TOPRF encryption key using given password and backups the provided seed phrase.
@@ -182,27 +194,32 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
182
194
  // to make sure that fail fast,
183
195
  // assert that the user is authenticated before creating the TOPRF key and backing up the seed phrase
184
196
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
185
- // locally evaluate the encryption key from the password
186
- const { encKey, authKeyPair, oprfKey } = this.toprfClient.createLocalKey({
187
- password,
188
- });
189
- // encrypt and store the seed phrase backup
190
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, {
191
- keyringId,
192
- seedPhrase,
193
- encKey,
194
- authKeyPair,
195
- });
196
- // store/persist the encryption key shares
197
- // We store the seed phrase metadata in the metadata store first. If this operation fails,
198
- // we avoid persisting the encryption key shares to prevent a situation where a user appears
199
- // to have an account but with no associated data.
200
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistOprfKey).call(this, oprfKey, authKeyPair.pk);
201
- // create a new vault with the resulting authentication data
202
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
203
- password,
204
- rawToprfEncryptionKey: encKey,
205
- rawToprfAuthKeyPair: authKeyPair,
197
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
198
+ // locally evaluate the encryption key from the password
199
+ const { encKey, authKeyPair, oprfKey } = this.toprfClient.createLocalKey({
200
+ password,
201
+ });
202
+ // encrypt and store the seed phrase backup
203
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, {
204
+ keyringId,
205
+ seedPhrase,
206
+ encKey,
207
+ authKeyPair,
208
+ });
209
+ // store/persist the encryption key shares
210
+ // We store the seed phrase metadata in the metadata store first. If this operation fails,
211
+ // we avoid persisting the encryption key shares to prevent a situation where a user appears
212
+ // to have an account but with no associated data.
213
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistOprfKey).call(this, oprfKey, authKeyPair.pk);
214
+ // create a new vault with the resulting authentication data
215
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
216
+ password,
217
+ rawToprfEncryptionKey: encKey,
218
+ rawToprfAuthKeyPair: authKeyPair,
219
+ });
220
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistAuthPubKey).call(this, {
221
+ authPubKey: authKeyPair.pk,
222
+ });
206
223
  });
207
224
  }
208
225
  /**
@@ -213,15 +230,21 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
213
230
  * @returns A promise that resolves to the success of the operation.
214
231
  */
215
232
  async addNewSeedPhraseBackup(seedPhrase, keyringId) {
216
- __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
217
- // verify the password and unlock the vault
218
- const { toprfEncryptionKey, toprfAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this);
219
- // encrypt and store the seed phrase backup
220
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, {
221
- keyringId,
222
- seedPhrase,
223
- encKey: toprfEncryptionKey,
224
- authKeyPair: toprfAuthKeyPair,
233
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
234
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
235
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertPasswordInSync).call(this, {
236
+ skipCache: true,
237
+ skipLock: true, // skip lock since we already have the lock
238
+ });
239
+ // verify the password and unlock the vault
240
+ const { toprfEncryptionKey, toprfAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this);
241
+ // encrypt and store the seed phrase backup
242
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_encryptAndStoreSeedPhraseBackup).call(this, {
243
+ keyringId,
244
+ seedPhrase,
245
+ encKey: toprfEncryptionKey,
246
+ authKeyPair: toprfAuthKeyPair,
247
+ });
225
248
  });
226
249
  }
227
250
  /**
@@ -235,40 +258,45 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
235
258
  async fetchAllSeedPhrases(password) {
236
259
  // assert that the user is authenticated before fetching the seed phrases
237
260
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
238
- let encKey;
239
- let authKeyPair;
240
- if (password) {
241
- const recoverEncKeyResult = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, password);
242
- encKey = recoverEncKeyResult.encKey;
243
- authKeyPair = recoverEncKeyResult.authKeyPair;
244
- }
245
- else {
246
- __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
247
- // verify the password and unlock the vault
248
- const { toprfEncryptionKey, toprfAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this);
249
- encKey = toprfEncryptionKey;
250
- authKeyPair = toprfAuthKeyPair;
251
- }
252
- try {
253
- const secretData = await this.toprfClient.fetchAllSecretDataItems({
254
- decKey: encKey,
255
- authKeyPair,
256
- });
257
- if (secretData?.length > 0 && password) {
258
- // if password is provided, we need to create a new vault with the auth data. (supposedly the user is trying to rehydrate the wallet)
259
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
260
- password,
261
- rawToprfEncryptionKey: encKey,
262
- rawToprfAuthKeyPair: authKeyPair,
261
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
262
+ let encKey;
263
+ let authKeyPair;
264
+ if (password) {
265
+ const recoverEncKeyResult = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, password);
266
+ encKey = recoverEncKeyResult.encKey;
267
+ authKeyPair = recoverEncKeyResult.authKeyPair;
268
+ }
269
+ else {
270
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
271
+ // verify the password and unlock the vault
272
+ const keysFromVault = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this);
273
+ encKey = keysFromVault.toprfEncryptionKey;
274
+ authKeyPair = keysFromVault.toprfAuthKeyPair;
275
+ }
276
+ try {
277
+ const secretData = await this.toprfClient.fetchAllSecretDataItems({
278
+ decKey: encKey,
279
+ authKeyPair,
263
280
  });
281
+ if (secretData?.length > 0 && password) {
282
+ // if password is provided, we need to create a new vault with the auth data. (supposedly the user is trying to rehydrate the wallet)
283
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
284
+ password,
285
+ rawToprfEncryptionKey: encKey,
286
+ rawToprfAuthKeyPair: authKeyPair,
287
+ });
288
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistAuthPubKey).call(this, {
289
+ authPubKey: authKeyPair.pk,
290
+ });
291
+ }
292
+ const secrets = SecretMetadata_1.SecretMetadata.parseSecretsFromMetadataStore(secretData, constants_1.SecretType.Mnemonic);
293
+ return secrets.map((secret) => secret.data);
264
294
  }
265
- const secrets = SecretMetadata_1.SecretMetadata.parseSecretsFromMetadataStore(secretData, constants_1.SecretType.Mnemonic);
266
- return secrets.map((secret) => secret.data);
267
- }
268
- catch (error) {
269
- log('Error fetching seed phrase metadata', error);
270
- throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.FailedToFetchSeedPhraseMetadata);
271
- }
295
+ catch (error) {
296
+ log('Error fetching seed phrase metadata', error);
297
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.FailedToFetchSeedPhraseMetadata);
298
+ }
299
+ });
272
300
  }
273
301
  /**
274
302
  * Update the password of the seedless onboarding flow.
@@ -277,25 +305,38 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
277
305
  *
278
306
  * @param newPassword - The new password to update.
279
307
  * @param oldPassword - The old password to verify.
308
+ * @returns A promise that resolves to the success of the operation.
280
309
  */
281
310
  async changePassword(newPassword, oldPassword) {
282
- __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
283
- // verify the old password of the encrypted vault
284
- await this.verifyPassword(oldPassword);
285
- try {
286
- // update the encryption key with new password and update the Metadata Store
287
- const { encKey: newEncKey, authKeyPair: newAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_changeEncryptionKey).call(this, newPassword, oldPassword);
288
- // update and encrypt the vault with new password
289
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
290
- password: newPassword,
291
- rawToprfEncryptionKey: newEncKey,
292
- rawToprfAuthKeyPair: newAuthKeyPair,
311
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
312
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsUnlocked).call(this);
313
+ // verify the old password of the encrypted vault
314
+ await this.verifyVaultPassword(oldPassword, {
315
+ skipLock: true, // skip lock since we already have the lock
293
316
  });
294
- }
295
- catch (error) {
296
- log('Error changing password', error);
297
- throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.FailedToChangePassword);
298
- }
317
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertPasswordInSync).call(this, {
318
+ skipCache: true,
319
+ skipLock: true, // skip lock since we already have the lock
320
+ });
321
+ try {
322
+ // update the encryption key with new password and update the Metadata Store
323
+ const { encKey: newEncKey, authKeyPair: newAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_changeEncryptionKey).call(this, newPassword, oldPassword);
324
+ // update and encrypt the vault with new password
325
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
326
+ password: newPassword,
327
+ rawToprfEncryptionKey: newEncKey,
328
+ rawToprfAuthKeyPair: newAuthKeyPair,
329
+ });
330
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistAuthPubKey).call(this, {
331
+ authPubKey: newAuthKeyPair.pk,
332
+ });
333
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_resetPasswordOutdatedCache).call(this);
334
+ }
335
+ catch (error) {
336
+ log('Error changing password', error);
337
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.FailedToChangePassword);
338
+ }
339
+ });
299
340
  }
300
341
  /**
301
342
  * Update the backup metadata state for the given seed phrase.
@@ -312,13 +353,21 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
312
353
  * Verify the password validity by decrypting the vault.
313
354
  *
314
355
  * @param password - The password to verify.
356
+ * @param options - Optional options object.
357
+ * @param options.skipLock - Whether to skip the lock acquisition.
358
+ * @returns A promise that resolves to the success of the operation.
315
359
  * @throws {Error} If the password is invalid or the vault is not initialized.
316
360
  */
317
- async verifyPassword(password) {
318
- if (!this.state.vault) {
319
- throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.VaultError);
320
- }
321
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").decrypt(password, this.state.vault);
361
+ async verifyVaultPassword(password, options) {
362
+ const doVerify = async () => {
363
+ if (!this.state.vault) {
364
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.VaultError);
365
+ }
366
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").decrypt(password, this.state.vault);
367
+ };
368
+ return options?.skipLock
369
+ ? await doVerify()
370
+ : await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, doVerify);
322
371
  }
323
372
  /**
324
373
  * Get the hash of the seed phrase backup for the given seed phrase, from the state.
@@ -341,10 +390,13 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
341
390
  * This operation is useful when user performs some actions that requires the user password/encryption key. e.g. add new srp backup
342
391
  *
343
392
  * @param password - The password to submit.
393
+ * @returns A promise that resolves to the success of the operation.
344
394
  */
345
395
  async submitPassword(password) {
346
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this, password);
347
- __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_setUnlocked).call(this);
396
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
397
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey).call(this, password);
398
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_setUnlocked).call(this);
399
+ });
348
400
  }
349
401
  /**
350
402
  * Set the controller to locked state, and deallocate the secrets (vault encryption key and salt).
@@ -358,6 +410,98 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
358
410
  });
359
411
  __classPrivateFieldSet(this, _SeedlessOnboardingController_isUnlocked, false, "f");
360
412
  }
413
+ /**
414
+ * Sync the latest global password to the controller.
415
+ * reset vault with latest globalPassword,
416
+ * persist the latest global password authPubKey
417
+ *
418
+ * @param params - The parameters for syncing the latest global password.
419
+ * @param params.oldPassword - The old password to verify.
420
+ * @param params.globalPassword - The latest global password.
421
+ * @returns A promise that resolves to the success of the operation.
422
+ */
423
+ async syncLatestGlobalPassword({ oldPassword, globalPassword, }) {
424
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
425
+ // verify correct old password
426
+ await this.verifyVaultPassword(oldPassword, {
427
+ skipLock: true, // skip lock since we already have the lock
428
+ });
429
+ // update vault with latest globalPassword
430
+ const { encKey, authKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, globalPassword);
431
+ // update and encrypt the vault with new password
432
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_createNewVaultWithAuthData).call(this, {
433
+ password: globalPassword,
434
+ rawToprfEncryptionKey: encKey,
435
+ rawToprfAuthKeyPair: authKeyPair,
436
+ });
437
+ // persist the latest global password authPubKey
438
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_persistAuthPubKey).call(this, {
439
+ authPubKey: authKeyPair.pk,
440
+ });
441
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_resetPasswordOutdatedCache).call(this);
442
+ });
443
+ }
444
+ /**
445
+ * @description Fetch the password corresponding to the current authPubKey in state (current device password which is already out of sync with the current global password).
446
+ * then we use this recovered old password to unlock the vault and set the password to the new global password.
447
+ *
448
+ * @param params - The parameters for fetching the password.
449
+ * @param params.globalPassword - The latest global password.
450
+ * @returns A promise that resolves to the password corresponding to the current authPubKey in state.
451
+ */
452
+ async recoverCurrentDevicePassword({ globalPassword, }) {
453
+ return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, async () => {
454
+ const currentDeviceAuthPubKey = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverAuthPubKey).call(this);
455
+ const { password: currentDevicePassword } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverPassword).call(this, {
456
+ targetPwPubKey: currentDeviceAuthPubKey,
457
+ globalPassword,
458
+ });
459
+ return {
460
+ password: currentDevicePassword,
461
+ };
462
+ });
463
+ }
464
+ /**
465
+ * @description Check if the current password is outdated compare to the global password.
466
+ *
467
+ * @param options - Optional options object.
468
+ * @param options.skipCache - If true, bypass the cache and force a fresh check.
469
+ * @param options.skipLock - Whether to skip the lock acquisition.
470
+ * @returns A promise that resolves to true if the password is outdated, false otherwise.
471
+ */
472
+ async checkIsPasswordOutdated(options) {
473
+ // cache result to reduce load on infra
474
+ // Check cache first unless skipCache is true
475
+ if (!options?.skipCache) {
476
+ const { passwordOutdatedCache } = this.state;
477
+ const now = Date.now();
478
+ const isCacheValid = passwordOutdatedCache &&
479
+ now - passwordOutdatedCache.timestamp < constants_1.PASSWORD_OUTDATED_CACHE_TTL_MS;
480
+ if (isCacheValid) {
481
+ return passwordOutdatedCache.isExpiredPwd;
482
+ }
483
+ }
484
+ const doCheck = async () => {
485
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
486
+ const { nodeAuthTokens, authConnectionId, groupedAuthConnectionId, userId, } = this.state;
487
+ const currentDeviceAuthPubKey = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverAuthPubKey).call(this);
488
+ const { authPubKey: globalAuthPubKey } = await this.toprfClient.fetchAuthPubKey({
489
+ nodeAuthTokens,
490
+ authConnectionId: groupedAuthConnectionId || authConnectionId,
491
+ userId,
492
+ });
493
+ // use noble lib to deserialize and compare curve point
494
+ const isExpiredPwd = !secp256k1_1.secp256k1.ProjectivePoint.fromHex(currentDeviceAuthPubKey).equals(secp256k1_1.secp256k1.ProjectivePoint.fromHex(globalAuthPubKey));
495
+ // Cache the result in state
496
+ this.update((state) => {
497
+ state.passwordOutdatedCache = { isExpiredPwd, timestamp: Date.now() };
498
+ });
499
+ return isExpiredPwd;
500
+ };
501
+ return options?.skipLock
502
+ ? await doCheck()
503
+ : await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withControllerLock).call(this, doCheck);
504
+ }
361
505
  /**
362
506
  * Clears the current state of the SeedlessOnboardingController.
363
507
  */
@@ -369,7 +513,29 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
369
513
  }
370
514
  }
371
515
  exports.SeedlessOnboardingController = SeedlessOnboardingController;
372
- _SeedlessOnboardingController_vaultEncryptor = new WeakMap(), _SeedlessOnboardingController_vaultOperationMutex = new WeakMap(), _SeedlessOnboardingController_isUnlocked = new WeakMap(), _SeedlessOnboardingController_instances = new WeakSet(), _SeedlessOnboardingController_setUnlocked = function _SeedlessOnboardingController_setUnlocked() {
516
+ _SeedlessOnboardingController_vaultEncryptor = new WeakMap(), _SeedlessOnboardingController_controllerOperationMutex = new WeakMap(), _SeedlessOnboardingController_vaultOperationMutex = new WeakMap(), _SeedlessOnboardingController_isUnlocked = new WeakMap(), _SeedlessOnboardingController_instances = new WeakSet(), _SeedlessOnboardingController_recoverPassword =
517
+ /**
518
+ * @description Fetch the password corresponding to the targetPwPubKey.
519
+ *
520
+ * @param params - The parameters for fetching the password.
521
+ * @param params.targetPwPubKey - The target public key of the password to recover.
522
+ * @param params.globalPassword - The latest global password.
523
+ * @returns A promise that resolves to the password corresponding to the current authPubKey in state.
524
+ */
525
+ async function _SeedlessOnboardingController_recoverPassword({ targetPwPubKey, globalPassword, }) {
526
+ const { encKey: latestPwEncKey, authKeyPair: latestPwAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, globalPassword);
527
+ try {
528
+ const res = await this.toprfClient.recoverPassword({
529
+ targetPwPubKey,
530
+ curEncKey: latestPwEncKey,
531
+ curAuthKeyPair: latestPwAuthKeyPair,
532
+ });
533
+ return res;
534
+ }
535
+ catch (error) {
536
+ throw errors_1.PasswordSyncError.getInstance(error);
537
+ }
538
+ }, _SeedlessOnboardingController_setUnlocked = function _SeedlessOnboardingController_setUnlocked() {
373
539
  __classPrivateFieldSet(this, _SeedlessOnboardingController_isUnlocked, true, "f");
374
540
  }, _SeedlessOnboardingController_persistOprfKey =
375
541
  /**
@@ -395,6 +561,14 @@ async function _SeedlessOnboardingController_persistOprfKey(oprfKey, authPubKey)
395
561
  log('Error persisting local encryption key', error);
396
562
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.FailedToPersistOprfKey);
397
563
  }
564
+ }, _SeedlessOnboardingController_persistAuthPubKey = function _SeedlessOnboardingController_persistAuthPubKey(params) {
565
+ this.update((state) => {
566
+ state.authPubKey = (0, utils_1.bytesToBase64)(params.authPubKey);
567
+ });
568
+ }, _SeedlessOnboardingController_recoverAuthPubKey = function _SeedlessOnboardingController_recoverAuthPubKey() {
569
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsSRPBackedUpUser).call(this, this.state);
570
+ const { authPubKey } = this.state;
571
+ return (0, utils_1.base64ToBytes)(authPubKey);
398
572
  }, _SeedlessOnboardingController_recoverEncKey =
399
573
  /**
400
574
  * Recover the encryption key from password.
@@ -631,6 +805,21 @@ async function _SeedlessOnboardingController_updateVault({ password, serializedV
631
805
  state.vaultEncryptionSalt = JSON.parse(vault).salt;
632
806
  });
633
807
  });
808
+ }, _SeedlessOnboardingController_withControllerLock =
809
+ /**
810
+ * Lock the controller mutex before executing the given function,
811
+ * and release it after the function is resolved or after an
812
+ * error is thrown.
813
+ *
814
+ * This wrapper ensures that each mutable operation that interacts with the
815
+ * controller and that changes its state is executed in a mutually exclusive way,
816
+ * preventing unsafe concurrent access that could lead to unpredictable behavior.
817
+ *
818
+ * @param callback - The function to execute while the controller mutex is locked.
819
+ * @returns The result of the function.
820
+ */
821
+ async function _SeedlessOnboardingController_withControllerLock(callback) {
822
+ return await withLock(__classPrivateFieldGet(this, _SeedlessOnboardingController_controllerOperationMutex, "f"), callback);
634
823
  }, _SeedlessOnboardingController_withVaultLock =
635
824
  /**
636
825
  * Lock the vault mutex before executing the given function,
@@ -698,6 +887,28 @@ async function _SeedlessOnboardingController_withVaultLock(callback) {
698
887
  ) {
699
888
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InsufficientAuthToken);
700
889
  }
890
+ }, _SeedlessOnboardingController_assertIsSRPBackedUpUser = function _SeedlessOnboardingController_assertIsSRPBackedUpUser(value) {
891
+ if (!this.state.authPubKey) {
892
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.SRPNotBackedUpError);
893
+ }
894
+ }, _SeedlessOnboardingController_assertPasswordInSync =
895
+ /**
896
+ * Assert that the password is in sync with the global password.
897
+ *
898
+ * @param options - The options for asserting the password is in sync.
899
+ * @param options.skipCache - Whether to skip the cache check.
900
+ * @param options.skipLock - Whether to skip the lock acquisition.
901
+ * @throws If the password is outdated.
902
+ */
903
+ async function _SeedlessOnboardingController_assertPasswordInSync(options) {
904
+ const isPasswordOutdated = await this.checkIsPasswordOutdated(options);
905
+ if (isPasswordOutdated) {
906
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.OutdatedPassword);
907
+ }
908
+ }, _SeedlessOnboardingController_resetPasswordOutdatedCache = function _SeedlessOnboardingController_resetPasswordOutdatedCache() {
909
+ this.update((state) => {
910
+ delete state.passwordOutdatedCache;
911
+ });
701
912
  }, _SeedlessOnboardingController_assertIsValidVaultData = function _SeedlessOnboardingController_assertIsValidVaultData(value) {
702
913
  // value is not valid vault data if any of the following conditions are true:
703
914
  if (!value || // value is not defined