diff --git a/keyserver/src/push/crypto.js b/keyserver/src/push/crypto.js --- a/keyserver/src/push/crypto.js +++ b/keyserver/src/push/crypto.js @@ -1,6 +1,5 @@ // @flow -import type { EncryptResult } from '@commapp/olm'; import apn from '@parse/node-apn'; import crypto from 'crypto'; import invariant from 'invariant'; @@ -18,14 +17,15 @@ AndroidNotificationRescind, NotificationTargetDevice, SenderDeviceID, + EncryptedNotifUtilsAPI, } from 'lib/types/notif-types.js'; import { toBase64URL } from 'lib/utils/base64.js'; -import { encryptAndUpdateOlmSession } from '../updaters/olm-session-updater.js'; import { encrypt, generateKey } from '../utils/aes-crypto-utils.js'; import { getOlmUtility } from '../utils/olm-utils.js'; async function encryptAPNsNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, notification: apn.Notification, @@ -71,26 +71,19 @@ let dbPersistCondition; if (notificationSizeValidator) { - dbPersistCondition = ({ - serializedPayload, - }: { - +[string]: EncryptResult, - }) => { + dbPersistCondition = (serializedPayload: string) => { const notifCopy = _cloneDeep(encryptedNotification); - notifCopy.payload.encryptedPayload = serializedPayload.body; + notifCopy.payload.encryptedPayload = serializedPayload; return notificationSizeValidator(notifCopy); }; } const { - encryptedMessages: { serializedPayload }, - dbPersistConditionViolated, + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, - } = await encryptAndUpdateOlmSession( + } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cookieID, - 'notifications', - { - serializedPayload: unencryptedSerializedPayload, - }, + unencryptedSerializedPayload, dbPersistCondition, ); @@ -140,6 +133,7 @@ } async function encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, unencryptedPayload: T, @@ -166,29 +160,23 @@ let dbPersistCondition; if (payloadSizeValidator) { - dbPersistCondition = ({ - serializedPayload, - }: { - +[string]: EncryptResult, - }) => + dbPersistCondition = (serializedPayload: string) => payloadSizeValidator({ - encryptedPayload: serializedPayload.body, + encryptedPayload: serializedPayload, ...senderDeviceID, }); } const { - encryptedMessages: { serializedPayload }, - dbPersistConditionViolated, + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, - } = await encryptAndUpdateOlmSession( + } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cookieID, - 'notifications', - { - serializedPayload: unencryptedSerializedPayload, - }, + unencryptedSerializedPayload, dbPersistCondition, ); + return { resultPayload: { encryptedPayload: serializedPayload.body, @@ -213,6 +201,7 @@ } async function encryptAndroidVisualNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, cookieID: string, notification: AndroidVisualNotification, @@ -249,6 +238,7 @@ } const { resultPayload, payloadSizeExceeded, encryptionOrder } = await encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, unencryptedPayload, @@ -267,6 +257,7 @@ } async function encryptAndroidSilentNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, @@ -276,6 +267,7 @@ // never exceed any FCM limit const { ...unencryptedPayload } = notification.data; const { resultPayload } = await encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, unencryptedPayload, @@ -300,6 +292,7 @@ } async function encryptBasicPayload( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, basicPayload: T, @@ -318,12 +311,11 @@ } try { - const { - encryptedMessages: { serializedPayload }, - encryptionOrder, - } = await encryptAndUpdateOlmSession(cookieID, 'notifications', { - serializedPayload: unencryptedSerializedPayload, - }); + const { encryptedData: serializedPayload, encryptionOrder } = + await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( + cookieID, + unencryptedSerializedPayload, + ); return { ...senderDeviceID, @@ -340,6 +332,7 @@ } async function encryptWebNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, notification: PlainTextWebNotification, @@ -347,6 +340,7 @@ const { id, ...payloadSansId } = notification; const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, payloadSansId, @@ -359,12 +353,14 @@ } async function encryptWNSNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, senderDeviceID: SenderDeviceID, notification: PlainTextWNSNotification, ): Promise<{ +notification: WNSNotification, +encryptionOrder?: number }> { const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, @@ -376,6 +372,7 @@ } function prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: apn.Notification, @@ -394,6 +391,7 @@ const notificationPromises = devices.map( async ({ cookieID, deviceToken, blobHolder }) => { const notif = await encryptAPNsNotification( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, @@ -408,6 +406,7 @@ } function prepareEncryptedIOSNotificationRescind( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: apn.Notification, @@ -422,6 +421,7 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const { notification: notif } = await encryptAPNsNotification( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, @@ -434,6 +434,7 @@ } function prepareEncryptedAndroidVisualNotifications( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: AndroidVisualNotification, @@ -452,6 +453,7 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID, blobHolder }) => { const notif = await encryptAndroidVisualNotification( + encryptedNotifUtilsAPI, senderDeviceID, cookieID, notification, @@ -465,6 +467,7 @@ } function prepareEncryptedAndroidSilentNotifications( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, @@ -479,6 +482,7 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptAndroidSilentNotification( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, @@ -490,6 +494,7 @@ } function prepareEncryptedWebNotifications( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: PlainTextWebNotification, @@ -503,6 +508,7 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptWebNotification( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, @@ -514,6 +520,7 @@ } function prepareEncryptedWNSNotifications( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: PlainTextWNSNotification, @@ -527,6 +534,7 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptWNSNotification( + encryptedNotifUtilsAPI, cookieID, senderDeviceID, notification, diff --git a/keyserver/src/push/encrypted-notif-utils-api.js b/keyserver/src/push/encrypted-notif-utils-api.js new file mode 100644 --- /dev/null +++ b/keyserver/src/push/encrypted-notif-utils-api.js @@ -0,0 +1,49 @@ +// @flow + +import type { EncryptResult } from '@commapp/olm'; + +import type { EncryptedNotifUtilsAPI } from 'lib/types/notif-types.js'; + +import { blobServiceUpload } from './utils.js'; +import { encryptAndUpdateOlmSession } from '../updaters/olm-session-updater.js'; + +const encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI = { + encryptSerializedNotifPayload: async ( + cryptoID: string, + unencryptedPayload: string, + encryptedPayloadSizeValidator?: (encryptedPayload: string) => boolean, + ) => { + let dbPersistCondition; + if (encryptedPayloadSizeValidator) { + dbPersistCondition = ({ + serializedPayload, + }: { + +[string]: EncryptResult, + }) => encryptedPayloadSizeValidator(serializedPayload.body); + } + + const { + encryptedMessages: { serializedPayload }, + dbPersistConditionViolated, + encryptionOrder, + } = await encryptAndUpdateOlmSession( + cryptoID, + 'notifications', + { + serializedPayload: unencryptedPayload, + }, + dbPersistCondition, + ); + + return { + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, + encryptionOrder, + }; + }, + uploadLargeNotifPayload: blobServiceUpload, + getNotifByteSize: (serializedPayload: string) => + Buffer.byteLength(serializedPayload), +}; + +export default encryptedNotifUtilsAPI; diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js --- a/keyserver/src/push/rescind.js +++ b/keyserver/src/push/rescind.js @@ -10,6 +10,7 @@ NotificationTargetDevice, TargetedAndroidNotification, SenderDeviceID, + EncryptedNotifUtilsAPI, } from 'lib/types/notif-types.js'; import { threadSubscriptions } from 'lib/types/subscription-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; @@ -20,6 +21,7 @@ prepareEncryptedAndroidSilentNotifications, prepareEncryptedIOSNotificationRescind, } from './crypto.js'; +import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js'; import { getAPNsNotificationTopic } from './providers.js'; import type { TargetedAPNsNotification } from './types.js'; import { @@ -275,11 +277,13 @@ } async function conditionallyEncryptNotification( + encryptedNotifUtilsAPIInstance: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, notification: T, codeVersion: ?number, devices: $ReadOnlyArray, encryptCallback: ( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, senderDeviceID: SenderDeviceID, devices: $ReadOnlyArray, notification: T, @@ -301,6 +305,7 @@ })); } const notifications = await encryptCallback( + encryptedNotifUtilsAPI, senderDeviceID, devices, notification, @@ -354,6 +359,7 @@ }, }; return await conditionallyEncryptNotification( + encryptedNotifUtilsAPI, { keyserverID }, notification, codeVersion, @@ -383,6 +389,7 @@ }, }; const targetedRescinds = await conditionallyEncryptNotification( + encryptedNotifUtilsAPI, { keyserverID }, notification, codeVersion, diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js --- a/keyserver/src/push/send.js +++ b/keyserver/src/push/send.js @@ -56,6 +56,7 @@ prepareEncryptedWebNotifications, prepareEncryptedWNSNotifications, } from './crypto.js'; +import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js'; import { getAPNsNotificationTopic } from './providers.js'; import { rescindPushNotifs } from './rescind.js'; import type { TargetedAPNsNotification } from './types.js'; @@ -70,7 +71,6 @@ wnsMaxNotificationPayloadByteSize, wnsPush, type WNSPushError, - blobServiceUpload, } from './utils.js'; import createIDs from '../creators/id-creator.js'; import { createUpdates } from '../creators/update-creator.js'; @@ -1039,6 +1039,7 @@ if (platformDetails.platform === 'macos') { const macOSNotifsWithoutMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1053,6 +1054,7 @@ } const notifsWithMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, copyWithMessageInfos, @@ -1090,7 +1092,7 @@ let blobHash, blobHolders, encryptionKey, blobUploadError; if (canQueryBlobService) { ({ blobHash, blobHolders, encryptionKey, blobUploadError } = - await blobServiceUpload( + await encryptedNotifUtilsAPI.uploadLargeNotifPayload( copyWithMessageInfos.compile(), devicesWithExcessiveSizeNoHolders.length, )); @@ -1123,6 +1125,7 @@ } const notifsWithoutMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devicesWithExcessiveSize, notification, @@ -1247,8 +1250,9 @@ const priority = 'high'; if (!shouldBeEncrypted) { const notificationToSend = - Buffer.byteLength(JSON.stringify(copyWithMessageInfos)) <= - fcmMaxNotificationPayloadByteSize + encryptedNotifUtilsAPI.getNotifByteSize( + JSON.stringify(copyWithMessageInfos), + ) <= fcmMaxNotificationPayloadByteSize ? copyWithMessageInfos : notification; @@ -1263,12 +1267,14 @@ const serializedNotif = JSON.stringify(notif); return ( !serializedNotif || - Buffer.byteLength(serializedNotif) <= fcmMaxNotificationPayloadByteSize + encryptedNotifUtilsAPI.getNotifByteSize(serializedNotif) <= + fcmMaxNotificationPayloadByteSize ); }; const notifsWithMessageInfos = await prepareEncryptedAndroidVisualNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, copyWithMessageInfos, @@ -1297,7 +1303,7 @@ let blobHash, blobHolders, encryptionKey, blobUploadError; if (canQueryBlobService) { ({ blobHash, blobHolders, encryptionKey, blobUploadError } = - await blobServiceUpload( + await encryptedNotifUtilsAPI.uploadLargeNotifPayload( JSON.stringify(copyWithMessageInfos.data), devicesWithExcessiveSizeNoHolders.length, )); @@ -1331,6 +1337,7 @@ const notifsWithoutMessageInfos = await prepareEncryptedAndroidVisualNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devicesWithExcessiveSize, notification, @@ -1402,6 +1409,7 @@ } return prepareEncryptedWebNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1440,7 +1448,7 @@ }; if ( - Buffer.byteLength(JSON.stringify(notification)) > + encryptedNotifUtilsAPI.getNotifByteSize(JSON.stringify(notification)) > wnsMaxNotificationPayloadByteSize ) { console.warn('WNS notification exceeds size limit'); @@ -1457,6 +1465,7 @@ })); } return await prepareEncryptedWNSNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1813,6 +1822,7 @@ let targetedNotifications: $ReadOnlyArray; if (codeVersion > 222) { const notificationsArray = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, @@ -1865,6 +1875,7 @@ if (codeVersion > 222) { const notificationsArray = await prepareEncryptedAndroidSilentNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, @@ -1923,6 +1934,7 @@ let targetedNotifications: $ReadOnlyArray; if (shouldBeEncrypted) { const notificationsArray = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js --- a/lib/types/notif-types.js +++ b/lib/types/notif-types.js @@ -1,5 +1,5 @@ // @flow - +import type { EncryptResult } from '@commapp/olm'; import t, { type TInterface } from 'tcomb'; import type { EntityText, ThreadEntity } from '../utils/entity-text.js'; @@ -198,3 +198,27 @@ +deviceToken: string, +blobHolder?: string, }; + +export type EncryptedNotifUtilsAPI = { + +encryptSerializedNotifPayload: ( + cryptoID: string, + unencryptedPayload: string, + encryptedPayloadSizeValidator?: (encryptedPayload: string) => boolean, + ) => Promise<{ + +encryptedData: EncryptResult, + +sizeLimitViolated?: boolean, + +encryptionOrder?: number, + }>, + +uploadLargeNotifPayload: ( + payload: string, + numberOfHolders: number, + ) => Promise< + | { + +blobHolders: $ReadOnlyArray, + +blobHash: string, + +encryptionKey: string, + } + | { +blobUploadError: string }, + >, + +getNotifByteSize: (serializedNotification: string) => number, +};