Skip to content

fix: back up logic to clear vault before reapplying #14743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions app/components/hooks/DeleteWallet/useDeleteWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Logger from '../../../util/Logger';
import { EXISTING_USER } from '../../../constants/storage';
import { Authentication } from '../../../core';
import AUTHENTICATION_TYPE from '../../../constants/userProperties';
import { resetVaultBackup } from '../../../core/BackupVault/backupVault';
import { clearAllVaultBackups } from '../../../core/BackupVault';
import { useMetrics } from '../useMetrics';

const useDeleteWallet = () => {
Expand All @@ -14,7 +14,7 @@ const useDeleteWallet = () => {
await Authentication.newWalletAndKeychain(`${Date.now()}`, {
currentAuthType: AUTHENTICATION_TYPE.UNKNOWN,
});
await resetVaultBackup();
await clearAllVaultBackups();
await Authentication.lockApp();
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
5 changes: 0 additions & 5 deletions app/constants/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ export const AUTHENTICATION_LOGIN_VAULT_CREATION_FAILED =
export const VAULT_CREATION_ERROR = 'Error creating the vault';
export const NO_VAULT_IN_BACKUP_ERROR = 'No vault in backup';

// backupVault
export const VAULT_BACKUP_FAILED = 'Vault backup failed';
export const VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP =
'getVaultFromBackup failed to retrieve vault';

// RPCMethodMiddleware
export const TOKEN_NOT_SUPPORTED_FOR_NETWORK =
'This token is not supported on this network';
Expand Down
277 changes: 245 additions & 32 deletions app/core/BackupVault/backupVault.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
import { VAULT_BACKUP_FAILED } from '../../constants/error';
import { backupVault } from './backupVault';
import {
VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP,
VAULT_BACKUP_KEY,
VAULT_BACKUP_TEMP_KEY,
} from './constants';
import {
backupVault,
getVaultFromBackup,
clearAllVaultBackups,
} from './backupVault';
import { KeyringControllerState } from '@metamask/keyring-controller';
import { setInternetCredentials } from 'react-native-keychain';
import {
getInternetCredentials,
Options,
resetInternetCredentials,
Result,
setInternetCredentials,
} from 'react-native-keychain';

let mockKeychainState: Record<string, { username: string; password: string }> =
{};

// Mock the react-native-keychain module
jest.mock('react-native-keychain', () => ({
...jest.requireActual('react-native-keychain'),
setInternetCredentials: jest.fn(
async (
server: string,
username: string,
password: string,
_?: Options,
): Promise<Result> => {
mockKeychainState[server] = { username, password };
return {
service: 'service',
storage: 'storage',
};
},
),
getInternetCredentials: jest.fn(
async (server: string) => mockKeychainState[server],
),
resetInternetCredentials: jest.fn(
async (server: string, _?: Options) => delete mockKeychainState[server],
),
}));

//TODO Mock the react-native-keychain module test the other functions inside backupVault
/*
Expand All @@ -12,40 +54,211 @@ import { setInternetCredentials } from 'react-native-keychain';
Documentation for the testing react-native-keychain can be found here: https://github.com/oblador/react-native-keychain#unit-testing-with-jest
More information on the issue can be found here: https://github.com/oblador/react-native-keychain/issues/460
*/
describe('backupVault', () => {
it('should throw when vault backup fails', async () => {
// Mock the setInternetCredentials function to return false, which simulates a failed vault backup
(setInternetCredentials as jest.Mock).mockResolvedValue(false);

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};

expect(async () => await backupVault(keyringState)).rejects.toThrow(
VAULT_BACKUP_FAILED,
);
describe('backupVault file', () => {
const dummyPassword = 'dummy-password';

beforeEach(() => {
jest.clearAllMocks();
mockKeychainState = {};
});

describe('clearAllVaultBackups', () => {
it('should clear all vault backups', async () => {
await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
dummyPassword,
);

await setInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
VAULT_BACKUP_TEMP_KEY,
dummyPassword,
);

const primaryVaultCredentials = await getInternetCredentials(
VAULT_BACKUP_KEY,
);

const temporaryVaultCredentials = await getInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
);

expect(primaryVaultCredentials).toEqual({
username: VAULT_BACKUP_KEY,
password: dummyPassword,
});

expect(temporaryVaultCredentials).toEqual({
username: VAULT_BACKUP_TEMP_KEY,
password: dummyPassword,
});

await clearAllVaultBackups();

const primaryVaultCredentialsAfterReset = await getInternetCredentials(
VAULT_BACKUP_KEY,
);

const temporaryVaultCredentialsAfterReset = await getInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
);

expect(primaryVaultCredentialsAfterReset).toBeUndefined();
expect(temporaryVaultCredentialsAfterReset).toBeUndefined();
});
});

it('should return success response when vault backup succeeds', async () => {
const mockedSuccessResponse = { success: true };
describe('backupVault', () => {
it('should throw error and skip primary backup if failed to backup temporary vault', async () => {
const mockedFailedResponse = {
error: 'Failed to backup temporary vault',
success: false,
};

// Populate primary vault backup
await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
dummyPassword,
);

// Mock the setInternetCredentials function to return false, which simulates a failed vault backup
(setInternetCredentials as jest.Mock).mockImplementationOnce(() => false);

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};

const response = await backupVault(keyringState);

expect(response).toEqual(mockedFailedResponse);
});

it('should throw error when primary vault backup fails', async () => {
const mockedFailedResponse = {
error: 'Vault backup failed',
success: false,
};

// Mock the setInternetCredentials function to return false, which simulates a failed vault backup
(setInternetCredentials as jest.Mock).mockImplementationOnce(() => false);

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};

const response = await backupVault(keyringState);

expect(response).toEqual(mockedFailedResponse);
});

it('should successfully backup primary vault', async () => {
const mockedSuccessResponse = { success: true };

// Populate primary vault backup
await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
dummyPassword,
);

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};

const response = await backupVault(keyringState);

expect(response).toEqual(mockedSuccessResponse);
});

it('should reset vault before backup', async () => {
const mockedSuccessResponse = { success: true };

await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
dummyPassword,
);

const internetCredentialsBeforeReset = await getInternetCredentials(
VAULT_BACKUP_KEY,
);

expect(internetCredentialsBeforeReset).toEqual({
username: VAULT_BACKUP_KEY,
password: dummyPassword,
});

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};

const response = await backupVault(keyringState);

// First reset temporary, then primary, then temporary again
expect(resetInternetCredentials).toHaveBeenCalledTimes(3);

expect(response).toEqual(mockedSuccessResponse);
});
});

