@metamask-previews/seedless-onboarding-controller 2.0.1-preview-2bb8758e → 2.0.1-preview-c10fdda1

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
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Added `access_token` and `metadata_access_token` in seedless controller state. ([#6060](https://github.com/MetaMask/core/pull/6060))
13
+ - `access_token` can be used by profile sync pairing and for other apis access after wallet is unlocked.
14
+ - `metadata_access_token` is used to give access for web3auth metadata apis.
15
+
10
16
  ## [2.0.1]
11
17
 
12
18
  ### Fixed
@@ -102,6 +102,17 @@ const seedlessOnboardingMetadata = {
102
102
  persist: false,
103
103
  anonymous: true,
104
104
  },
105
+ // stays in vault
106
+ accessToken: {
107
+ persist: false,
108
+ anonymous: true,
109
+ },
110
+ // stays outside of vault as this token is accessed by the metadata service
111
+ // before the vault is created or unlocked.
112
+ metadataAccessToken: {
113
+ persist: true,
114
+ anonymous: true,
115
+ },
105
116
  encryptedSeedlessEncryptionKey: {
106
117
  persist: true,
107
118
  anonymous: true,
@@ -150,6 +161,7 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
150
161
  this.toprfClient = new toprf_secure_backup_1.ToprfSecureBackup({
151
162
  network,
152
163
  keyDeriver: toprfKeyDeriver,
164
+ fetchMetadataAccessCreds: this.fetchMetadataAccessCreds.bind(this),
153
165
  });
154
166
  __classPrivateFieldSet(this, _SeedlessOnboardingController_refreshJWTToken, refreshJWTToken, "f");
155
167
  __classPrivateFieldSet(this, _SeedlessOnboardingController_revokeRefreshToken, revokeRefreshToken, "f");
@@ -162,6 +174,25 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
162
174
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_setUnlocked).call(this);
163
175
  });
164
176
  }
177
+ async fetchMetadataAccessCreds() {
178
+ const { metadataAccessToken } = this.state;
179
+ if (!metadataAccessToken) {
180
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidMetadataAccessToken);
181
+ }
182
+ // Check if token is expired and refresh if needed
183
+ const decodedToken = this.decodeJWTToken(metadataAccessToken);
184
+ if (decodedToken.exp < Math.floor(Date.now() / 1000)) {
185
+ // Token is expired, refresh it
186
+ await this.refreshAuthTokens();
187
+ // Get the new token after refresh
188
+ const { metadataAccessToken: newMetadataAccessToken } = this.state;
189
+ if (!newMetadataAccessToken) {
190
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidMetadataAccessToken);
191
+ }
192
+ return { metadataAccessToken: newMetadataAccessToken };
193
+ }
194
+ return { metadataAccessToken };
195
+ }
165
196
  /**
166
197
  * Authenticate OAuth user using the seedless onboarding flow
167
198
  * and determine if the user is already registered or not.
@@ -175,13 +206,15 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
175
206
  * @param params.socialLoginEmail - The user email from Social login.
176
207
  * @param params.refreshToken - refresh token for refreshing expired nodeAuthTokens.
177
208
  * @param params.revokeToken - revoke token for revoking refresh token and get new refresh token and new revoke token.
209
+ * @param params.accessToken - Access token for pairing with profile sync auth service and to access other services.
210
+ * @param params.metadataAccessToken - Metadata access token for accessing the metadata service before the vault is created or unlocked.
178
211
  * @param params.skipLock - Optional flag to skip acquiring the controller lock. (to prevent deadlock in case the caller already acquired the lock)
179
212
  * @returns A promise that resolves to the authentication result.
180
213
  */
181
214
  async authenticate(params) {
182
215
  const doAuthenticateWithNodes = async () => {
183
216
  try {
184
- const { idTokens, authConnectionId, groupedAuthConnectionId, userId, authConnection, socialLoginEmail, refreshToken, revokeToken, } = params;
217
+ const { idTokens, authConnectionId, groupedAuthConnectionId, userId, authConnection, socialLoginEmail, refreshToken, revokeToken, accessToken, metadataAccessToken, } = params;
185
218
  const authenticationResult = await this.toprfClient.authenticate({
186
219
  authConnectionId,
187
220
  userId,
@@ -203,6 +236,12 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
203
236
  // Temporarily store revoke token in state for later vault creation
204
237
  state.revokeToken = revokeToken;
205
238
  }
239
+ if (accessToken) {
240
+ state.accessToken = accessToken;
241
+ }
242
+ if (metadataAccessToken) {
243
+ state.metadataAccessToken = metadataAccessToken;
244
+ }
206
245
  });
207
246
  return authenticationResult;
208
247
  }
@@ -461,6 +500,7 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
461
500
  delete state.vaultEncryptionKey;
462
501
  delete state.vaultEncryptionSalt;
463
502
  delete state.revokeToken;
503
+ delete state.accessToken;
464
504
  });
