-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathbackupKeyUtils.ts
More file actions
187 lines (173 loc) · 6.31 KB
/
Copy pathbackupKeyUtils.ts
File metadata and controls
187 lines (173 loc) · 6.31 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import localStorageService from 'app/core/services/local-storage.service';
import notificationsService, { ToastType } from 'app/notifications/services/notifications.service';
import { saveAs } from 'file-saver';
import { ChangePasswordWithLinkPayload } from '@internxt/sdk';
import { getKeys } from 'app/crypto/services/keys.service';
import { encryptText, encryptTextWithKey, passToHash } from 'app/crypto/services/utils';
import { validateMnemonic } from 'bip39';
import { encryptMessageWithPublicKey, hybridEncryptMessageWithPublicKey } from '../crypto/services/pgp.service';
/**
* Interface representing the backup data structure
* @interface BackupData
* @property {string} mnemonic - The user's mnemonic phrase
* @property {string} privateKey - The user's private key
* @property {Object} keys - The user's encryption keys
* @property {string} keys.ecc - The user's ECC private key
* @property {string} keys.kyber - The user's Kyber private key
*/
export interface BackupData {
mnemonic: string;
privateKey: string;
userUuid?: string;
keys: {
ecc: string;
kyber: string;
};
}
/**
* Downloads the backup key of the user and shows a notification
* @param {Function} translate - Translation function to localize notification messages
* @returns {void}
* @throws {Error} Implicitly throws if file saving fails
*/
export function handleExportBackupKey(translate) {
const mnemonic = localStorageService.get('xMnemonic');
const user = localStorageService.getUser();
if (!mnemonic || !user) {
notificationsService.show({
text: translate('views.account.tabs.security.backupKey.error'),
type: ToastType.Error,
});
} else {
const backupData: BackupData = {
mnemonic,
privateKey: user.privateKey,
userUuid: user.uuid,
keys: {
ecc: user.keys?.ecc?.privateKey || user.privateKey,
kyber: user.keys?.kyber?.privateKey || '',
},
};
const backupContent = JSON.stringify(backupData, null, 2);
saveAs(new Blob([backupContent], { type: 'text/plain' }), 'INTERNXT-BACKUP-KEY.txt');
notificationsService.show({
text: translate('views.account.tabs.security.backupKey.success'),
type: ToastType.Success,
});
}
}
/**
* Detects if a backup key file is in the old format (only mnemonic) or new format (has private keys)
*
* @param {string} backupKeyContent - The content of the backup key file to analyze
* @returns {Object} Format detection result
* @returns {('old'|'new')} return.type - The format type: 'old' for plain mnemonic, 'new' for JSON with keys
* @returns {string} return.mnemonic - The extracted mnemonic phrase
* @returns {BackupData} [return.backupData] - The full backup data (only for 'new' format)
* @throws {Error} If the backup key format is invalid or cannot be parsed
*/
export const detectBackupKeyFormat = (
backupKeyContent: string,
): { type: 'old' | 'new'; mnemonic: string; backupData?: BackupData } => {
try {
const parsedData = JSON.parse(backupKeyContent);
if (
parsedData &&
parsedData.mnemonic &&
parsedData.privateKey &&
parsedData.keys &&
parsedData.keys.ecc &&
parsedData.keys.kyber
) {
const backupData: BackupData = {
mnemonic: parsedData.mnemonic,
privateKey: parsedData.privateKey,
userUuid: parsedData.userUuid || undefined,
keys: {
ecc: parsedData.keys.ecc,
kyber: parsedData.keys.kyber,
},
};
return {
type: 'new',
mnemonic: parsedData.mnemonic,
backupData,
};
}
} catch (err) {
// Not JSON, might be an old format (just plain mnemonic)
}
const trimmedContent = backupKeyContent.trim();
if (validateMnemonic(trimmedContent)) {
return {
type: 'old',
mnemonic: trimmedContent,
};
}
throw new Error('Invalid backup key format');
};
/**
* Prepares the payload in the format required by the backend for account recovery
* using the old backup format (mnemonic only)
*
* @param {Object} params - The parameters object
* @param {string} params.mnemonic - The mnemonic phrase from the backup key
* @param {string} params.password - The new password for the account
* @param {string} params.token - The recovery token provided by the system
* @returns {Promise<ChangePasswordWithLinkPayload>} Promise with the recovery payload formatted for the backend
* @throws {Error} If the mnemonic is invalid or encryption fails
*/
export const prepareOldBackupRecoverPayloadForBackend = async ({
mnemonic,
password,
token,
}: {
mnemonic: string;
password: string;
token: string;
}): Promise<ChangePasswordWithLinkPayload> => {
if (!validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic in backup key');
}
try {
const hashObj = passToHash({ password });
const encryptedPassword = encryptText(hashObj.hash);
const encryptedSalt = encryptText(hashObj.salt);
const encryptedMnemonic = encryptTextWithKey(mnemonic, password);
const generatedKeys = await getKeys(password);
const eccPublicKeyInBase64 = generatedKeys.publicKey;
const kyberPublicKeyInBase64 = generatedKeys.kyber.publicKey;
const eccEncryptedMnemonic = await encryptMessageWithPublicKey({
message: mnemonic,
publicKeyInBase64: generatedKeys.publicKey,
});
const base64EccEncryptedMnemonic = btoa(eccEncryptedMnemonic as string);
const hybridEncryptedMnemonic = await hybridEncryptMessageWithPublicKey({
message: mnemonic,
publicKeyInBase64: eccPublicKeyInBase64,
publicKyberKeyBase64: kyberPublicKeyInBase64 as string,
});
return {
token,
encryptedPassword: encryptedPassword,
encryptedSalt: encryptedSalt,
encryptedMnemonic: encryptedMnemonic,
eccEncryptedMnemonic: base64EccEncryptedMnemonic,
kyberEncryptedMnemonic: hybridEncryptedMnemonic,
keys: {
ecc: {
public: generatedKeys.ecc?.publicKey,
private: generatedKeys.ecc?.privateKeyEncrypted,
revocationKey: generatedKeys.revocationCertificate,
},
kyber: {
public: generatedKeys.kyber.publicKey as string,
private: generatedKeys.kyber.privateKeyEncrypted as string,
},
},
};
} catch (error) {
console.error('Error preparing recovery payload:', error);
throw new Error('Error preparing recovery payload');
}
};