Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / keyTransparency / useRunSelfAudit.ts
blob082aa2baad6d20152988cc9792ccb9f88db7af13
1 import { useCallback } from 'react';
3 import { useGetAddressKeys } from '@proton/account/addressKeys/hooks';
4 import { useGetAddresses } from '@proton/account/addresses/hooks';
5 import { useGetUser } from '@proton/account/user/hooks';
6 import { useGetUserKeys } from '@proton/account/userKeys/hooks';
7 import useApi from '@proton/components/hooks/useApi';
8 import useConfig from '@proton/components/hooks/useConfig';
9 import useEventManager from '@proton/components/hooks/useEventManager';
10 import { CryptoProxy, serverTime } from '@proton/crypto';
11 import type { SelfAuditResult } from '@proton/key-transparency/lib';
12 import {
13     StaleEpochError,
14     getKTLocalStorage,
15     getSelfAuditInterval,
16     ktSentryReportError,
17     selfAudit,
18 } from '@proton/key-transparency/lib';
19 import { getSilentApi } from '@proton/shared/lib/api/helpers/customConfig';
20 import { INTERVAL_EVENT_TIMER, MINUTE } from '@proton/shared/lib/constants';
21 import { KEY_TRANSPARENCY_REMINDER_UPDATE } from '@proton/shared/lib/drawer/interfaces';
22 import { wait } from '@proton/shared/lib/helpers/promise';
23 import type { DecryptedAddressKey, KeyPair, SelfAuditState } from '@proton/shared/lib/interfaces';
25 import useGetLatestEpoch from './useGetLatestEpoch';
26 import useReportSelfAuditErrors from './useReportSelfAuditErrors';
28 const SELF_AUDIT_MAX_TRIALS = 6;
30 const ignoreError = (error: any): boolean => {
31     return error instanceof StaleEpochError;
34 const reportError = (error: any, tooManyRetries: boolean) => {
35     if (tooManyRetries || !ignoreError(error)) {
36         ktSentryReportError(error, { context: 'runSelfAudit' });
37     }
40 const useRunSelfAudit = () => {
41     const getUser = useGetUser();
42     const getAddresses = useGetAddresses();
43     const getUserKeys = useGetUserKeys();
44     const getLatestEpoch = useGetLatestEpoch();
45     const normalApi = useApi();
46     const { APP_NAME: appName } = useConfig();
47     const reportSelfAuditErrors = useReportSelfAuditErrors();
48     const getAddressKeys = useGetAddressKeys();
49     const { subscribe } = useEventManager();
51     const createSelfAuditStateUserKeys = useCallback(async (): Promise<KeyPair[]> => {
52         const userKeys = await getUserKeys();
53         const exportedUserKeys = await Promise.all(
54             userKeys.map((key) =>
55                 CryptoProxy.exportPrivateKey({
56                     privateKey: key.privateKey,
57                     passphrase: null,
58                     format: 'binary',
59                 })
60             )
61         );
62         try {
63             const selfAuditKeyReferences = await Promise.all(
64                 exportedUserKeys.map(async (key) => {
65                     const privateKey = await CryptoProxy.importPrivateKey({
66                         binaryKey: key,
67                         passphrase: null,
68                     });
69                     return {
70                         privateKey: privateKey,
71                         publicKey: privateKey,
72                     };
73                 })
74             );
75             return selfAuditKeyReferences;
76         } finally {
77             exportedUserKeys.forEach((privateKey) => privateKey.fill(0));
78         }
79     }, []);
81     const createSelfAuditStateAddressKeys = useCallback(async () => {
82         /*
83          * Wait for the event loop to return an event with no address change
84          * At this point self audit can be sure to have the latest version of addresses.
85          */
86         const waitForAddressUpdates = async () => {
87             let resolve: () => void;
88             const eventPromise = new Promise<void>((_resolve) => (resolve = _resolve));
89             const unsubscribe = subscribe((data) => {
90                 if (!data.Addresses) {
91                     resolve();
92                 }
93             });
94             await Promise.any([eventPromise, wait(5 * INTERVAL_EVENT_TIMER)]);
95             unsubscribe();
96         };
98         await waitForAddressUpdates();
99         const addressesWithoutKeys = await getAddresses();
100         const addressesKeys = await Promise.all(addressesWithoutKeys.map((address) => getAddressKeys(address.ID)));
101         const exportedAddressesKeys = await Promise.all(
102             addressesKeys.map(async (keys) =>
103                 Promise.all(
104                     keys.map(async (key) => {
105                         const privateKey = await CryptoProxy.exportPrivateKey({
106                             privateKey: key.privateKey,
107                             passphrase: null,
108                             format: 'binary',
109                         });
110                         return {
111                             ID: key.ID,
112                             Flags: key.Flags,
113                             Primary: key.Primary,
114                             privateKey,
115                         };
116                     })
117                 )
118             )
119         );
120         try {
121             const selfAuditAddressesKeys = await Promise.all<Promise<DecryptedAddressKey[]>>(
122                 exportedAddressesKeys.map(async (keys) =>
123                     Promise.all(
124                         keys.map(async (key) => {
125                             const privateKey = await CryptoProxy.importPrivateKey({
126                                 binaryKey: key.privateKey,
127                                 passphrase: null,
128                             });
129                             return {
130                                 ID: key.ID,
131                                 Flags: key.Flags,
132                                 Primary: key.Primary,
133                                 privateKey: privateKey,
134                                 publicKey: privateKey,
135                             };
136                         })
137                     )
138                 )
139             );
140             const addresses = addressesWithoutKeys.map((address, index) => {
141                 return {
142                     address: address,
143                     addressKeys: selfAuditAddressesKeys[index],
144                 };
145             });
146             return { addresses };
147         } finally {
148             exportedAddressesKeys.forEach((keys) => keys.forEach(({ privateKey }) => privateKey.fill(0)));
149         }
150     }, []);
152     const createSelfAuditState = useCallback(
153         async (lastSelfAudit: SelfAuditResult | undefined): Promise<SelfAuditState> => {
154             const [userKeys, addressKeys] = await Promise.all([
155                 createSelfAuditStateUserKeys(),
156                 createSelfAuditStateAddressKeys(),
157             ]);
158             return {
159                 userKeys,
160                 lastSelfAudit,
161                 ...addressKeys,
162             };
163         },
164         []
165     );
167     const clearSelfAuditState = useCallback(async (state: SelfAuditState) => {
168         const clearUserKeysPromise = Promise.all(
169             state.userKeys.map(async ({ privateKey }) => CryptoProxy.clearKey({ key: privateKey }))
170         );
171         const clearAddressKeysPromise = Promise.all(
172             state.addresses.map(async ({ addressKeys }) => {
173                 await Promise.all(addressKeys.map(async ({ privateKey }) => CryptoProxy.clearKey({ key: privateKey })));
174             })
175         );
176         await Promise.all([clearUserKeysPromise, clearAddressKeysPromise]);
177     }, []);
179     const runSelfAuditWithState = useCallback(async (state: SelfAuditState) => {
180         const ktLSAPIPromise = getKTLocalStorage(appName);
181         const ktLSAPI = await ktLSAPIPromise;
182         if (state.userKeys.length === 0) {
183             throw new Error('User has no user keys');
184         }
185         try {
186             const selfAuditResult = await selfAudit({
187                 userContext: {
188                     getUser,
189                     getUserKeys,
190                     getAddressKeys,
191                     appName,
192                 },
193                 state,
194                 api: getSilentApi(normalApi),
195                 ktLSAPI,
196                 getLatestEpoch,
197             });
199             // Update local storage value
200             document.dispatchEvent(
201                 new CustomEvent(KEY_TRANSPARENCY_REMINDER_UPDATE, {
202                     detail: {
203                         value: false,
204                     },
205                 })
206             );
208             await reportSelfAuditErrors(selfAuditResult);
209             return { selfAuditResult };
210         } catch (error: any) {
211             const failedTrials = (state.lastSelfAudit?.error?.failedTrials ?? 0) + 1;
212             const tooManyRetries = failedTrials >= SELF_AUDIT_MAX_TRIALS;
213             const currentTime = +serverTime();
214             const selfAuditBaseInterval = getSelfAuditInterval();
215             const nextSelfAuditInterval = tooManyRetries
216                 ? selfAuditBaseInterval
217                 : Math.min(Math.pow(2, failedTrials) * MINUTE, selfAuditBaseInterval);
218             const selfAuditResult: SelfAuditResult = {
219                 auditTime: currentTime,
220                 nextAuditTime: currentTime + nextSelfAuditInterval,
221                 addressAuditResults: [],
222                 localStorageAuditResultsOtherAddress: [],
223                 localStorageAuditResultsOwnAddress: [],
224                 error: { failedTrials: tooManyRetries ? 0 : failedTrials, tooManyRetries },
225             };
226             reportError(error, tooManyRetries);
227             await reportSelfAuditErrors(selfAuditResult);
228             return { selfAuditResult };
229         }
230     }, []);
232     const runSelfAudit = useCallback(async (lastSelfAudit: SelfAuditResult | undefined) => {
233         const state = await createSelfAuditState(lastSelfAudit);
234         try {
235             return await runSelfAuditWithState(state);
236         } finally {
237             await clearSelfAuditState(state);
238         }
239     }, []);
241     return runSelfAudit;
244 export default useRunSelfAudit;