Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/seedless-onboarding-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Revert `revokeToken` value as optional in `authenticate` method. ([#7012](https://github.com/MetaMask/core/pull/7012))
- Renamed `checkIsSeedlessOnboardingUserAuthenticated` to `getIsUserAuthenticated`. ([#7012](https://github.com/MetaMask/core/pull/7012))

### Fixed

- Fixed `InvalidRevokeToken` issue in `refreshAuthTokens` method. ([#7012](https://github.com/MetaMask/core/pull/7012))

## [6.0.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1083,56 +1083,42 @@ describe('SeedlessOnboardingController', () => {
}),
},
async ({ controller }) => {
expect(
await controller.checkIsSeedlessOnboardingUserAuthenticated(),
).toBe(true);
expect(await controller.getIsUserAuthenticated()).toBe(true);
},
);
});

it('should return false if the user is not authenticated (accessToken is missing)', async () => {
await withController(
{
state: {
userId,
authConnectionId,
groupedAuthConnectionId,
metadataAccessToken,
revokeToken,
},
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withoutMockAccessToken: true, // missing accessToken
}),
},
async ({ controller }) => {
expect(
await controller.checkIsSeedlessOnboardingUserAuthenticated(),
).toBe(false);
expect(await controller.getIsUserAuthenticated()).toBe(false);
},
);
});

it('should return false if the user is not authenticated (revokeToken is missing)', async () => {
await withController(
{
state: {
userId,
authConnectionId,
groupedAuthConnectionId,
metadataAccessToken,
accessToken,
},
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withoutMockRevokeToken: true, // missing revokeToken
}),
},
async ({ controller }) => {
expect(
await controller.checkIsSeedlessOnboardingUserAuthenticated(),
).toBe(false);
expect(await controller.getIsUserAuthenticated()).toBe(false);
},
);
});

