diff --git a/packages/seedless-onboarding-controller/CHANGELOG.md b/packages/seedless-onboarding-controller/CHANGELOG.md index 8fabfcf2ae6..9e13c5a3275 100644 --- a/packages/seedless-onboarding-controller/CHANGELOG.md +++ b/packages/seedless-onboarding-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6504](https://github.com/MetaMask/core/pull/6504)) + ## [4.0.0] ### Added diff --git a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts index 5cd8662d01b..d058088eb61 100644 --- a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts +++ b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts @@ -1,5 +1,8 @@ import { keccak256AndHexify } from '@metamask/auth-network-utils'; -import type { Messenger } from '@metamask/base-controller'; +import { + deriveStateFromMetadata, + type Messenger, +} from '@metamask/base-controller'; import type { EncryptionKey } from '@metamask/browser-passworder'; import { encrypt, @@ -5432,4 +5435,240 @@ describe('SeedlessOnboardingController', () => { ); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', async () => { + await withController( + { + state: { + accessToken: 'accessToken', + authPubKey: 'authPubKey', + authConnection: AuthConnection.Google, + authConnectionId: 'authConnectionId', + encryptedKeyringEncryptionKey: 'encryptedKeyringEncryptionKey', + encryptedSeedlessEncryptionKey: 'encryptedSeedlessEncryptionKey', + groupedAuthConnectionId: 'groupedAuthConnectionId', + isSeedlessOnboardingUserAuthenticated: true, + metadataAccessToken: 'metadataAccessToken', + nodeAuthTokens: [], + passwordOutdatedCache: { + isExpiredPwd: false, + timestamp: 1234567890, + }, + pendingToBeRevokedTokens: [ + { refreshToken: 'refreshToken', revokeToken: 'revokeToken' }, + ], + refreshToken: 'refreshToken', + revokeToken: 'revokeToken', + socialBackupsMetadata: [], + socialLoginEmail: 'socialLoginEmail', + userId: 'userId', + vault: 'vault', + vaultEncryptionKey: 'vaultEncryptionKey', + vaultEncryptionSalt: 'vaultEncryptionSalt', + }, + }, + ({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(` + Object { + "authConnection": "google", + "authConnectionId": "authConnectionId", + "groupedAuthConnectionId": "groupedAuthConnectionId", + "isSeedlessOnboardingUserAuthenticated": false, + "passwordOutdatedCache": Object { + "isExpiredPwd": false, + "timestamp": 1234567890, + }, + } + `); + }, + ); + }); + + it('includes expected state in state logs', async () => { + await withController( + { + state: { + accessToken: 'accessToken', + authPubKey: 'authPubKey', + authConnection: AuthConnection.Google, + authConnectionId: 'authConnectionId', + encryptedKeyringEncryptionKey: 'encryptedKeyringEncryptionKey', + encryptedSeedlessEncryptionKey: 'encryptedSeedlessEncryptionKey', + groupedAuthConnectionId: 'groupedAuthConnectionId', + isSeedlessOnboardingUserAuthenticated: true, + metadataAccessToken: 'metadataAccessToken', + nodeAuthTokens: [], + passwordOutdatedCache: { + isExpiredPwd: false, + timestamp: 1234567890, + }, + pendingToBeRevokedTokens: [ + { refreshToken: 'refreshToken', revokeToken: 'revokeToken' }, + ], + refreshToken: 'refreshToken', + revokeToken: 'revokeToken', + socialBackupsMetadata: [], + socialLoginEmail: 'socialLoginEmail', + userId: 'userId', + vault: 'vault', + vaultEncryptionKey: 'vaultEncryptionKey', + vaultEncryptionSalt: 'vaultEncryptionSalt', + }, + }, + ({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "accessToken": true, + "authConnection": "google", + "authConnectionId": "authConnectionId", + "authPubKey": "authPubKey", + "groupedAuthConnectionId": "groupedAuthConnectionId", + "isSeedlessOnboardingUserAuthenticated": false, + "metadataAccessToken": true, + "nodeAuthTokens": true, + "passwordOutdatedCache": Object { + "isExpiredPwd": false, + "timestamp": 1234567890, + }, + "pendingToBeRevokedTokens": true, + "refreshToken": true, + "revokeToken": true, + "userId": "userId", + } + `); + }, + ); + }); + + it('persists expected state', async () => { + await withController( + { + state: { + accessToken: 'accessToken', + authPubKey: 'authPubKey', + authConnection: AuthConnection.Google, + authConnectionId: 'authConnectionId', + encryptedKeyringEncryptionKey: 'encryptedKeyringEncryptionKey', + encryptedSeedlessEncryptionKey: 'encryptedSeedlessEncryptionKey', + groupedAuthConnectionId: 'groupedAuthConnectionId', + isSeedlessOnboardingUserAuthenticated: true, + metadataAccessToken: 'metadataAccessToken', + nodeAuthTokens: [], + passwordOutdatedCache: { + isExpiredPwd: false, + timestamp: 1234567890, + }, + pendingToBeRevokedTokens: [ + { refreshToken: 'refreshToken', revokeToken: 'revokeToken' }, + ], + refreshToken: 'refreshToken', + revokeToken: 'revokeToken', + socialBackupsMetadata: [], + socialLoginEmail: 'socialLoginEmail', + userId: 'userId', + vault: 'vault', + vaultEncryptionKey: 'vaultEncryptionKey', + vaultEncryptionSalt: 'vaultEncryptionSalt', + }, + }, + ({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "authConnection": "google", + "authConnectionId": "authConnectionId", + "authPubKey": "authPubKey", + "encryptedKeyringEncryptionKey": "encryptedKeyringEncryptionKey", + "encryptedSeedlessEncryptionKey": "encryptedSeedlessEncryptionKey", + "groupedAuthConnectionId": "groupedAuthConnectionId", + "isSeedlessOnboardingUserAuthenticated": false, + "metadataAccessToken": "metadataAccessToken", + "nodeAuthTokens": Array [], + "passwordOutdatedCache": Object { + "isExpiredPwd": false, + "timestamp": 1234567890, + }, + "pendingToBeRevokedTokens": Array [ + Object { + "refreshToken": "refreshToken", + "revokeToken": "revokeToken", + }, + ], + "refreshToken": "refreshToken", + "socialBackupsMetadata": Array [], + "socialLoginEmail": "socialLoginEmail", + "userId": "userId", + "vault": "vault", + } + `); + }, + ); + }); + + it('exposes expected state to UI', async () => { + await withController( + { + state: { + accessToken: 'accessToken', + authPubKey: 'authPubKey', + authConnection: AuthConnection.Google, + authConnectionId: 'authConnectionId', + encryptedKeyringEncryptionKey: 'encryptedKeyringEncryptionKey', + encryptedSeedlessEncryptionKey: 'encryptedSeedlessEncryptionKey', + groupedAuthConnectionId: 'groupedAuthConnectionId', + isSeedlessOnboardingUserAuthenticated: true, + metadataAccessToken: 'metadataAccessToken', + nodeAuthTokens: [], + passwordOutdatedCache: { + isExpiredPwd: false, + timestamp: 1234567890, + }, + pendingToBeRevokedTokens: [ + { refreshToken: 'refreshToken', revokeToken: 'revokeToken' }, + ], + refreshToken: 'refreshToken', + revokeToken: 'revokeToken', + socialBackupsMetadata: [], + socialLoginEmail: 'socialLoginEmail', + userId: 'userId', + vault: 'vault', + vaultEncryptionKey: 'vaultEncryptionKey', + vaultEncryptionSalt: 'vaultEncryptionSalt', + }, + }, + ({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "authConnection": "google", + "socialLoginEmail": "socialLoginEmail", + } + `); + }, + ); + }); + }); }); diff --git a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts index ce5cec7d0d6..b06b377a956 100644 --- a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts +++ b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts @@ -11,7 +11,11 @@ import { TOPRFErrorCode, TOPRFError, } from '@metamask/toprf-secure-backup'; -import { base64ToBytes, bytesToBase64 } from '@metamask/utils'; +import { + base64ToBytes, + bytesToBase64, + isNullOrUndefined, +} from '@metamask/utils'; import { gcm } from '@noble/ciphers/aes'; import { bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils'; import { managedNonce } from '@noble/ciphers/webcrypto'; @@ -93,87 +97,131 @@ export function getInitialSeedlessOnboardingControllerStateWithDefaults( const seedlessOnboardingMetadata: StateMetadata = { vault: { + includeInStateLogs: false, persist: true, anonymous: false, + usedInUi: false, }, socialBackupsMetadata: { + includeInStateLogs: false, persist: true, anonymous: false, + usedInUi: false, }, nodeAuthTokens: { + includeInStateLogs: (nodeAuthTokens) => + !isNullOrUndefined(nodeAuthTokens), persist: true, anonymous: false, + usedInUi: false, }, authConnection: { + includeInStateLogs: true, persist: true, anonymous: true, + usedInUi: true, }, authConnectionId: { + includeInStateLogs: true, persist: true, anonymous: true, + usedInUi: false, }, groupedAuthConnectionId: { + includeInStateLogs: true, persist: true, anonymous: true, + usedInUi: false, }, userId: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: false, }, socialLoginEmail: { + includeInStateLogs: false, persist: true, anonymous: false, + usedInUi: true, }, vaultEncryptionKey: { + includeInStateLogs: false, persist: false, anonymous: false, + usedInUi: false, }, vaultEncryptionSalt: { + includeInStateLogs: false, persist: false, anonymous: false, + usedInUi: false, }, authPubKey: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: false, }, passwordOutdatedCache: { + includeInStateLogs: true, persist: true, anonymous: true, + usedInUi: false, }, refreshToken: { + includeInStateLogs: (refreshToken) => !isNullOrUndefined(refreshToken), persist: true, anonymous: false, + usedInUi: false, }, revokeToken: { + includeInStateLogs: (revokeToken) => !isNullOrUndefined(revokeToken), persist: false, anonymous: false, + usedInUi: false, }, pendingToBeRevokedTokens: { + includeInStateLogs: (pendingToBeRevokedTokens) => + !isNullOrUndefined(pendingToBeRevokedTokens) && + pendingToBeRevokedTokens.length > 0, persist: true, anonymous: false, + usedInUi: false, }, // stays in vault accessToken: { + includeInStateLogs: (accessToken) => !isNullOrUndefined(accessToken), persist: false, anonymous: false, + usedInUi: false, }, // stays outside of vault as this token is accessed by the metadata service // before the vault is created or unlocked. metadataAccessToken: { + includeInStateLogs: (metadataAccessToken) => + !isNullOrUndefined(metadataAccessToken), persist: true, anonymous: false, + usedInUi: false, }, encryptedSeedlessEncryptionKey: { + includeInStateLogs: false, persist: true, anonymous: false, + usedInUi: false, }, encryptedKeyringEncryptionKey: { + includeInStateLogs: false, persist: true, anonymous: false, + usedInUi: false, }, isSeedlessOnboardingUserAuthenticated: { + includeInStateLogs: true, persist: true, anonymous: true, + usedInUi: false, }, }; diff --git a/packages/shield-controller/CHANGELOG.md b/packages/shield-controller/CHANGELOG.md index bb9d5311d67..f0ec19a0273 100644 --- a/packages/shield-controller/CHANGELOG.md +++ b/packages/shield-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6504](https://github.com/MetaMask/core/pull/6504)) + ### Changed - Bump `@metamask/base-controller` from `^8.2.0` to `^8.3.0` ([#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/shield-controller/src/ShieldController.test.ts b/packages/shield-controller/src/ShieldController.test.ts index 760f6a208a6..99c48de3d2c 100644 --- a/packages/shield-controller/src/ShieldController.test.ts +++ b/packages/shield-controller/src/ShieldController.test.ts @@ -1,3 +1,4 @@ +import { deriveStateFromMetadata } from '@metamask/base-controller'; import type { TransactionControllerState } from '@metamask/transaction-controller'; import { ShieldController } from './ShieldController'; @@ -142,4 +143,68 @@ describe('ShieldController', () => { expect(backend.checkCoverage).toHaveBeenCalledWith(txMeta); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const { controller } = setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', async () => { + const { controller } = setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "coverageResults": Object {}, + "orderedTransactionHistory": Array [], + } + `); + }); + + it('persists expected state', async () => { + const { controller } = setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "coverageResults": Object {}, + "orderedTransactionHistory": Array [], + } + `); + }); + + it('exposes expected state to UI', async () => { + const { controller } = setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "coverageResults": Object {}, + } + `); + }); + }); }); diff --git a/packages/shield-controller/src/ShieldController.ts b/packages/shield-controller/src/ShieldController.ts index bbfb790ea61..af642ab9962 100644 --- a/packages/shield-controller/src/ShieldController.ts +++ b/packages/shield-controller/src/ShieldController.ts @@ -101,12 +101,16 @@ export type ShieldControllerMessenger = RestrictedMessenger< */ const metadata = { coverageResults: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, }, orderedTransactionHistory: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: false, }, }; diff --git a/packages/subscription-controller/CHANGELOG.md b/packages/subscription-controller/CHANGELOG.md index fc4fb2f3eea..b7483921596 100644 --- a/packages/subscription-controller/CHANGELOG.md +++ b/packages/subscription-controller/CHANGELOG.md @@ -16,5 +16,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `getPricing` method ([#6356](https://github.com/MetaMask/core/pull/6356)) - Add methods `startSubscriptionWithCrypto` and `getCryptoApproveTransactionParams` method ([#6456](https://github.com/MetaMask/core/pull/6456)) - Added `triggerAccessTokenRefresh` to trigger an access token refresh ([#6374](https://github.com/MetaMask/core/pull/6374)) +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6504](https://github.com/MetaMask/core/pull/6504)) [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/subscription-controller/src/SubscriptionController.test.ts b/packages/subscription-controller/src/SubscriptionController.test.ts index feca9b5c8e2..49655b76af9 100644 --- a/packages/subscription-controller/src/SubscriptionController.test.ts +++ b/packages/subscription-controller/src/SubscriptionController.test.ts @@ -1,4 +1,4 @@ -import { Messenger } from '@metamask/base-controller'; +import { deriveStateFromMetadata, Messenger } from '@metamask/base-controller'; import { controllerName, @@ -819,4 +819,66 @@ describe('SubscriptionController', () => { }); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', async () => { + await withController(({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + }); + + it('includes expected state in state logs', async () => { + await withController(({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "subscriptions": Array [], + } + `); + }); + }); + + it('persists expected state', async () => { + await withController(({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "subscriptions": Array [], + } + `); + }); + }); + + it('exposes expected state to UI', async () => { + await withController(({ controller }) => { + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "subscriptions": Array [], + } + `); + }); + }); + }); }); diff --git a/packages/subscription-controller/src/SubscriptionController.ts b/packages/subscription-controller/src/SubscriptionController.ts index 6853f380e93..bbc5d0a6cef 100644 --- a/packages/subscription-controller/src/SubscriptionController.ts +++ b/packages/subscription-controller/src/SubscriptionController.ts @@ -133,8 +133,10 @@ export function getDefaultSubscriptionControllerState(): SubscriptionControllerS const subscriptionControllerMetadata: StateMetadata = { subscriptions: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, }, };