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