Repository: MetaMask/metamask-mobile (2025-07-25T15:51:21Z)

Issue:
[Bug]: Should allow user to change the password if the login failed many times with the validation and another device is still logged in state
### Describe the bug

When user have 2 devices,
user try to log in device A, but failed many times and it's locked by the validation
user use the device B, try to change the password but got it's failed to change

### Expected behavior

Should be able to change the password in that case

### Screenshots/Recordings

_No response_

### Steps to reproduce

Logged in in device A
In device B, input incorrect password many times to see the validation
in device A, try to change the password
get the error to change

### Error messages or log output

```shell

```

### Detection stage

On main branch

### Version

7.52.0

### Build type

Beta

### Device

All

### Operating system

iOS, Android

### Additional context

_No response_

### Severity

_No response_


Discussion:
Hi @ieow,

This issue has been closed. Please complete this RCA form:
https://docs.google.com/forms/d/e/1FAIpQLSdnPbJISzFlR_aQD2uRpnMKSoGAopuTd_yeZK7J4Q5GzgbsOA/viewform?entry.340898780=17655&entry.1228565618=regression-develop&entry.1698032149=team-web3auth

<!-- AUTO-FORM -->



Patch:
diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts
index a2d117cc7645..f9fb2aac3188 100644
--- a/app/core/Authentication/Authentication.ts
+++ b/app/core/Authentication/Authentication.ts
@@ -107,6 +107,9 @@ class AuthenticationService {
     await KeyringController.submitPassword(password);
     if (selectSeedlessOnboardingLoginFlow(ReduxService.store.getState())) {
       await SeedlessOnboardingController.submitPassword(password);
+      SeedlessOnboardingController.revokeRefreshToken(password).catch((err) => {
+        Logger.error(err, 'Failed to revoke refresh token');
+      });
     }
     password = this.wipeSensitiveData();
   };
@@ -1064,6 +1067,11 @@ class AuthenticationService {
       });
       await KeyringController.changePassword(globalPassword);
       await this.syncKeyringEncryptionKey();
+      SeedlessOnboardingController.revokeRefreshToken(globalPassword).catch(
+        (err) => {
+          Logger.error(err, 'Failed to revoke refresh token');
+        },
+      );
     } catch (err) {
       // lock app again on error after submitPassword succeeded
       await this.lockApp({ locked: true });
diff --git a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts
index 6fc83c50ecc0..4062968c04d3 100644
--- a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts
+++ b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts
@@ -51,7 +51,7 @@ export const seedlessOnboardingControllerInit: ControllerInitFunction<
       ) => Promise<unknown>,
     },
     network: web3AuthNetwork as Web3AuthNetwork,
-
+    passwordOutdatedCacheTTL: 15_000, // 15 seconds
     refreshJWTToken: AuthTokenHandler.refreshJWTToken,
     revokeRefreshToken: AuthTokenHandler.revokeRefreshToken,
   });
diff --git a/package.json b/package.json
index e6504fe715ca..c1bef859263f 100644
--- a/package.json
+++ b/package.json
@@ -248,7 +248,7 @@
     "@metamask/scure-bip39": "^2.1.0",
     "@metamask/sdk-analytics": "0.0.5",
     "@metamask/sdk-communication-layer": "0.33.0",
-    "@metamask/seedless-onboarding-controller": "2.4.0",
+    "@metamask/seedless-onboarding-controller": "2.5.0",
     "@metamask/selected-network-controller": "^22.1.0",
     "@metamask/signature-controller": "^30.0.0",
     "@metamask/slip44": "^4.2.0",
diff --git a/yarn.lock b/yarn.lock
index f2163d132d1f..bdbaca48c137 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5955,14 +5955,14 @@
     utf-8-validate "^5.0.2"
     uuid "^8.3.2"
 
-"@metamask/seedless-onboarding-controller@2.4.0":
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/@metamask/seedless-onboarding-controller/-/seedless-onboarding-controller-2.4.0.tgz#d98f86d0090f088b148b4c33ada20438527c7bbf"
-  integrity sha512-k2FeV9LxrT0nzTHWWkz6bsdzlMLS4Tb4Kb4Q7rtOFETS/qldlLIjeqTasxUJT2U3RPsEvtALGS8tMM2ScHmgZA==
+"@metamask/seedless-onboarding-controller@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@metamask/seedless-onboarding-controller/-/seedless-onboarding-controller-2.5.0.tgz#3af69838242799515b5844da6db6edbf822e717d"
+  integrity sha512-0L/GrEhBbqhcSUzhnsP8lADr5udNkfn83P7f0IRT+XPGC1nvOekwI4HofhYxkHiCVHqm1mxUyfS33AE6oMgaiA==
   dependencies:
     "@metamask/auth-network-utils" "^0.3.0"
     "@metamask/base-controller" "^8.0.1"
-    "@metamask/toprf-secure-backup" "^0.7.0"
+    "@metamask/toprf-secure-backup" "^0.7.1"
     "@metamask/utils" "^11.4.2"
     async-mutex "^0.5.0"
 
@@ -6252,10 +6252,10 @@
     "@metamask/base-controller" "^8.0.0"
     "@metamask/utils" "^11.2.0"
 
-"@metamask/toprf-secure-backup@^0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@metamask/toprf-secure-backup/-/toprf-secure-backup-0.7.0.tgz#a81be8c4e93fdbd5ddb4b486ad5a0393e9d7e9af"
-  integrity sha512-hRYkUw7DbgxZE2GjY4mKCpa8buH3fGyrN9NqXVPrQCZto7VNbLr6qUrsJbOrWsdHQAAS+h9y+VxCLonyw0CPkg==
+"@metamask/toprf-secure-backup@^0.7.1":
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/@metamask/toprf-secure-backup/-/toprf-secure-backup-0.7.1.tgz#8d29d3af5e05417f641a4883c09b1120e82c307b"
+  integrity sha512-q2twPlzY/gJyAyx0sxqcv6BbA1A5/7w67ptKcpfMZf9odvwZwDfVzweNe7Dx3yWxlALlpOnI8nw9SrrsXu1cVw==
   dependencies:
     "@metamask/auth-network-utils" "^0.3.1"
     "@noble/ciphers" "^1.2.1"


Test patch:
diff --git a/app/core/Authentication/Authentication.test.ts b/app/core/Authentication/Authentication.test.ts
index 3e26dd902ba0..ad7b50f42111 100644
--- a/app/core/Authentication/Authentication.test.ts
+++ b/app/core/Authentication/Authentication.test.ts
@@ -1484,6 +1484,7 @@ describe('Authentication', () => {
         loadKeyringEncryptionKey: jest.fn(),
         submitGlobalPassword: jest.fn(),
         fetchAllSecretData: jest.fn(),
+        revokeRefreshToken: jest.fn().mockResolvedValue(undefined),
       } as unknown as SeedlessOnboardingController<EncryptionKey>;
 
       jest.spyOn(Authentication, 'resetPassword');
@@ -1932,6 +1933,7 @@ describe('Authentication', () => {
         state: { vault: 'existing vault data' },
         submitPassword: jest.fn(),
         checkIsPasswordOutdated: jest.fn(),
+        revokeRefreshToken: jest.fn().mockResolvedValue(undefined),
       } as unknown as SeedlessOnboardingController<EncryptionKey>;
       Engine.context.KeyringController = {
         submitPassword: jest.fn(),