-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathbackupVault.ts
More file actions
158 lines (141 loc) · 4.32 KB
/
backupVault.ts
File metadata and controls
158 lines (141 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { KeyringControllerState } from '@metamask/keyring-controller';
import {
getInternetCredentials,
setInternetCredentials,
resetInternetCredentials,
ACCESSIBLE,
type SetOptions,
} from 'react-native-keychain';
import {
VAULT_BACKUP_FAILED,
VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP,
VAULT_BACKUP_KEY,
VAULT_BACKUP_TEMP_KEY,
TEMP_VAULT_BACKUP_FAILED,
} from './constants';
import Logger from '../../util/Logger';
const options: SetOptions = {
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
};
interface KeyringBackupResponse {
success: boolean;
vault?: string;
error?: string;
}
/**
* Removes the primary vault backup from react-native-keychain
*/
const _resetVaultBackup = async (): Promise<void> => {
// Clear existing backup
await resetInternetCredentials(VAULT_BACKUP_KEY);
};
/**
* Removes the temporary vault backup from react-native-keychain
*/
const _resetTemporaryVaultBackup = async (): Promise<void> => {
// Clear temporary backup
await resetInternetCredentials(VAULT_BACKUP_TEMP_KEY);
};
/**
* Clears all vault backups from react-native-keychain
*/
export async function clearAllVaultBackups() {
await _resetVaultBackup();
await _resetTemporaryVaultBackup();
}
/**
* places the vault in react-native-keychain for backup
* @returns Promise<KeyringBackupResponse>
interface KeyringBackupResponse {
success: boolean;
error?: string;
vault?: string;
}
*/
export async function backupVault(
keyringState: KeyringControllerState,
): Promise<KeyringBackupResponse> {
const keyringVault = keyringState.vault as string;
try {
// Does a primary backup exist?
// Wrapped in its own try/catch because Android Keystore key invalidation
// (e.g. biometric enrollment change, Android 16 behavioural change) causes
// getInternetCredentials to throw rather than return false/undefined.
// If the read fails we treat it as "no existing backup" so the fresh
// backup can still be written below.
let existingBackup;
try {
existingBackup = await getInternetCredentials(VAULT_BACKUP_KEY);
} catch (readError) {
Logger.log(
readError,
'backupVault: failed to read existing backup, proceeding with fresh backup',
);
}
// An existing backup exists, backup it to the temp key
if (existingBackup && existingBackup.password) {
const existingVault = existingBackup.password;
// Clear any existing temporary backup
await _resetTemporaryVaultBackup();
// Then back up a secondary copy of the vault
const tempBackupResult = await setInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
VAULT_BACKUP_TEMP_KEY,
existingVault,
options,
);
// Temporary vault backup failed, throw error
if (!tempBackupResult) {
throw new Error(TEMP_VAULT_BACKUP_FAILED);
}
}
// Clear any existing vault backup first to prevent "item already exists" errors
await _resetVaultBackup();
// Backup primary vault
const backupResult = await setInternetCredentials(
VAULT_BACKUP_KEY,
VAULT_BACKUP_KEY,
keyringVault,
options,
);
// Vault backup failed, throw error
if (!backupResult) {
throw new Error(VAULT_BACKUP_FAILED);
}
// Clear the temporary backup
await _resetTemporaryVaultBackup();
return {
success: true,
vault: keyringState.vault,
};
} catch (error) {
Logger.error(error as Error, 'Vault backup failed');
return {
success: false,
error: error instanceof Error ? error.message : VAULT_BACKUP_FAILED,
};
}
}
/**
* retrieves the vault backup from react-native-keychain
* @returns Promise<KeyringBackupResponse>
interface KeyringBackupResponse {
success: boolean;
error?: string;
vault?: string;
}
*/
export async function getVaultFromBackup(): Promise<KeyringBackupResponse> {
const primaryVaultCredentials =
await getInternetCredentials(VAULT_BACKUP_KEY);
if (primaryVaultCredentials) {
return { success: true, vault: primaryVaultCredentials.password };
}
const temporaryVaultCredentials = await getInternetCredentials(
VAULT_BACKUP_TEMP_KEY,
);
if (temporaryVaultCredentials) {
return { success: true, vault: temporaryVaultCredentials.password };
}
return { success: false, error: VAULT_FAILED_TO_GET_VAULT_FROM_BACKUP };
}