Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / account / organizationKey / listener.ts
bloba8a6d06545e68f1826e4a4985f85d63adc65b16c
1 import type { Action } from '@reduxjs/toolkit';
3 import { CryptoProxy } from '@proton/crypto';
4 import type { SharedStartListening } from '@proton/redux-shared-store-types';
5 import { CacheType } from '@proton/redux-utilities';
6 import { getIsAddressConfirmed, getIsAddressEnabled } from '@proton/shared/lib/helpers/address';
7 import { hasBit } from '@proton/shared/lib/helpers/bitset';
8 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
9 import { getSentryError } from '@proton/shared/lib/keys';
10 import noop from '@proton/utils/noop';
12 import { addressesThunk, selectAddresses } from '../addresses';
13 import { serverEvent } from '../eventLoop';
14 import { selectMembers } from '../members';
15 import { selectOrganization } from '../organization';
16 import {
17     changeOrganizationSignature,
18     migrateOrganizationKeyPasswordless,
19     migrateOrganizationKeyPasswordlessPrivateAdmin,
20 } from './actions';
21 import { type OrganizationKeyState, organizationKeyThunk, selectOrganizationKey } from './index';
23 export const organizationKeysListener = (startListening: SharedStartListening<OrganizationKeyState>) => {
24     startListening({
25         predicate: (action: Action, currentState: OrganizationKeyState) => {
26             // Warning: There is no event update coming for organization key changes, however, an update for the organization
27             // is received as the keys are changed. So each time it changes, it will redo this.
28             return !!(
29                 serverEvent.match(action) &&
30                 action.payload.Organization &&
31                 !!selectOrganizationKey(currentState).value
32             );
33         },
34         effect: async (action, listenerApi) => {
35             await listenerApi.dispatch(organizationKeyThunk({ cache: CacheType.None }));
36         },
37     });
39     startListening({
40         predicate: (action, currentState, nextState) => {
41             const oldValue = selectOrganizationKey(currentState).value;
42             return !!(oldValue && oldValue !== selectOrganizationKey(nextState).value);
43         },
44         effect: async (action, listenerApi) => {
45             const oldValue = selectOrganizationKey(listenerApi.getOriginalState())?.value;
46             if (oldValue?.privateKey) {
47                 await CryptoProxy.clearKey({ key: oldValue.privateKey }).catch(noop);
48             }
49         },
50     });
53 export const organizationKeysManagementListener = (startListening: SharedStartListening<OrganizationKeyState>) => {
54     startListening({
55         predicate: (action, currentState, nextState) => {
56             return Boolean(
57                 selectOrganizationKey(nextState).value &&
58                     selectMembers(nextState).value?.length &&
59                     selectOrganization(nextState).value
60             );
61         },
62         effect: async (action, listenerApi) => {
63             const state = listenerApi.getState();
64             const organization = selectOrganization(state).value;
65             const members = selectMembers(state).value;
66             const self = members?.find((member) => Boolean(member.Self));
67             const organizationKey = selectOrganizationKey(state).value;
68             if (!organization || !self || !organizationKey?.privateKey) {
69                 return;
70             }
71             try {
72                 listenerApi.unsubscribe();
74                 if (hasBit(organization.ToMigrate, 2)) {
75                     await listenerApi.dispatch(migrateOrganizationKeyPasswordless());
76                 } else if (hasBit(self.ToMigrate, 2)) {
77                     await listenerApi.dispatch(migrateOrganizationKeyPasswordlessPrivateAdmin());
78                 }
79             } catch (e) {}
80         },
81     });
83     startListening({
84         predicate: (action, currentState, nextState) => {
85             const orgKey = selectOrganizationKey(nextState).value;
86             const addresses = selectAddresses(nextState).value;
87             return Boolean(orgKey?.privateKey && !orgKey.Key.FingerprintSignature && (addresses || [])?.length >= 1);
88         },
89         effect: async (action, listenerApi) => {
90             const state = listenerApi.getState();
91             const organizationKey = selectOrganizationKey(state).value;
92             if (!organizationKey?.privateKey) {
93                 return;
94             }
96             listenerApi.unsubscribe();
98             const addresses = await listenerApi.dispatch(addressesThunk());
99             const activeAddresses = addresses.filter(
100                 (address) => getIsAddressEnabled(address) && getIsAddressConfirmed(address)
101             );
102             const primaryActiveAddress = activeAddresses[0];
103             if (!primaryActiveAddress) {
104                 return;
105             }
107             try {
108                 await listenerApi.dispatch(changeOrganizationSignature({ address: primaryActiveAddress }));
109             } catch (e) {
110                 const error = getSentryError(e);
111                 if (error) {
112                     captureMessage('Organization identity: Error generating signature', {
113                         level: 'error',
114                         extra: { error },
115                     });
116                 }
117             }
118         },
119     });