it('should return false if the user is not authenticated (social login details are missing)', async () => {
await withController(async ({ controller }) => {
expect(
await controller.checkIsSeedlessOnboardingUserAuthenticated(),
).toBe(false);
expect(await controller.getIsUserAuthenticated()).toBe(false);
});
});
});
Expand Down Expand Up @@ -1288,6 +1274,7 @@ describe('SeedlessOnboardingController', () => {
pwEncKey,
authKeyPair,
MOCK_PASSWORD,
controller.state.revokeToken,
);

const expectedVaultValue = await encryptor.decrypt(
Expand All @@ -1299,7 +1286,7 @@ describe('SeedlessOnboardingController', () => {
controller.state.vault as string,
);

expect(expectedVaultValue).toStrictEqual(resultedVaultValue);
expect(resultedVaultValue).toStrictEqual(expectedVaultValue);

// should be able to get the hash of the seed phrase backup from the state
expect(
Expand Down Expand Up @@ -1377,6 +1364,36 @@ describe('SeedlessOnboardingController', () => {
);
});

it('should throw error if revokeToken is missing when creating new vault', async () => {
await withController(
{
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withMockAuthPubKey: true,
withoutMockRevokeToken: true,
}),
},
async ({ controller, toprfClient }) => {
mockcreateLocalKey(toprfClient, MOCK_PASSWORD);

// persist the local enc key
jest.spyOn(toprfClient, 'persistLocalKey').mockResolvedValueOnce();

// encrypt and store the secret data
handleMockSecretDataAdd();
await expect(
controller.createToprfKeyAndBackupSeedPhrase(
MOCK_PASSWORD,
MOCK_SEED_PHRASE,
MOCK_KEYRING_ID,
),
).rejects.toThrow(
SeedlessOnboardingControllerErrorMessage.InvalidRevokeToken,
);
},
);
});

it('should throw an error if create encryption key fails', async () => {
await withController(
{
Expand Down Expand Up @@ -2084,27 +2101,6 @@ describe('SeedlessOnboardingController', () => {
);
});

it('should throw an error if the user is not authenticated', async () => {
await withController(
{
state: {
userId,
authConnectionId,
groupedAuthConnectionId,
metadataAccessToken,
refreshToken,
revokeToken,
nodeAuthTokens: MOCK_NODE_AUTH_TOKENS,
},
},
async ({ controller }) => {
await expect(controller.fetchAllSecretData()).rejects.toThrow(
SeedlessOnboardingControllerErrorMessage.InvalidAccessToken,
);
},
);
});

it('should be able to fetch seed phrases with cached encryption key without providing password', async () => {
const mockToprfEncryptor = createMockToprfEncryptor();

Expand Down Expand Up @@ -3839,6 +3835,7 @@ describe('SeedlessOnboardingController', () => {
initialPwEncKey,
initialAuthKeyPair,
OLD_PASSWORD,
revokeToken,
);

MOCK_VAULT = mockResult.encryptedMockVault;
Expand Down Expand Up @@ -5331,6 +5328,24 @@ describe('SeedlessOnboardingController', () => {
});
});

it('should return true if access token is missing', async () => {
await withController(
{
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withoutMockAccessToken: true,
}),
},
async ({ controller }) => {
// Restore the original implementation to test the real logic
jest.spyOn(controller, 'checkAccessTokenExpired').mockRestore();

const result = controller.checkAccessTokenExpired();
expect(result).toBe(true);
},
);
});

it('should return true if token has invalid format', async () => {
await withController(
{
Expand All @@ -5350,6 +5365,117 @@ describe('SeedlessOnboardingController', () => {
});
});

describe('#getAccessTokenAndRevokeToken', () => {
const MOCK_PASSWORD = 'mock-password';

it('should retrieve the access token and revoke token from the vault if it is not available in the state', async () => {
const mockToprfEncryptor = createMockToprfEncryptor();
const MOCK_ENCRYPTION_KEY =
mockToprfEncryptor.deriveEncKey(MOCK_PASSWORD);
const MOCK_PASSWORD_ENCRYPTION_KEY =
mockToprfEncryptor.derivePwEncKey(MOCK_PASSWORD);
const MOCK_AUTH_KEY_PAIR =
mockToprfEncryptor.deriveAuthKeyPair(MOCK_PASSWORD);

const mockResult = await createMockVault(
MOCK_ENCRYPTION_KEY,
MOCK_PASSWORD_ENCRYPTION_KEY,
MOCK_AUTH_KEY_PAIR,
MOCK_PASSWORD,
);

const MOCK_VAULT = mockResult.encryptedMockVault;
const MOCK_VAULT_ENCRYPTION_KEY = mockResult.vaultEncryptionKey;
const MOCK_VAULT_ENCRYPTION_SALT = mockResult.vaultEncryptionSalt;

await withController(
{
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withoutMockAccessToken: true,
withoutMockRevokeToken: true,
vault: MOCK_VAULT,
vaultEncryptionKey: MOCK_VAULT_ENCRYPTION_KEY,
vaultEncryptionSalt: MOCK_VAULT_ENCRYPTION_SALT,
}),
},
async ({ controller, toprfClient }) => {
// fetch and decrypt the secret data
mockRecoverEncKey(toprfClient, MOCK_PASSWORD);

// mock the secret data get
jest
.spyOn(toprfClient, 'fetchAllSecretDataItems')
.mockResolvedValueOnce([
stringToBytes(
JSON.stringify({
data: bytesToBase64(MOCK_SEED_PHRASE),
timestamp: 1234567890,
type: SecretType.Mnemonic,
version: 'v1',
}),
),
stringToBytes(
JSON.stringify({
data: bytesToBase64(MOCK_PRIVATE_KEY),
timestamp: 1234567890,
type: SecretType.PrivateKey,
version: 'v1',
}),
),
]);

const secretData = await controller.fetchAllSecretData(MOCK_PASSWORD);
expect(secretData).toBeDefined();
expect(secretData).toHaveLength(2);
expect(secretData[0].type).toStrictEqual(SecretType.Mnemonic);
expect(secretData[0].data).toStrictEqual(MOCK_SEED_PHRASE);
expect(secretData[1].type).toStrictEqual(SecretType.PrivateKey);
expect(secretData[1].data).toStrictEqual(MOCK_PRIVATE_KEY);

// expect(mockSecretDataGet.isDone()).toBe(true);
},
);
});

it('should throw error if access token is not available either in the state or the vault', async () => {
await withController(
{
state: getMockInitialControllerState({
withMockAuthenticatedUser: true,
withoutMockAccessToken: true,
}),
},
async ({ controller, toprfClient }) => {
// assert that the vault is not available in the state
expect(controller.state.vault).toBeUndefined();

// fetch and decrypt the secret data
mockRecoverEncKey(toprfClient, MOCK_PASSWORD);

jest
.spyOn(toprfClient, 'fetchAllSecretDataItems')
.mockResolvedValueOnce([
stringToBytes(
JSON.stringify({
data: 'value',
timestamp: 1234567890,
type: 'mnemonic',
version: 'v1',
}),
),
]);

await expect(
controller.fetchAllSecretData(MOCK_PASSWORD),
).rejects.toThrow(
SeedlessOnboardingControllerErrorMessage.InvalidAccessToken,
);
},
);
});
});

describe('renewRefreshToken', () => {
const MOCK_PASSWORD = 'mock-password';
const MOCK_REVOKE_TOKEN = 'newRevokeToken';
Expand Down
Loading
Loading