diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js --- a/web/crypto/olm-api.js +++ b/web/crypto/olm-api.js @@ -152,4 +152,4 @@ }, }; -export { olmAPI }; +export { olmAPI, usingSharedWorker }; diff --git a/web/grpc/identity-service-client-proxy.js b/web/grpc/identity-service-client-proxy.js new file mode 100644 --- /dev/null +++ b/web/grpc/identity-service-client-proxy.js @@ -0,0 +1,224 @@ +// @flow + +import type { + OneTimeKeysResultValues, + SignedPrekeys, +} from 'lib/types/crypto-types.js'; +import type { + IdentityServiceClient, + IdentityServiceAuthLayer, + DeviceOlmOutboundKeys, + IdentityAuthResult, + UserDevicesOlmInboundKeys, + UserDevicesOlmOutboundKeys, +} from 'lib/types/identity-service-types.js'; +import { getConfig } from 'lib/utils/config.js'; + +import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js'; +import type { CommSharedWorker } from '../shared-worker/shared-worker-provider.js'; +import { opaqueWasmPath } from '../shared-worker/utils/constants.js'; +import { + workerRequestMessageTypes, + workerResponseMessageTypes, +} from '../types/worker-types.js'; + +class IdentityServiceClientSharedProxy implements IdentityServiceClient { + sharedWorkerPromise: Promise; + + constructor(authLayer: ?IdentityServiceAuthLayer) { + this.sharedWorkerPromise = (async () => { + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT, + opaqueWasmPath: opaqueWasmPath(), + platformDetails: getConfig().platformDetails, + authLayer, + }); + + return sharedWorker; + })(); + } + + deleteUser: () => Promise = async () => { + const sharedWorker = await this.sharedWorkerPromise; + await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_DELETE_USER, + }); + }; + + getKeyserverKeys: (keyserverID: string) => Promise = + async (keyserverID: string) => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_GET_KEYSERVER_KEYS, + keyserverID, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== workerResponseMessageTypes.IDENTITY_GET_KEYSERVER_KEYS + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.keys; + }; + + getOutboundKeysForUser: ( + userID: string, + ) => Promise = async (userID: string) => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_GET_OUTBOUND_KEYS_FOR_USER, + userID, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== + workerResponseMessageTypes.IDENTITY_GET_OUTBOUND_KEYS_FOR_USER + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.keys; + }; + + getInboundKeysForUser: ( + userID: string, + ) => Promise = async (userID: string) => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_GET_INBOUND_KEYS_FOR_USER, + userID, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== + workerResponseMessageTypes.IDENTITY_GET_INBOUND_KEYS_FOR_USER + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.keys; + }; + + uploadOneTimeKeys: (oneTimeKeys: OneTimeKeysResultValues) => Promise = + async (oneTimeKeys: OneTimeKeysResultValues) => { + const sharedWorker = await this.sharedWorkerPromise; + await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_UPLOAD_ONE_TIME_KEYS, + oneTimeKeys, + }); + }; + + logInPasswordUser: ( + username: string, + password: string, + ) => Promise = async ( + username: string, + password: string, + ) => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_LOG_IN_PASSWORD_USER, + username, + password, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== workerResponseMessageTypes.IDENTITY_AUTH_RESULT + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.result; + }; + + logInWalletUser: ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => Promise = async ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_LOG_IN_WALLET_USER, + walletAddress, + siweMessage, + siweSignature, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== workerResponseMessageTypes.IDENTITY_AUTH_RESULT + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.result; + }; + + generateNonce: () => Promise = async () => { + const sharedWorker = await this.sharedWorkerPromise; + const result = await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_GENERATE_NONCE, + }); + + if (!result) { + throw new Error(`Worker identity call didn't return expected message`); + } else if ( + result.type !== workerResponseMessageTypes.IDENTITY_GENERATE_NONCE + ) { + throw new Error( + `Worker identity call didn't return expected message. Instead got: ${JSON.stringify( + result, + )}`, + ); + } + + return result.nonce; + }; + + publishWebPrekeys: (prekeys: SignedPrekeys) => Promise = async ( + prekeys: SignedPrekeys, + ) => { + const sharedWorker = await this.sharedWorkerPromise; + await sharedWorker.schedule({ + type: workerRequestMessageTypes.IDENTITY_PUBLISH_WEB_PREKEYS, + prekeys, + }); + }; +} + +export { IdentityServiceClientSharedProxy }; diff --git a/web/grpc/identity-service-context-provider.react.js b/web/grpc/identity-service-context-provider.react.js --- a/web/grpc/identity-service-context-provider.react.js +++ b/web/grpc/identity-service-context-provider.react.js @@ -8,8 +8,10 @@ } from 'lib/shared/identity-client-context.js'; import { getConfig } from 'lib/utils/config.js'; +import { IdentityServiceClientSharedProxy } from './identity-service-client-proxy.js'; import { IdentityServiceClientWrapper } from './identity-service-client-wrapper.js'; import { useGetDeviceKeyUpload } from '../account/account-hooks.js'; +import { usingSharedWorker } from '../crypto/olm-api.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { @@ -34,12 +36,16 @@ commServicesAccessToken: accessToken, }; } - return new IdentityServiceClientWrapper( - getConfig().platformDetails, - null, - authLayer, - getDeviceKeyUpload, - ); + if (usingSharedWorker) { + return new IdentityServiceClientSharedProxy(authLayer); + } else { + return new IdentityServiceClientWrapper( + getConfig().platformDetails, + null, + authLayer, + getDeviceKeyUpload, + ); + } }, [accessToken, deviceID, getDeviceKeyUpload, userID]); const getAuthMetadata = React.useCallback<() => Promise>( diff --git a/web/shared-worker/shared-worker-provider.js b/web/shared-worker/shared-worker-provider.js --- a/web/shared-worker/shared-worker-provider.js +++ b/web/shared-worker/shared-worker-provider.js @@ -162,4 +162,4 @@ return newModule; } -export { getCommSharedWorker }; +export { CommSharedWorker, getCommSharedWorker }; diff --git a/web/shared-worker/worker/identity-client.js b/web/shared-worker/worker/identity-client.js --- a/web/shared-worker/worker/identity-client.js +++ b/web/shared-worker/worker/identity-client.js @@ -5,15 +5,17 @@ import { getDeviceKeyUpload } from './worker-crypto.js'; import { IdentityServiceClientWrapper } from '../../grpc/identity-service-client-wrapper.js'; -import { workerRequestMessageTypes } from '../../types/worker-types.js'; import type { WorkerResponseMessage, WorkerRequestMessage, } from '../../types/worker-types.js'; -import type { EmscriptenModule } from '../types/module'; -import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor'; +import { + workerRequestMessageTypes, + workerResponseMessageTypes, +} from '../../types/worker-types.js'; +import type { EmscriptenModule } from '../types/module.js'; +import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor.js'; -// eslint-disable-next-line no-unused-vars let identityClient: ?IdentityServiceClientWrapper = null; async function processAppIdentityClientRequest( @@ -32,6 +34,75 @@ message.authLayer, ); } + + if (!identityClient) { + throw new Error('Identity client not created'); + } + + if (message.type === workerRequestMessageTypes.IDENTITY_DELETE_USER) { + await identityClient.deleteUser(); + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_GET_KEYSERVER_KEYS + ) { + return { + type: workerResponseMessageTypes.IDENTITY_GET_KEYSERVER_KEYS, + keys: await identityClient.getKeyserverKeys(message.keyserverID), + }; + } else if ( + message.type === + workerRequestMessageTypes.IDENTITY_GET_OUTBOUND_KEYS_FOR_USER + ) { + return { + type: workerResponseMessageTypes.IDENTITY_GET_OUTBOUND_KEYS_FOR_USER, + keys: await identityClient.getOutboundKeysForUser(message.userID), + }; + } else if ( + message.type === + workerRequestMessageTypes.IDENTITY_GET_INBOUND_KEYS_FOR_USER + ) { + return { + type: workerResponseMessageTypes.IDENTITY_GET_INBOUND_KEYS_FOR_USER, + keys: await identityClient.getInboundKeysForUser(message.userID), + }; + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_UPLOAD_ONE_TIME_KEYS + ) { + await identityClient.uploadOneTimeKeys(message.oneTimeKeys); + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_LOG_IN_PASSWORD_USER + ) { + return { + type: workerResponseMessageTypes.IDENTITY_AUTH_RESULT, + result: await identityClient.logInPasswordUser( + message.username, + message.password, + ), + }; + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_LOG_IN_WALLET_USER + ) { + return { + type: workerResponseMessageTypes.IDENTITY_AUTH_RESULT, + result: await identityClient.logInWalletUser( + message.walletAddress, + message.siweMessage, + message.siweSignature, + ), + }; + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_GENERATE_NONCE + ) { + return { + type: workerResponseMessageTypes.IDENTITY_GENERATE_NONCE, + nonce: await identityClient.generateNonce(), + }; + } else if ( + message.type === workerRequestMessageTypes.IDENTITY_PUBLISH_WEB_PREKEYS + ) { + await identityClient.publishWebPrekeys(message.prekeys); + } + + return undefined; } function createIdentityServiceClient( diff --git a/web/types/worker-types.js b/web/types/worker-types.js --- a/web/types/worker-types.js +++ b/web/types/worker-types.js @@ -1,9 +1,19 @@ // @flow import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; -import type { CryptoStore } from 'lib/types/crypto-types.js'; +import type { + OneTimeKeysResultValues, + SignedPrekeys, + CryptoStore, +} from 'lib/types/crypto-types.js'; import type { PlatformDetails } from 'lib/types/device-types.js'; -import type { IdentityServiceAuthLayer } from 'lib/types/identity-service-types.js'; +import type { + DeviceOlmOutboundKeys, + IdentityAuthResult, + UserDevicesOlmInboundKeys, + UserDevicesOlmOutboundKeys, + IdentityServiceAuthLayer, +} from 'lib/types/identity-service-types.js'; import type { ClientDBStore, ClientDBStoreOperations, @@ -25,6 +35,15 @@ BACKUP_RESTORE: 11, INITIALIZE_CRYPTO_ACCOUNT: 12, CREATE_IDENTITY_SERVICE_CLIENT: 13, + IDENTITY_DELETE_USER: 14, + IDENTITY_GET_KEYSERVER_KEYS: 15, + IDENTITY_GET_OUTBOUND_KEYS_FOR_USER: 16, + IDENTITY_GET_INBOUND_KEYS_FOR_USER: 17, + IDENTITY_UPLOAD_ONE_TIME_KEYS: 18, + IDENTITY_LOG_IN_PASSWORD_USER: 19, + IDENTITY_LOG_IN_WALLET_USER: 20, + IDENTITY_GENERATE_NONCE: 21, + IDENTITY_PUBLISH_WEB_PREKEYS: 22, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -38,6 +57,15 @@ export const workerIdentityClientRequests: $ReadOnlyArray = [ workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT, + workerRequestMessageTypes.IDENTITY_DELETE_USER, + workerRequestMessageTypes.IDENTITY_GET_KEYSERVER_KEYS, + workerRequestMessageTypes.IDENTITY_GET_OUTBOUND_KEYS_FOR_USER, + workerRequestMessageTypes.IDENTITY_GET_INBOUND_KEYS_FOR_USER, + workerRequestMessageTypes.IDENTITY_UPLOAD_ONE_TIME_KEYS, + workerRequestMessageTypes.IDENTITY_LOG_IN_PASSWORD_USER, + workerRequestMessageTypes.IDENTITY_LOG_IN_WALLET_USER, + workerRequestMessageTypes.IDENTITY_GENERATE_NONCE, + workerRequestMessageTypes.IDENTITY_PUBLISH_WEB_PREKEYS, ]; export type PingWorkerRequestMessage = { @@ -117,6 +145,52 @@ +authLayer: ?IdentityServiceAuthLayer, }; +export type IdentityDeleteUserRequestMessage = { + +type: 14, +}; + +export type IdentityGetKeyserverKeysRequestMessage = { + +type: 15, + +keyserverID: string, +}; + +export type IdentityGetOutboundKeysRequestMessage = { + +type: 16, + +userID: string, +}; + +export type IdentityGetInboundKeysRequestMessage = { + +type: 17, + +userID: string, +}; + +export type IdentityUploadOneTimeKeysRequestMessage = { + +type: 18, + +oneTimeKeys: OneTimeKeysResultValues, +}; + +export type IdentityLogInPasswordUserRequestMessage = { + +type: 19, + +username: string, + +password: string, +}; + +export type IdentityLogInWalletUserRequestMessage = { + +type: 20, + +walletAddress: string, + +siweMessage: string, + +siweSignature: string, +}; + +export type IdentityGenerateNonceRequestMessage = { + +type: 21, +}; + +export type IdentityPublishWebPrekeysRequestMessage = { + +type: 22, + +prekeys: SignedPrekeys, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -131,7 +205,16 @@ | ClearSensitiveDataRequestMessage | BackupRestoreRequestMessage | InitializeCryptoAccountRequestMessage - | CreateIdentityServiceClientRequestMessage; + | CreateIdentityServiceClientRequestMessage + | IdentityDeleteUserRequestMessage + | IdentityGetKeyserverKeysRequestMessage + | IdentityGetOutboundKeysRequestMessage + | IdentityGetInboundKeysRequestMessage + | IdentityUploadOneTimeKeysRequestMessage + | IdentityLogInPasswordUserRequestMessage + | IdentityLogInWalletUserRequestMessage + | IdentityGenerateNonceRequestMessage + | IdentityPublishWebPrekeysRequestMessage; export type WorkerRequestProxyMessage = { +id: number, @@ -144,6 +227,11 @@ CLIENT_STORE: 1, GET_CURRENT_USER_ID: 2, GET_PERSIST_STORAGE_ITEM: 3, + IDENTITY_GET_KEYSERVER_KEYS: 4, + IDENTITY_GET_OUTBOUND_KEYS_FOR_USER: 5, + IDENTITY_GET_INBOUND_KEYS_FOR_USER: 6, + IDENTITY_AUTH_RESULT: 7, + IDENTITY_GENERATE_NONCE: 8, }); export type PongWorkerResponseMessage = { @@ -166,11 +254,41 @@ +item: string, }; +export type IdentityGetKeyserverKeysResponseMessage = { + +type: 4, + +keys: DeviceOlmOutboundKeys, +}; + +export type IdentityGetOutboundKeysResponseMessage = { + +type: 5, + +keys: UserDevicesOlmOutboundKeys[], +}; + +export type IdentityGetInboundKeysResponseMessage = { + +type: 6, + +keys: UserDevicesOlmInboundKeys, +}; + +export type IdentityAuthResultResponseMessage = { + +type: 7, + +result: IdentityAuthResult, +}; + +export type IdentityGenerateNonceResponseMessage = { + +type: 8, + +nonce: string, +}; + export type WorkerResponseMessage = | PongWorkerResponseMessage | ClientStoreResponseMessage | GetCurrentUserIDResponseMessage - | GetPersistStorageItemResponseMessage; + | GetPersistStorageItemResponseMessage + | IdentityGetKeyserverKeysResponseMessage + | IdentityGetOutboundKeysResponseMessage + | IdentityGetInboundKeysResponseMessage + | IdentityAuthResultResponseMessage + | IdentityGenerateNonceResponseMessage; export type WorkerResponseProxyMessage = { +id?: number,