465
505
  __classPrivateFieldSet(this, _SeedlessOnboardingController_isUnlocked, false, "f");
466
506
  }
@@ -582,14 +622,14 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
582
622
  return await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_loadKeyringEncryptionKey).call(this, encKey);
583
623
  }
584
624
  /**
585
- * Refresh expired nodeAuthTokens using the stored refresh token.
625
+ * Refresh expired nodeAuthTokens, accessToken, and metadataAccessToken using the stored refresh token.
586
626
  *
587
627
  * This method retrieves the refresh token from the vault and uses it to obtain
588
628
  * new nodeAuthTokens when the current ones have expired.
589
629
  *
590
630
  * @returns A promise that resolves to the new nodeAuthTokens.
591
631
  */
592
- async refreshNodeAuthTokens() {
632
+ async refreshAuthTokens() {
593
633
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
594
634
  const { refreshToken } = this.state;
595
635
  try {
@@ -597,10 +637,12 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
597
637
  connection: this.state.authConnection,
598
638
  refreshToken,
599
639
  });
600
- const { idTokens } = res;
640
+ const { idTokens, accessToken, metadataAccessToken } = res;
601
641
  // re-authenticate with the new id tokens to set new node auth tokens
602
642
  await this.authenticate({
603
643
  idTokens,
644
+ accessToken,
645
+ metadataAccessToken,
604
646
  authConnection: this.state.authConnection,
605
647
  authConnectionId: this.state.authConnectionId,
606
648
  groupedAuthConnectionId: this.state.groupedAuthConnectionId,
@@ -656,6 +698,34 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
656
698
  // check if the token is expired
657
699
  return decodedToken.exp < Date.now() / 1000;
658
700
  }
701
+ checkMetadataAccessTokenExpired() {
702
+ try {
703
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
704
+ const { metadataAccessToken } = this.state;
705
+ if (!metadataAccessToken) {
706
+ return true; // Consider missing token as expired
707
+ }
708
+ const decodedToken = this.decodeJWTToken(metadataAccessToken);
709
+ return decodedToken.exp < Math.floor(Date.now() / 1000);
710
+ }
711
+ catch {
712
+ return true; // Consider unauthenticated user as having expired tokens
713
+ }
714
+ }
715
+ checkAccessTokenExpired() {
716
+ try {
717
+ __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_assertIsAuthenticatedUser).call(this, this.state);
718
+ const { accessToken } = this.state;
719
+ if (!accessToken) {
720
+ return true; // Consider missing token as expired
721
+ }
722
+ const decodedToken = this.decodeJWTToken(accessToken);
723
+ return decodedToken.exp < Math.floor(Date.now() / 1000);
724
+ }
725
+ catch {
726
+ return true; // Consider unauthenticated user as having expired tokens
727
+ }
728
+ }
659
729
  /**
660
730
  * Decode the node auth token from base64 to json object.
661
731
  *
@@ -665,6 +735,30 @@ class SeedlessOnboardingController extends base_controller_1.BaseController {
665
735
  decodeNodeAuthToken(token) {
666
736
  return JSON.parse((0, utils_2.bytesToUtf8)((0, utils_1.base64ToBytes)(token)));
667
737
  }
738
+ /**
739
+ * Decode JWT token
740
+ *
741
+ * @param token - The JWT token to decode.
742
+ * @returns The decoded JWT token.
743
+ */
744
+ decodeJWTToken(token) {
745
+ // JWT tokens have 3 parts separated by dots: header.payload.signature
746
+ const parts = token.split('.');
747
+ if (parts.length !== 3) {
748
+ throw new Error('Invalid JWT token format');
749
+ }
750
+ // Decode the payload (second part)
751
+ const payload = parts[1];
752
+ // Add padding if needed for base64 decoding
753
+ const paddedPayload = payload + '='.repeat((4 - (payload.length % 4)) % 4);
754
+ try {
755
+ const decoded = JSON.parse(Buffer.from(paddedPayload, 'base64').toString());
756
+ return decoded;
757
+ }
758
+ catch (error) {
759
+ throw new Error(`Failed to decode JWT token: ${error instanceof Error ? error.message : String(error)}`);
760
+ }
761
+ }
668
762
  }
