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';
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;
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 } = {}
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];
52 const addressKeys = await getAddressKeys(Address.ID);
53 const [decryptedPassphrase, decryptedPassphraseSessionKey] = await Promise.all([
55 armoredPassphrase: Passphrase,
56 armoredSignature: Signature,
57 ...splitKeys(addressKeys),
59 decryptPassphraseSessionKey({ armoredPassphrase: Passphrase, ...splitKeys(addressKeys) }),
62 if (!decryptedPassphrase || !decryptedPassphraseSessionKey) {
63 throw new Error('Error decrypting calendar passphrase');
66 return { decryptedPassphrase, decryptedPassphraseSessionKey };
72 export const getDecryptedPassphraseAndCalendarKeysThunk = ({
77 Promise<DecryptedPassphraseAndCalendarKeysResult>,
78 CalendarsBootstrapState,
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()),
88 const { ID: PassphraseID, MemberPassphrases } = Passphrase;
89 const addressesMembersMap = getAddressesMembersMap(Members, Addresses);
90 const { decryptedPassphrase, decryptedPassphraseSessionKey } = await getCalendarKeyPassphrase(
91 (addressID) => dispatch(addressKeysThunk({ addressID })),
96 if (!decryptedPassphrase || !decryptedPassphraseSessionKey) {
97 throw new Error('No passphrase');
101 decryptedCalendarKeys: await getDecryptedCalendarKeys(Keys, { [PassphraseID]: decryptedPassphrase }),
103 decryptedPassphraseSessionKey,
104 passphraseID: PassphraseID,
108 return async (dispatch) => {
109 const oldPromise = map.get(calendarID);
113 const promise = run(dispatch);
114 map.set(calendarID, promise);
119 export const useGetDecryptedPassphraseAndCalendarKeys = (): GetDecryptedPassphraseAndCalendarKeys => {
120 const dispatch = baseUseDispatch<ThunkDispatch<CalendarsBootstrapState, ProtonThunkArguments, Action>>();
122 return useCallback((calendarID: string) => {
123 return dispatch(getDecryptedPassphraseAndCalendarKeysThunk({ calendarID }));
127 export const useGetCalendarKeys = () => {
128 const getDecryptedPassphraseAndCalendarKeys = useGetDecryptedPassphraseAndCalendarKeys();
131 async (calendarID: string) => {
132 const { decryptedCalendarKeys } = await getDecryptedPassphraseAndCalendarKeys(calendarID);
133 return decryptedCalendarKeys;
135 [getDecryptedPassphraseAndCalendarKeys]