Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / calendar / calendarBootstrap / keys.ts
blob122dede297f8410132c27853a9b3aac4e9adb52a
1 import { useCallback } from 'react';
3 import type { UnknownAction } from '@reduxjs/toolkit';
4 import type { Action, ThunkDispatch } from '@reduxjs/toolkit';
5 import type { SessionKey } from 'packages/crypto';
6 import type { ThunkAction } from 'redux-thunk';
8 import { addressKeysThunk } from '@proton/account/addressKeys';
9 import { addressesThunk } from '@proton/account/addresses';
10 import { baseUseDispatch } from '@proton/react-redux-store';
11 import type { ProtonThunkArguments } from '@proton/redux-shared-store-types';
12 import {
13     decryptPassphrase,
14     decryptPassphraseSessionKey,
15     getDecryptedCalendarKeys,
16 } from '@proton/shared/lib/calendar/crypto/keys/calendarKeys';
17 import { getAddressesMembersMap } from '@proton/shared/lib/calendar/crypto/keys/helpers';
18 import type { Address } from '@proton/shared/lib/interfaces';
19 import type { DecryptedCalendarKey, MemberPassphrase } from '@proton/shared/lib/interfaces/calendar';
20 import type { GetAddressKeys } from '@proton/shared/lib/interfaces/hooks/GetAddressKeys';
21 import type { GetDecryptedPassphraseAndCalendarKeys } from '@proton/shared/lib/interfaces/hooks/GetDecryptedPassphraseAndCalendarKeys';
22 import { splitKeys } from '@proton/shared/lib/keys';
23 import noop from '@proton/utils/noop';
25 import type { CalendarsBootstrapState } from './index';
26 import { calendarBootstrapThunk } from './index';
28 interface DecryptedPassphraseAndCalendarKeysResult {
29     decryptedCalendarKeys: DecryptedCalendarKey[];
30     decryptedPassphrase: string;
31     decryptedPassphraseSessionKey: SessionKey;
32     passphraseID: string;
35 const map = new Map<string, Promise<DecryptedPassphraseAndCalendarKeysResult> | undefined>();
37 export const deleteCalendarFromKeyCache = (calendarID: string) => {
38     map.delete(calendarID);
41 const getCalendarKeyPassphrase = async (
42     getAddressKeys: GetAddressKeys,
43     MemberPassphrases: MemberPassphrase[] = [],
44     addressesMembersMap: { [key: string]: Address } = {}
45 ) => {
46     // Try to decrypt each passphrase with the address keys belonging to that member until it succeeds.
47     for (const { Passphrase, Signature, MemberID } of MemberPassphrases) {
48         const Address = addressesMembersMap[MemberID];
49         if (!Address) {
50             continue;
51         }
52         const addressKeys = await getAddressKeys(Address.ID);
53         const [decryptedPassphrase, decryptedPassphraseSessionKey] = await Promise.all([
54             decryptPassphrase({
55                 armoredPassphrase: Passphrase,
56                 armoredSignature: Signature,
57                 ...splitKeys(addressKeys),
58             }).catch(noop),
59             decryptPassphraseSessionKey({ armoredPassphrase: Passphrase, ...splitKeys(addressKeys) }),
60         ]);
62         if (!decryptedPassphrase || !decryptedPassphraseSessionKey) {
63             throw new Error('Error decrypting calendar passphrase');
64         }
66         return { decryptedPassphrase, decryptedPassphraseSessionKey };
67     }
69     return {};
72 export const getDecryptedPassphraseAndCalendarKeysThunk = ({
73     calendarID,
74 }: {
75     calendarID: string;
76 }): ThunkAction<
77     Promise<DecryptedPassphraseAndCalendarKeysResult>,
78     CalendarsBootstrapState,
79     ProtonThunkArguments,
80     UnknownAction
81 > => {
82     const run = async (dispatch: ThunkDispatch<CalendarsBootstrapState, ProtonThunkArguments, UnknownAction>) => {
83         const [{ Keys, Passphrase, Members = [] }, Addresses] = await Promise.all([
84             dispatch(calendarBootstrapThunk({ calendarID })),
85             dispatch(addressesThunk()),
86         ]);
88         const { ID: PassphraseID, MemberPassphrases } = Passphrase;
89         const addressesMembersMap = getAddressesMembersMap(Members, Addresses);
90         const { decryptedPassphrase, decryptedPassphraseSessionKey } = await getCalendarKeyPassphrase(
91             (addressID) => dispatch(addressKeysThunk({ addressID })),
92             MemberPassphrases,
93             addressesMembersMap
94         );
96         if (!decryptedPassphrase || !decryptedPassphraseSessionKey) {
97             throw new Error('No passphrase');
98         }
100         return {
101             decryptedCalendarKeys: await getDecryptedCalendarKeys(Keys, { [PassphraseID]: decryptedPassphrase }),
102             decryptedPassphrase,
103             decryptedPassphraseSessionKey,
104             passphraseID: PassphraseID,
105         };
106     };
108     return async (dispatch) => {
109         const oldPromise = map.get(calendarID);
110         if (oldPromise) {
111             return oldPromise;
112         }
113         const promise = run(dispatch);
114         map.set(calendarID, promise);
115         return promise;
116     };
119 export const useGetDecryptedPassphraseAndCalendarKeys = (): GetDecryptedPassphraseAndCalendarKeys => {
120     const dispatch = baseUseDispatch<ThunkDispatch<CalendarsBootstrapState, ProtonThunkArguments, Action>>();
122     return useCallback((calendarID: string) => {
123         return dispatch(getDecryptedPassphraseAndCalendarKeysThunk({ calendarID }));
124     }, []);
127 export const useGetCalendarKeys = () => {
128     const getDecryptedPassphraseAndCalendarKeys = useGetDecryptedPassphraseAndCalendarKeys();
130     return useCallback(
131         async (calendarID: string) => {
132             const { decryptedCalendarKeys } = await getDecryptedPassphraseAndCalendarKeys(calendarID);
133             return decryptedCalendarKeys;
134         },
135         [getDecryptedPassphraseAndCalendarKeys]
136     );