describe('getVaultFromBackup', () => {
it('should successfully get primary vault from backup', async () => {
const mockedSuccessResponse = { success: true, vault: dummyPassword };

await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
dummyPassword,
);

const response = await getVaultFromBackup();

expect(response).toEqual(mockedSuccessResponse);
});

it('should successfully get temporary vault from backup if primary vault does not exist', async () => {
const tempDummyPassword = 'temp-dummy-password';

const mockedSuccessResponse = { success: true, vault: tempDummyPassword };

await setInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
VAULT_BACKUP_TEMP_KEY,
tempDummyPassword,
);

const response = await getVaultFromBackup();

expect(response).toEqual(mockedSuccessResponse);
});

// Mock the setInternetCredentials function to return a success response, which simulates a successful vault backup
(setInternetCredentials as jest.Mock).mockResolvedValue(
mockedSuccessResponse,
);
it('should return error when vault backup fails', async () => {
const mockedFailedResponse = {
error: VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP,
success: false,
};

const keyringState: KeyringControllerState = {
vault: undefined,
keyrings: [],
isUnlocked: false,
keyringsMetadata: [],
};
(getInternetCredentials as jest.Mock).mockImplementationOnce(
() => undefined,
);

const response = await backupVault(keyringState);
const response = await getVaultFromBackup();

expect(response).toEqual(mockedSuccessResponse);
expect(response).toEqual(mockedFailedResponse);
});
});
});
Loading
Loading