Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3380620
D12703.id43081.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D12703.id43081.diff
View Options
diff --git a/lib/facts/identity-service.js b/lib/facts/identity-service.js
--- a/lib/facts/identity-service.js
+++ b/lib/facts/identity-service.js
@@ -2,10 +2,34 @@
import { isDev } from '../utils/dev-utils.js';
-const config: { defaultURL: string } = {
+type IdentityServicePath = '/device_inbound_keys?device_id=';
+
+type IdentityServiceEndpoint = {
+ +path: IdentityServicePath,
+ +method: 'PUT' | 'GET' | 'POST' | 'DELETE',
+};
+
+const httpEndpoints = Object.freeze({
+ GET_INBOUND_KEYS: {
+ path: '/device_inbound_keys?device_id=',
+ method: 'GET',
+ },
+});
+
+type IdentityServiceConfig = {
+ +defaultURL: string,
+ +defaultHttpURL: string,
+ +httpEndpoints: { +[endpoint: string]: IdentityServiceEndpoint },
+};
+
+const config: IdentityServiceConfig = {
defaultURL: isDev
? 'https://identity.staging.commtechnologies.org:50054'
: 'https://identity.commtechnologies.org:50054',
+ defaultHttpURL: isDev
+ ? 'https://identity.staging.commtechnologies.org:51004'
+ : 'https://identity.commtechnologies.org:51004',
+ httpEndpoints,
};
export default config;
diff --git a/lib/utils/identity-service.js b/lib/utils/identity-service.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/identity-service.js
@@ -0,0 +1,48 @@
+// @flow
+
+import type { TInterface } from 'tcomb';
+import t from 'tcomb';
+
+import identityServiceConfig from '../facts/identity-service.js';
+import { tShape } from '../utils/validation-utils.js';
+
+export type InboundKeysForDeviceResponse = {
+ identityKeyInfo: {
+ keyPayload: string,
+ keyPayloadSignature: string,
+ },
+ contentPrekey: {
+ prekey: string,
+ prekeySignature: string,
+ },
+ notifPrekey: {
+ prekey: string,
+ prekeySignature: string,
+ },
+};
+
+export const inboundKeysForDeviceResponseValidator: TInterface<InboundKeysForDeviceResponse> =
+ tShape<InboundKeysForDeviceResponse>({
+ identityKeyInfo: tShape({
+ keyPayload: t.String,
+ keyPayloadSignature: t.String,
+ }),
+ contentPrekey: tShape({
+ prekey: t.String,
+ prekeySignature: t.String,
+ }),
+ notifPrekey: tShape({
+ prekey: t.String,
+ prekeySignature: t.String,
+ }),
+ });
+
+function getInboundKeysForDeviceURL(deviceID: string): string {
+ const urlSafeDeviceID = deviceID.replaceAll('+', '-').replaceAll('/', '_');
+ const endpointBasePath =
+ identityServiceConfig.httpEndpoints.GET_INBOUND_KEYS.path;
+ const path = `${endpointBasePath}${urlSafeDeviceID}`;
+ return `${identityServiceConfig.defaultHttpURL}${path}`;
+}
+
+export { getInboundKeysForDeviceURL };
diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js
--- a/web/push-notif/push-notifs-handler.js
+++ b/web/push-notif/push-notifs-handler.js
@@ -1,5 +1,6 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
import { recordAlertActionType } from 'lib/actions/alert-actions.js';
@@ -9,6 +10,7 @@
} from 'lib/actions/device-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
import {
alertTypes,
@@ -128,19 +130,29 @@
const callSetDeviceToken = useSetDeviceTokenFanout();
const staffCanSee = useStaffCanSee();
+ const identityContext = React.useContext(IdentityClientContext);
+ invariant(identityContext, 'Identity context should be set');
+ const { getAuthMetadata } = identityContext;
+
return React.useCallback(async () => {
if (!publicKey) {
return;
}
const workerRegistration = await navigator.serviceWorker?.ready;
- if (!workerRegistration || !workerRegistration.pushManager) {
+ const authMetadata = await getAuthMetadata();
+ if (
+ !workerRegistration ||
+ !workerRegistration.pushManager ||
+ !authMetadata
+ ) {
return;
}
workerRegistration.active?.postMessage({
olmWasmPath: getOlmWasmPath(),
staffCanSee,
+ authMetadata,
});
const subscription = await workerRegistration.pushManager.subscribe({
@@ -155,7 +167,13 @@
undefined,
{ type: 'device_token', deviceToken: token },
);
- }, [callSetDeviceToken, dispatchActionPromise, publicKey, staffCanSee]);
+ }, [
+ callSetDeviceToken,
+ dispatchActionPromise,
+ publicKey,
+ staffCanSee,
+ getAuthMetadata,
+ ]);
}
function PushNotificationsHandler(): React.Node {
diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js
--- a/web/push-notif/service-worker.js
+++ b/web/push-notif/service-worker.js
@@ -2,6 +2,7 @@
import localforage from 'localforage';
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
import type {
PlainTextWebNotification,
WebNotification,
@@ -15,6 +16,7 @@
type WebNotifsServiceUtilsData,
type WebNotifDecryptionError,
} from './notif-crypto-utils.js';
+import { persistAuthMetadata } from './services-client.js';
import { authoritativeKeyserverID } from '../authoritative-keyserver.js';
import { localforageConfig } from '../shared-worker/utils/constants.js';
@@ -26,7 +28,11 @@
}
declare class CommAppMessage extends ExtendableEvent {
- +data: { +olmWasmPath?: string, +staffCanSee?: boolean };
+ +data: {
+ +olmWasmPath?: string,
+ +staffCanSee?: boolean,
+ +authMetadata?: AuthMetadata,
+ };
}
declare var clients: Clients;
@@ -66,19 +72,24 @@
localforage.config(localforageConfig);
event.waitUntil(
(async () => {
- if (!event.data.olmWasmPath || event.data.staffCanSee === undefined) {
+ const { olmWasmPath, staffCanSee, authMetadata } = event.data;
+
+ if (!olmWasmPath || staffCanSee === undefined || !authMetadata) {
return;
}
const webNotifsServiceUtils: WebNotifsServiceUtilsData = {
- olmWasmPath: event.data.olmWasmPath,
- staffCanSee: event.data.staffCanSee,
+ olmWasmPath: olmWasmPath,
+ staffCanSee: staffCanSee,
};
- await localforage.setItem(
- WEB_NOTIFS_SERVICE_UTILS_KEY,
- webNotifsServiceUtils,
- );
+ await Promise.all([
+ localforage.setItem(
+ WEB_NOTIFS_SERVICE_UTILS_KEY,
+ webNotifsServiceUtils,
+ ),
+ persistAuthMetadata(authMetadata),
+ ]);
await migrateLegacyOlmNotificationsSessions();
})(),
diff --git a/web/push-notif/services-client.js b/web/push-notif/services-client.js
new file mode 100644
--- /dev/null
+++ b/web/push-notif/services-client.js
@@ -0,0 +1,120 @@
+// @flow
+
+import localforage from 'localforage';
+
+import identityServiceConfig from 'lib/facts/identity-service.js';
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
+import {
+ identityKeysBlobValidator,
+ type OLMIdentityKeys,
+} from 'lib/types/crypto-types.js';
+import { getMessageForException } from 'lib/utils/errors.js';
+import {
+ getInboundKeysForDeviceURL,
+ inboundKeysForDeviceResponseValidator,
+} from 'lib/utils/identity-service.js';
+import { createHTTPAuthorizationHeader } from 'lib/utils/services-utils.js';
+import { assertWithValidator } from 'lib/utils/validation-utils.js';
+
+import {
+ persistEncryptionKey,
+ retrieveEncryptionKey,
+} from './notif-crypto-utils.js';
+import {
+ type EncryptedData,
+ decryptData,
+ encryptData,
+ generateCryptoKey,
+} from '../crypto/aes-gcm-crypto-utils.js';
+import { isDesktopSafari } from '../shared-worker/utils/db-utils.js';
+
+export const WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY = 'notifsCSATEncryptionKey';
+export const WEB_NOTIFS_SERVICE_CSAT = 'notifsCSAT';
+
+async function persistAuthMetadata(authMetadata: AuthMetadata): Promise<void> {
+ const encryptionKey = await generateCryptoKey({
+ extractable: isDesktopSafari,
+ });
+
+ const encryptedAuthMetadata = await encryptData(
+ new TextEncoder().encode(JSON.stringify(authMetadata)),
+ encryptionKey,
+ );
+
+ await Promise.all([
+ localforage.setItem(WEB_NOTIFS_SERVICE_CSAT, encryptedAuthMetadata),
+ persistEncryptionKey(WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY, encryptionKey),
+ ]);
+}
+
+async function fetchAuthMetadata(): Promise<AuthMetadata> {
+ const [encryptionKey, encryptedAuthMetadata] = await Promise.all([
+ retrieveEncryptionKey(WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY),
+ localforage.getItem<EncryptedData>(WEB_NOTIFS_SERVICE_CSAT),
+ ]);
+
+ if (!encryptionKey || !encryptedAuthMetadata) {
+ throw new Error('CSAT unavailable in push notifs service worker');
+ }
+
+ const authMetadata: AuthMetadata = JSON.parse(
+ new TextDecoder().decode(
+ await decryptData(encryptedAuthMetadata, encryptionKey),
+ ),
+ );
+
+ return authMetadata;
+}
+
+async function getNotifsInboundKeysForDeviceID(
+ deviceID: string,
+ authMetadata: AuthMetadata,
+): Promise<OLMIdentityKeys | { error: string }> {
+ const authorization = createHTTPAuthorizationHeader(authMetadata);
+ const headers = {
+ Authorization: authorization,
+ Accept: 'application/json',
+ };
+ try {
+ const getInboundKeysResponse = await fetch(
+ getInboundKeysForDeviceURL(deviceID),
+ {
+ method: identityServiceConfig.httpEndpoints.GET_INBOUND_KEYS.method,
+ headers,
+ },
+ );
+
+ if (!getInboundKeysResponse.ok) {
+ const { statusText, status } = getInboundKeysResponse;
+ return {
+ error:
+ `Failed to fetch inbound keys for ${deviceID} with code: ${status}. ` +
+ `Details: ${statusText}`,
+ };
+ }
+
+ const inboundKeysForDeviceBlob = await getInboundKeysResponse.json();
+ const inboundKeysForDevice = assertWithValidator(
+ inboundKeysForDeviceBlob,
+ inboundKeysForDeviceResponseValidator,
+ );
+ const identityKeysBlob = inboundKeysForDevice.identityKeyInfo.keyPayload;
+ const identityKeys = assertWithValidator(
+ JSON.parse(identityKeysBlob),
+ identityKeysBlobValidator,
+ );
+ return identityKeys.notificationIdentityPublicKeys;
+ } catch (e) {
+ return {
+ error: `Failed to fetch inbound keys for ${deviceID}. Details: ${
+ getMessageForException(e) ?? ''
+ }`,
+ };
+ }
+}
+
+export {
+ persistAuthMetadata,
+ fetchAuthMetadata,
+ getNotifsInboundKeysForDeviceID,
+};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Nov 29, 1:02 AM (21 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2595320
Default Alt Text
D12703.id43081.diff (10 KB)
Attached To
Mode
D12703: Transfer CSAT to IndexedDB from service worker and implement call to identity to query for inbound keys
Attached
Detach File
Event Timeline
Log In to Comment