1 import type { PublicKeyReference } from '@proton/crypto';
2 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
3 import type { ContactEmail } from '@proton/shared/lib/interfaces/contacts';
4 import type { GetVerificationPreferences } from '@proton/shared/lib/interfaces/hooks/GetVerificationPreferences';
5 import isTruthy from '@proton/utils/isTruthy';
6 import unique from '@proton/utils/unique';
8 import { canonicalizeInternalEmail } from '../helpers/email';
9 import type { Address } from '../interfaces';
10 import type { CalendarEvent, CalendarEventData } from '../interfaces/calendar';
11 import type { GetAddressKeys } from '../interfaces/hooks/GetAddressKeys';
12 import type { SimpleMap } from '../interfaces/utils';
13 import { getKeyHasFlagsToVerify } from '../keys';
14 import { getActiveKeys } from '../keys/getActiveKeys';
15 import { CALENDAR_CARD_TYPE } from './constants';
17 const { SIGNED, ENCRYPTED_AND_SIGNED } = CALENDAR_CARD_TYPE;
19 export const withNormalizedAuthor = (x: CalendarEventData) => ({
21 Author: canonicalizeInternalEmail(x.Author),
23 export const withNormalizedAuthors = (x: CalendarEventData[]) => {
27 return x.map(withNormalizedAuthor);
30 interface GetAuthorPublicKeysMap {
33 getAddressKeys: GetAddressKeys;
34 getVerificationPreferences: GetVerificationPreferences;
35 contactEmailsMap: SimpleMap<ContactEmail>;
38 export const getAuthorPublicKeysMap = async ({
42 getVerificationPreferences,
44 }: GetAuthorPublicKeysMap) => {
45 const publicKeysMap: SimpleMap<PublicKeyReference | PublicKeyReference[]> = {};
46 const authors = unique(
47 [...event.SharedEvents, ...event.CalendarEvents]
48 .map(({ Author, Type }) => {
49 if (![SIGNED, ENCRYPTED_AND_SIGNED].includes(Type)) {
50 // no need to fetch keys in this case
53 return canonicalizeInternalEmail(Author);
57 const normalizedAddresses = addresses.map((address) => ({
59 normalizedEmailAddress: canonicalizeInternalEmail(address.Email),
61 const promises = authors.map(async (author) => {
62 const ownAddress = normalizedAddresses.find(({ normalizedEmailAddress }) => normalizedEmailAddress === author);
64 const decryptedKeys = await getAddressKeys(ownAddress.ID);
65 const addressKeys = await getActiveKeys(
67 ownAddress.SignedKeyList,
71 publicKeysMap[author] = addressKeys
72 .filter((decryptedKey) => {
73 return getKeyHasFlagsToVerify(decryptedKey.flags);
75 .map((key) => key.publicKey);
78 const { verifyingKeys } = await getVerificationPreferences({ email: author, contactEmailsMap });
79 publicKeysMap[author] = verifyingKeys;
80 } catch (error: any) {
81 // We're seeing too many unexpected offline errors in the GET /keys route.
82 // We log them to Sentry and ignore them here (no verification will take place in these cases)
83 const { ID, CalendarID } = event;
84 const errorMessage = error?.message || 'Unknown error';
85 captureMessage('Unexpected error verifying event signature', {
86 extra: { message: errorMessage, eventID: ID, calendarID: CalendarID },
91 await Promise.all(promises);