Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / keys / reactivation / reactivateKeysProcessV2.ts
blobb1ecab3261fe4d19cbc802106902fa583a165be3
1 import type { PrivateKeyReference } from '@proton/crypto';
2 import { CryptoProxy } from '@proton/crypto';
3 import { getDefaultKeyFlags } from '@proton/shared/lib/keys';
5 import { getApiError } from '../../api/helpers/apiErrorHelper';
6 import { reactivateUserKeyRouteV2, reactiveLegacyAddressKeyRouteV2 } from '../../api/keys';
7 import { HTTP_STATUS_CODE } from '../../constants';
8 import type {
9     ActiveKey,
10     Address,
11     Api,
12     DecryptedKey,
13     KeyTransparencyVerify,
14     Address as tsAddress,
15     User as tsUser,
16 } from '../../interfaces';
17 import type { SimpleMap } from '../../interfaces/utils';
18 import { generateAddressKeyTokens } from '../addressKeys';
19 import {
20     getActiveKeyObject,
21     getActiveKeys,
22     getNormalizedActiveKeys,
23     getPrimaryFlag,
24     getReactivatedKeyFlag,
25 } from '../getActiveKeys';
26 import { getDecryptedAddressKeysHelper } from '../getDecryptedAddressKeys';
27 import { getPrimaryKey } from '../getPrimaryKey';
28 import { getHasMigratedAddressKey } from '../keyMigration';
29 import { getSignedKeyListWithDeferredPublish } from '../signedKeyList';
30 import type { KeyReactivationData, KeyReactivationRecord, OnKeyReactivationCallback } from './interface';
31 import { getAddressReactivationPayload, getReactivatedAddressesKeys, resetUserId } from './reactivateKeyHelper';
33 interface ReactivateUserKeysArguments {
34     addressRecordsInV2Format: KeyReactivationRecord[];
35     api: Api;
36     addresses: Address[];
37     user: tsUser;
38     activeKeys: ActiveKey[];
39     onReactivation: OnKeyReactivationCallback;
40     keysToReactivate: KeyReactivationData[];
41     keyPassword: string;
42     keyTransparencyVerify: KeyTransparencyVerify;
45 export const reactivateUserKeys = async ({
46     addressRecordsInV2Format,
47     api,
48     addresses,
49     user,
50     activeKeys,
51     keysToReactivate,
52     onReactivation,
53     keyPassword,
54     keyTransparencyVerify,
55 }: ReactivateUserKeysArguments) => {
56     const keyReactivationDataMap = addressRecordsInV2Format.reduce<SimpleMap<KeyReactivationData>>((acc, record) => {
57         record.keysToReactivate.forEach((keyData) => {
58             acc[keyData.Key.ID] = keyData;
59         });
60         return acc;
61     }, {});
62     const reactivatedAddressKeysMap: SimpleMap<boolean> = {};
63     const allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
65     let mutableActiveKeys = activeKeys;
66     let mutableAddresses = addresses;
68     for (const keyToReactivate of keysToReactivate) {
69         const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
70         const { ID } = Key;
71         try {
72             if (!reactivatedKey) {
73                 throw new Error('Missing key');
74             }
76             await resetUserId(Key, reactivatedKey);
78             const privateKeyArmored = await CryptoProxy.exportPrivateKey({
79                 privateKey: reactivatedKey,
80                 passphrase: keyPassword,
81             });
82             const newActiveKey = await getActiveKeyObject(reactivatedKey, {
83                 ID,
84                 primary: getPrimaryFlag(mutableActiveKeys),
85                 flags: getDefaultKeyFlags(undefined),
86             });
87             const updatedActiveKeys = getNormalizedActiveKeys(undefined, [...mutableActiveKeys, newActiveKey]);
89             const reactivatedAddressKeysResult = await getReactivatedAddressesKeys({
90                 addresses,
91                 oldUserKeys: mutableActiveKeys,
92                 newUserKeys: updatedActiveKeys,
93                 user,
94                 keyPassword,
95                 keyTransparencyVerify,
96             });
97             const addressReactivationPayload = getAddressReactivationPayload(reactivatedAddressKeysResult);
98             mutableAddresses = mutableAddresses.map((address) => {
99                 const updatedSignedKeyList = addressReactivationPayload.SignedKeyLists[address.ID];
100                 if (updatedSignedKeyList) {
101                     return {
102                         ...address,
103                         SignedKeyList: {
104                             ...updatedSignedKeyList,
105                             MinEpochID: null,
106                             MaxEpochID: null,
107                         },
108                     };
109                 }
110                 return address;
111             });
112             await api(
113                 reactivateUserKeyRouteV2({
114                     ID,
115                     PrivateKey: privateKeyArmored,
116                     ...addressReactivationPayload,
117                 })
118             );
119             // Only once the SKLs have been successfully posted we add it to the KT commit state.
120             await Promise.all(
121                 reactivatedAddressKeysResult.map(({ onSKLPublishSuccess }) =>
122                     onSKLPublishSuccess ? onSKLPublishSuccess() : Promise.resolve()
123                 )
124             );
126             mutableActiveKeys = updatedActiveKeys;
128             onReactivation(id, 'ok');
130             // Notify all the address keys that got reactivated from this user key
131             reactivatedAddressKeysResult.forEach(({ reactivatedKeys }) => {
132                 reactivatedKeys?.forEach(({ ID }) => {
133                     allReactivatedAddressKeysMap[ID] = true;
134                     const reactivationData = keyReactivationDataMap[ID];
135                     if (reactivationData) {
136                         onReactivation(reactivationData.id, 'ok');
137                         reactivatedAddressKeysMap[reactivationData.id] = true;
138                     }
139                 });
140             });
141         } catch (e: any) {
142             onReactivation(id, e);
143             const { status } = getApiError(e);
144             if (status === HTTP_STATUS_CODE.FORBIDDEN) {
145                 // The password prompt has been cancelled. No need to attempt to reactivate the other keys.
146                 break;
147             }
148         }
149     }
151     addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
152         keysToReactivate.forEach(({ id, privateKey }) => {
153             if (!reactivatedAddressKeysMap[id] && !privateKey) {
154                 onReactivation(id, new Error('User key inactivate'));
155             }
156         });
157     });
159     return {
160         userKeys: mutableActiveKeys,
161         addresses: mutableAddresses,
162         allReactivatedAddressKeysMap,
163     };
166 interface ReactivateAddressKeysV2Arguments {
167     api: Api;
168     address: Address;
169     activeKeys: ActiveKey[];
170     userKey: PrivateKeyReference;
171     onReactivation: OnKeyReactivationCallback;
172     keysToReactivate: KeyReactivationData[];
173     keyTransparencyVerify: KeyTransparencyVerify;
176 export const reactivateAddressKeysV2 = async ({
177     api,
178     address,
179     activeKeys,
180     keysToReactivate,
181     onReactivation,
182     userKey,
183     keyTransparencyVerify,
184 }: ReactivateAddressKeysV2Arguments) => {
185     let mutableActiveKeys = activeKeys;
187     for (const keyToReactivate of keysToReactivate) {
188         const { id, Key, privateKey: reactivatedKey } = keyToReactivate;
189         const { ID, Flags } = Key;
190         try {
191             if (!reactivatedKey) {
192                 throw new Error('Missing key');
193             }
195             await resetUserId(Key, reactivatedKey);
197             const { token, encryptedToken, signature } = await generateAddressKeyTokens(userKey);
198             const privateKeyArmored = await CryptoProxy.exportPrivateKey({
199                 privateKey: reactivatedKey,
200                 passphrase: token,
201             });
202             const newActiveKey = await getActiveKeyObject(reactivatedKey, {
203                 ID,
204                 primary: getPrimaryFlag(mutableActiveKeys),
205                 flags: getReactivatedKeyFlag(address, Flags),
206             });
207             const updatedActiveKeys = getNormalizedActiveKeys(address, [...mutableActiveKeys, newActiveKey]);
208             const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
209                 updatedActiveKeys,
210                 address,
211                 keyTransparencyVerify
212             );
213             await api(
214                 reactiveLegacyAddressKeyRouteV2({
215                     ID,
216                     PrivateKey: privateKeyArmored,
217                     SignedKeyList: signedKeyList,
218                     Token: encryptedToken,
219                     Signature: signature,
220                 })
221             );
222             await onSKLPublishSuccess();
224             mutableActiveKeys = updatedActiveKeys;
226             onReactivation(id, 'ok');
227         } catch (e: any) {
228             onReactivation(id, e);
229         }
230     }
232     return mutableActiveKeys;
235 export interface ReactivateKeysProcessV2Arguments {
236     api: Api;
237     user: tsUser;
238     keyPassword: string;
239     addresses: tsAddress[];
240     userKeys: DecryptedKey[];
241     keyReactivationRecords: KeyReactivationRecord[];
242     onReactivation: OnKeyReactivationCallback;
243     keyTransparencyVerify: KeyTransparencyVerify;
246 const reactivateKeysProcessV2 = async ({
247     api,
248     user,
249     keyReactivationRecords,
250     onReactivation,
251     keyPassword,
252     addresses: oldAddresses,
253     userKeys: oldUserKeys,
254     keyTransparencyVerify,
255 }: ReactivateKeysProcessV2Arguments) => {
256     const { userRecord, addressRecordsInV2Format, addressRecordsInLegacyFormatOrWithBackup } =
257         keyReactivationRecords.reduce<{
258             addressRecordsInV2Format: KeyReactivationRecord[];
259             addressRecordsInLegacyFormatOrWithBackup: KeyReactivationRecord[];
260             userRecord?: KeyReactivationRecord;
261         }>(
262             (acc, record) => {
263                 const { user, address, keysToReactivate } = record;
264                 if (user) {
265                     acc.userRecord = record;
266                 }
267                 if (address) {
268                     const keysInV2Format = keysToReactivate.filter(
269                         ({ privateKey, Key }) => !privateKey && getHasMigratedAddressKey(Key)
270                     );
271                     const keysInLegacyFormatOrWithBackup = keysToReactivate.filter(
272                         ({ privateKey, Key }) => privateKey || !getHasMigratedAddressKey(Key)
273                     );
275                     if (keysInV2Format.length) {
276                         acc.addressRecordsInV2Format.push({
277                             address,
278                             keysToReactivate: keysInV2Format,
279                         });
280                     }
282                     if (keysInLegacyFormatOrWithBackup.length) {
283                         acc.addressRecordsInLegacyFormatOrWithBackup.push({
284                             address,
285                             keysToReactivate: keysInLegacyFormatOrWithBackup,
286                         });
287                     }
288                 }
289                 return acc;
290             },
292             { addressRecordsInV2Format: [], addressRecordsInLegacyFormatOrWithBackup: [], userRecord: undefined }
293         );
295     let userKeys = oldUserKeys;
296     let addresses = oldAddresses;
297     let allReactivatedAddressKeysMap: SimpleMap<boolean> = {};
298     if (userRecord) {
299         try {
300             const activeUserKeys = await getActiveKeys(undefined, null, user.Keys, userKeys);
301             const userKeysReactivationResult = await reactivateUserKeys({
302                 api,
303                 user,
304                 activeKeys: activeUserKeys,
305                 addresses: oldAddresses,
306                 keysToReactivate: userRecord.keysToReactivate,
307                 onReactivation,
308                 keyPassword,
309                 addressRecordsInV2Format,
310                 keyTransparencyVerify,
311             });
312             userKeys = userKeysReactivationResult.userKeys;
313             addresses = userKeysReactivationResult.addresses;
314             allReactivatedAddressKeysMap = userKeysReactivationResult.allReactivatedAddressKeysMap;
315         } catch (e: any) {
316             userRecord.keysToReactivate.forEach(({ id }) => onReactivation(id, e));
317             addressRecordsInV2Format.forEach(({ keysToReactivate }) => {
318                 keysToReactivate.forEach(({ id }) => onReactivation(id, e));
319             });
320         }
321     }
323     const primaryPrivateUserKey = getPrimaryKey(userKeys)?.privateKey;
325     for (const { address: oldAddress, keysToReactivate } of addressRecordsInLegacyFormatOrWithBackup) {
326         const keysLeftToReactivate: KeyReactivationData[] = [];
327         keysToReactivate.forEach((x) => {
328             // Even if this key was uploaded, it may have gotten reactivated from a user key earlier, and so it
329             // should not be attempted to be reactivated again
330             const alreadyReactivated = allReactivatedAddressKeysMap[x.Key.ID] === true;
331             if (alreadyReactivated) {
332                 onReactivation(x.id, 'ok');
333             } else {
334                 keysLeftToReactivate.push(x);
335             }
336         });
337         try {
338             const address = addresses.find(({ ID: otherID }) => oldAddress?.ID === otherID);
339             if (!address || !primaryPrivateUserKey) {
340                 throw new Error('Missing dependency');
341             }
343             const addressKeys = await getDecryptedAddressKeysHelper(address.Keys, user, userKeys, '');
344             const activeAddressKeys = await getActiveKeys(address, address.SignedKeyList, address.Keys, addressKeys);
346             await reactivateAddressKeysV2({
347                 api,
348                 address,
349                 activeKeys: activeAddressKeys,
350                 userKey: primaryPrivateUserKey,
351                 onReactivation,
352                 keysToReactivate: keysLeftToReactivate,
353                 keyTransparencyVerify,
354             });
355         } catch (e: any) {
356             keysLeftToReactivate.forEach(({ id }) => onReactivation(id, e));
357         }
358     }
361 export default reactivateKeysProcessV2;