669
763
  exports.SeedlessOnboardingController = SeedlessOnboardingController;
670
764
  _SeedlessOnboardingController_vaultEncryptor = new WeakMap(), _SeedlessOnboardingController_controllerOperationMutex = new WeakMap(), _SeedlessOnboardingController_vaultOperationMutex = new WeakMap(), _SeedlessOnboardingController_refreshJWTToken = new WeakMap(), _SeedlessOnboardingController_revokeRefreshToken = new WeakMap(), _SeedlessOnboardingController_isUnlocked = new WeakMap(), _SeedlessOnboardingController_instances = new WeakSet(), _SeedlessOnboardingController_submitGlobalPassword =
@@ -919,7 +1013,6 @@ async function _SeedlessOnboardingController_encryptAndStoreSecretData(params) {
919
1013
  * @param password - The optional password to unlock the vault.
920
1014
  * @param encryptionKey - The optional encryption key to unlock the vault.
921
1015
  * @returns A promise that resolves to an object containing:
922
- * - nodeAuthTokens: Authentication tokens to communicate with the TOPRF service
923
1016
  * - toprfEncryptionKey: The decrypted TOPRF encryption key
924
1017
  * - toprfAuthKeyPair: The decrypted TOPRF authentication key pair
925
1018
  * @throws {Error} If:
@@ -962,20 +1055,19 @@ async function _SeedlessOnboardingController_unlockVaultAndGetBackupEncKey(passw
962
1055
  updatedState.vaultEncryptionKey = vaultEncryptionKey;
963
1056
  updatedState.vaultEncryptionSalt = vaultEncryptionSalt;
964
1057
  }
965
- const { nodeAuthTokens, toprfEncryptionKey, toprfPwEncryptionKey, toprfAuthKeyPair, revokeToken, } = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_parseVaultData).call(this, decryptedVaultData);
966
- // update the state with the restored nodeAuthTokens
1058
+ const { toprfEncryptionKey, toprfPwEncryptionKey, toprfAuthKeyPair, revokeToken, accessToken, } = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_parseVaultData).call(this, decryptedVaultData);
967
1059
  this.update((state) => {
968
- state.nodeAuthTokens = nodeAuthTokens;
969
1060
  state.vaultEncryptionKey = updatedState.vaultEncryptionKey;
970
1061
  state.vaultEncryptionSalt = updatedState.vaultEncryptionSalt;
971
1062
  state.revokeToken = revokeToken;
1063
+ state.accessToken = accessToken;
972
1064
  });
973
1065
  return {
974
- nodeAuthTokens,
975
1066
  toprfEncryptionKey,
976
1067
  toprfPwEncryptionKey,
977
1068
  toprfAuthKeyPair,
978
1069
  revokeToken,
1070
+ accessToken,
979
1071
  };
980
1072
  });
981
1073
  }, _SeedlessOnboardingController_withPersistedSecretMetadataBackupsState =
@@ -1051,14 +1143,17 @@ async function _SeedlessOnboardingController_createNewVaultWithAuthData({ passwo
1051
1143
  if (!this.state.revokeToken) {
1052
1144
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidRevokeToken);
1053
1145
  }
1146
+ if (!this.state.accessToken) {
1147
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidAccessToken);
1148
+ }
1054
1149
  __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_setUnlocked).call(this);
1055
1150
  const { toprfEncryptionKey, toprfPwEncryptionKey, toprfAuthKeyPair } = __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_serializeKeyData).call(this, rawToprfEncryptionKey, rawToprfPwEncryptionKey, rawToprfAuthKeyPair);
1056
1151
  const serializedVaultData = JSON.stringify({
1057
- authTokens: this.state.nodeAuthTokens,
1058
1152
  toprfEncryptionKey,
1059
1153
  toprfPwEncryptionKey,
1060
1154
  toprfAuthKeyPair,
1061
1155
  revokeToken: this.state.revokeToken,
1156
+ accessToken: this.state.accessToken,
1062
1157
  });
