@metamask-previews/seedless-onboarding-controller 7.1.0-preview-69f51f81 → 7.1.0-preview-40468f94

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/CHANGELOG.md CHANGED
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - **BREAKING** The `encryptor` constructor param requires `encryptWithKey` method. ([#7800](https://github.com/MetaMask/core/pull/7800))
13
+ - The method is to encrypt the vault with cached encryption key while the wallet is unlocked.
12
14
  - Added new public method, `getAccessToken`. ([#7800](https://github.com/MetaMask/core/pull/7800))
13
15
  - Clients can use this method to get `accessToken` from the controller, instead of directly accessing from the state.
14
16
  - This method also adds refresh token mechanism when `accessToken` is expired, hence preventing expired token usage in the clients.
@@ -20,6 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
22
  - Bump `@metamask/keyring-controller` from `^25.0.0` to `^25.1.0` ([#7713](https://github.com/MetaMask/core/pull/7713))
21
23
  - Upgrade `@metamask/utils` from `^11.8.1` to `^11.9.0` ([#7511](https://github.com/MetaMask/core/pull/7511))
22
24
 
25
+ ### Fixed
26
+
27
+ - Fixed new `accessToken` not being persisted in the vault after the token refresh. ([#7800](https://github.com/MetaMask/core/pull/7800))
28
+
23
29
  ## [7.1.0]
24
30
 
25
31
  ### Added
@@ -975,22 +975,39 @@ _SeedlessOnboardingController_vaultEncryptor = new WeakMap(), _SeedlessOnboardin
975
975
  * corresponding to the current authPubKey in state.
976
976
  */
977
977
  async function _SeedlessOnboardingController_submitGlobalPassword({ targetAuthPubKey, globalPassword, maxKeyChainLength, }) {
978
- const { pwEncKey: curPwEncKey, authKeyPair: curAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, globalPassword);
978
+ const { pwEncKey: globalPwEncKey, authKeyPair: globalAuthKeyPair } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_recoverEncKey).call(this, globalPassword);
979
979
  try {
980
980
  // Recover vault encryption key.
981
981
  const res = await this.toprfClient.recoverPwEncKey({
982
982
  targetAuthPubKey,
983
- curPwEncKey,
984
- curAuthKeyPair,
983
+ curPwEncKey: globalPwEncKey,
984
+ curAuthKeyPair: globalAuthKeyPair,
985
985
  maxPwChainLength: maxKeyChainLength,
986
986
  });
987
987
  const { pwEncKey } = res;
988
988
  const vaultKey = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_loadSeedlessEncryptionKey).call(this, pwEncKey);
989
+ // accessToken before unlocking vault and flooding the state with values from the decrypted vault
990
+ // it might be the new token set from the `refreshAuthTokens` method.
991
+ const { accessToken: accessTokenBeforeUnlock } = this.state;
989
992
  // Unlock the controller
990
- await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetVaultData).call(this, {
993
+ const decryptedVaultData = await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_unlockVaultAndGetVaultData).call(this, {
991
994
  encryptionKey: vaultKey,
992
995
  });
993
996
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_setUnlocked).call(this);
997
+ // accessToken from decrypted vault
998
+ const accessTokenFromDecryptedVault = decryptedVaultData.accessToken;
999
+ // compare the two access tokens, take the latest access token if it's different.
1000
+ if (accessTokenBeforeUnlock &&
1001
+ accessTokenFromDecryptedVault &&
1002
+ accessTokenBeforeUnlock !== accessTokenFromDecryptedVault) {
1003
+ const latestAccessToken = (0, utils_3.compareAndGetLatestToken)(accessTokenBeforeUnlock, accessTokenFromDecryptedVault);
1004
+ // update the access token in the state with the latest access token if it's different from the decrypted access token after unlocking
1005
+ // later when we call `syncLatestGlobalPassword`, the encrypted vault will be updated with the latest access token.
1006
+ this.update((state) => {
1007
+ state.accessToken = latestAccessToken;
1008
+ });
1009
+ }
1010
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_resetPasswordOutdatedCache).call(this);
994
1011
  }
995
1012
  catch (error) {
996
1013
  if (__classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_isAuthTokenError).call(this, error)) {
@@ -1420,24 +1437,14 @@ async function _SeedlessOnboardingController_updateVault({ password, vaultData,
1420
1437
  // from the password using an intentionally slow key derivation function.
1421
1438
  // We should make sure that we only call it very intentionally.
1422
1439
  const { vault, exportedKeyString } = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").encryptWithDetail(password, serializedVaultData);
1423
- const updatedState = {
1424
- vault,
1425
- vaultEncryptionKey: exportedKeyString,
1426
- vaultEncryptionSalt: JSON.parse(vault).salt,
1427
- };
1428
- // Encrypt vault key.
1429
- if (pwEncKey) {
1430
- const aes = (0, webcrypto_1.managedNonce)(aes_1.gcm)(pwEncKey);
1431
- const encryptedKey = aes.encrypt((0, utils_2.utf8ToBytes)(exportedKeyString));
1432
- updatedState.encryptedSeedlessEncryptionKey =
1433
- (0, utils_1.bytesToBase64)(encryptedKey);
1434
- }
1440
+ const aes = (0, webcrypto_1.managedNonce)(aes_1.gcm)(pwEncKey);
1441
+ const encryptedKey = aes.encrypt((0, utils_2.utf8ToBytes)(exportedKeyString));
1442
+ const encryptedSeedlessEncryptionKey = (0, utils_1.bytesToBase64)(encryptedKey);
1435
1443
  this.update((state) => {
1436
- state.vault = updatedState.vault;
1437
- state.vaultEncryptionKey = updatedState.vaultEncryptionKey;
1438
- state.vaultEncryptionSalt = updatedState.vaultEncryptionSalt;
1439
- state.encryptedSeedlessEncryptionKey =
1440
- updatedState.encryptedSeedlessEncryptionKey;
1444
+ state.vault = vault;
1445
+ state.vaultEncryptionKey = exportedKeyString;
1446
+ state.vaultEncryptionSalt = JSON.parse(vault).salt;
1447
+ state.encryptedSeedlessEncryptionKey = encryptedSeedlessEncryptionKey;
1441
1448
  });
1442
1449
  });
1443
1450
  }, _SeedlessOnboardingController_getAccessTokenAndRevokeToken =
@@ -1623,35 +1630,43 @@ async function _SeedlessOnboardingController_executeWithTokenRefresh(operation,
1623
1630
  }
1624
1631
  }
1625
1632
  }, _SeedlessOnboardingController_updateVaultAfterAuthTokenRefresh = async function _SeedlessOnboardingController_updateVaultAfterAuthTokenRefresh(accessToken) {
1626
- if (!__classPrivateFieldGet(this, _SeedlessOnboardingController_isUnlocked, "f")) {
1627
- // we just temporarily store the access token in the state
1628
- // when user attempts to unlock the vault, we will use this access token to update the vault
1633
+ await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_withVaultLock).call(this, async () => {
1634
+ if (!__classPrivateFieldGet(this, _SeedlessOnboardingController_isUnlocked, "f")) {
1635
+ // we just temporarily store the access token in the state
1636
+ // when user attempts to unlock the vault, we will use this access token to update the vault
1637
+ this.update((state) => {
1638
+ state.accessToken = accessToken;
1639
+ });
1640
+ return;
1641
+ }
1642
+ const { vaultEncryptionKey, vaultEncryptionSalt } = this.state;
1643
+ if (!vaultEncryptionKey ||
1644
+ !vaultEncryptionSalt ||
1645
+ !__classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f")) {
1646
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.MissingCredentials);
1647
+ }
1648
+ const serializedVaultData = (0, utils_3.serializeVaultData)({
1649
+ ...__classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f"),
1650
+ accessToken,
1651
+ });
1652
+ const encryptionKey = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").importKey(vaultEncryptionKey);
1653
+ const updatedEncVault = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").encryptWithKey(encryptionKey, serializedVaultData);
1654
+ // NOTE: Referenced from keyring-controller!
1655
+ // We need to include the salt used to derive
1656
+ // the encryption key, to be able to derive it
1657
+ // from password again.
1658
+ updatedEncVault.salt = vaultEncryptionSalt;
1629
1659
  this.update((state) => {
1660
+ state.vault = JSON.stringify(updatedEncVault);
1661
+ state.vaultEncryptionSalt = vaultEncryptionSalt;
1630
1662
  state.accessToken = accessToken;
1663
+ state.vaultEncryptionKey = vaultEncryptionKey;
1631
1664
  });
1632
- return;
1633
- }
1634
- const { vaultEncryptionKey, vaultEncryptionSalt } = this.state;
1635
- if (!vaultEncryptionKey ||
1636
- !vaultEncryptionSalt ||
1637
- !__classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f")) {
1638
- throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.MissingCredentials);
1639
- }
1640
- // update the cached decrypted vault data with the new access token
1641
- __classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f").accessToken = accessToken;
1642
- const serializedVaultData = (0, utils_3.serializeVaultData)(__classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f"));
1643
- const encryptionKey = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").importKey(vaultEncryptionKey);
1644
- const updatedEncVault = await __classPrivateFieldGet(this, _SeedlessOnboardingController_vaultEncryptor, "f").encryptWithKey(encryptionKey, serializedVaultData);
1645
- // NOTE: Referenced from keyring-controller!
1646
- // We need to include the salt used to derive
1647
- // the encryption key, to be able to derive it
1648
- // from password again.
1649
- updatedEncVault.salt = vaultEncryptionSalt;
1650
- this.update((state) => {
1651
- state.vault = JSON.stringify(updatedEncVault);
1652
- state.vaultEncryptionSalt = vaultEncryptionSalt;
1653
- state.accessToken = accessToken;
1654
- state.vaultEncryptionKey = vaultEncryptionKey;
1665
+ // update the cached decrypted vault data with the new access token
1666
+ __classPrivateFieldSet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, {
1667
+ ...__classPrivateFieldGet(this, _SeedlessOnboardingController_cachedDecryptedVaultData, "f"),
1668
+ accessToken,
1669
+ }, "f");
1655
1670
  });
1656
1671
  }, _SeedlessOnboardingController_checkTokensExpired = function _SeedlessOnboardingController_checkTokensExpired() {
1657
1672
  // proactively check for expired tokens and refresh them if needed