1063
1158
  await __classPrivateFieldGet(this, _SeedlessOnboardingController_instances, "m", _SeedlessOnboardingController_updateVault).call(this, {
1064
1159
  password,
@@ -1157,11 +1252,11 @@ async function _SeedlessOnboardingController_withVaultLock(callback) {
1157
1252
  pk: (0, utils_1.base64ToBytes)(parsedToprfAuthKeyPair.pk),
1158
1253
  };
1159
1254
  return {
1160
- nodeAuthTokens: parsedVaultData.authTokens,
1161
1255
  toprfEncryptionKey: rawToprfEncryptionKey,
1162
1256
  toprfPwEncryptionKey: rawToprfPwEncryptionKey,
1163
1257
  toprfAuthKeyPair: rawToprfAuthKeyPair,
1164
1258
  revokeToken: parsedVaultData.revokeToken,
1259
+ accessToken: parsedVaultData.accessToken,
1165
1260
  };
1166
1261
  }, _SeedlessOnboardingController_assertIsUnlocked = function _SeedlessOnboardingController_assertIsUnlocked() {
1167
1262
  if (!__classPrivateFieldGet(this, _SeedlessOnboardingController_isUnlocked, "f")) {
@@ -1186,6 +1281,10 @@ async function _SeedlessOnboardingController_withVaultLock(callback) {
1186
1281
  if (!('refreshToken' in value) || typeof value.refreshToken !== 'string') {
1187
1282
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidRefreshToken);
1188
1283
  }
1284
+ if (!('metadataAccessToken' in value) ||
1285
+ typeof value.metadataAccessToken !== 'string') {
1286
+ throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.InvalidMetadataAccessToken);
1287
+ }
1189
1288
  }, _SeedlessOnboardingController_assertIsSRPBackedUpUser = function _SeedlessOnboardingController_assertIsSRPBackedUpUser(value) {
1190
1289
  if (!this.state.authPubKey) {
1191
1290
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.SRPNotBackedUpError);
@@ -1212,8 +1311,6 @@ async function _SeedlessOnboardingController_assertPasswordInSync(options) {
1212
1311
  // value is not valid vault data if any of the following conditions are true:
1213
1312
  if (!value || // value is not defined
1214
1313
  typeof value !== 'object' || // value is not an object
1215
- !('authTokens' in value) || // authTokens is not defined
1216
- typeof value.authTokens !== 'object' || // authTokens is not an object
1217
1314
  !('toprfEncryptionKey' in value) || // toprfEncryptionKey is not defined
1218
1315
  typeof value.toprfEncryptionKey !== 'string' || // toprfEncryptionKey is not a string
1219
1316
  !('toprfPwEncryptionKey' in value) || // toprfPwEncryptionKey is not defined
@@ -1221,7 +1318,9 @@ async function _SeedlessOnboardingController_assertPasswordInSync(options) {
1221
1318
  !('toprfAuthKeyPair' in value) || // toprfAuthKeyPair is not defined
1222
1319
  typeof value.toprfAuthKeyPair !== 'string' || // toprfAuthKeyPair is not a string
1223
1320
  !('revokeToken' in value) || // revokeToken is not defined
1224
- typeof value.revokeToken !== 'string' // revokeToken is not a string
1321
+ typeof value.revokeToken !== 'string' || // revokeToken is not a string
1322
+ !('accessToken' in value) || // accessToken is not defined
1323
+ typeof value.accessToken !== 'string' // accessToken is not a string
1225
1324
  ) {
1226
1325
  throw new Error(constants_1.SeedlessOnboardingControllerErrorMessage.VaultDataError);
1227
1326
  }
@@ -1248,9 +1347,13 @@ async function _SeedlessOnboardingController_executeWithTokenRefresh(operation,
1248
1347
  try {
1249
1348
  // proactively check for expired tokens and refresh them if needed
1250
1349
  const isNodeAuthTokenExpired = this.checkNodeAuthTokenExpired();
1251
- if (isNodeAuthTokenExpired) {
1350
+ const isMetadataAccessTokenExpired = this.checkMetadataAccessTokenExpired();
1351
+ const isAccessTokenExpired = this.checkAccessTokenExpired();
1352
+ if (isNodeAuthTokenExpired ||
1353
+ isMetadataAccessTokenExpired ||
1354
+ isAccessTokenExpired) {
1252
1355
  log(`JWT token expired during ${operationName}, attempting to refresh tokens`, 'node auth token exp check');
1253
- await this.refreshNodeAuthTokens();
1356
+ await this.refreshAuthTokens();
1254
1357
  }
1255
1358
  return await operation();
1256
1359
  }
@@ -1260,7 +1363,7 @@ async function _SeedlessOnboardingController_executeWithTokenRefresh(operation,
1260
1363
  log(`Token expired during ${operationName}, attempting to refresh tokens`, error);
1261
1364
  try {
1262
1365
  // Refresh the tokens
1263
- await this.refreshNodeAuthTokens();
1366
+ await this.refreshAuthTokens();
1264
1367
  // Retry the operation with fresh tokens
1265
1368
  return await operation();
1266
1369
  }