diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js index f6df3e829..5cd1387ce 100644 --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -1,85 +1,85 @@ // @flow import t, { type TInterface } from 'tcomb'; import { identityKeysBlobValidator, type IdentityKeysBlob, } from './crypto-types.js'; import { type OlmSessionInitializationInfo, olmSessionInitializationInfoValidator, } from './request-types.js'; import { tShape } from '../utils/validation-utils.js'; export type UserLoginResponse = { +userId: string, +accessToken: string, }; // This type should not be altered without also updating // OutboundKeyInfoResponse in native/native_rust_library/src/lib.rs export type OutboundKeyInfoResponse = { +payload: string, +payloadSignature: string, +socialProof: ?string, +contentPrekey: string, +contentPrekeySignature: string, +notifPrekey: string, +notifPrekeySignature: string, +oneTimeContentPrekey: ?string, +oneTimeNotifPrekey: ?string, }; -export type KeyserverKeys = { +export type DeviceOlmOutboundKeys = { +identityKeysBlob: IdentityKeysBlob, +contentInitializationInfo: OlmSessionInitializationInfo, +notifInitializationInfo: OlmSessionInitializationInfo, +payloadSignature: string, +socialProof: ?string, }; -export const keyserverKeysValidator: TInterface = - tShape({ +export const deviceOlmOutboundKeysValidator: TInterface = + tShape({ identityKeysBlob: identityKeysBlobValidator, contentInitializationInfo: olmSessionInitializationInfoValidator, notifInitializationInfo: olmSessionInitializationInfoValidator, payloadSignature: t.String, socialProof: t.maybe(t.String), }); export interface IdentityServiceClient { +deleteUser: () => Promise; - +getKeyserverKeys: string => Promise; + +getKeyserverKeys: string => Promise; +registerUser?: ( username: string, password: string, ) => Promise; } export type IdentityServiceAuthLayer = { +userID: string, +deviceID: string, +commServicesAccessToken: string, }; // This type should not be altered without also updating // InboundKeyInfoResponse in native/native_rust_library/src/lib.rs export type InboundKeyInfoResponse = { +payload: string, +payloadSignature: string, +socialProof?: ?string, +contentPrekey: string, +contentPrekeySignature: string, +notifPrekey: string, +notifPrekeySignature: string, +username?: ?string, +walletAddress?: ?string, }; export type IdentityAuthResult = { +userID: string, +accessToken: string, +username: string, }; export const ONE_TIME_KEYS_NUMBER = 10; diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js index 70353d9ad..143d6e94c 100644 --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -1,155 +1,160 @@ // @flow import * as React from 'react'; import { getOneTimeKeyArray } from 'lib/shared/crypto-utils.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { type IdentityServiceClient, type UserLoginResponse, - type KeyserverKeys, - keyserverKeysValidator, + type DeviceOlmOutboundKeys, + deviceOlmOutboundKeysValidator, } from 'lib/types/identity-service-types.js'; import { ONE_TIME_KEYS_NUMBER } from 'lib/types/identity-service-types.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { getCommServicesAuthMetadataEmitter } from '../event-emitters/csa-auth-metadata-emitter.js'; import { commCoreModule, commRustModule } from '../native-modules.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; type Props = { +children: React.Node, }; function IdentityServiceContextProvider(props: Props): React.Node { const { children } = props; const authMetadataPromiseRef = React.useRef>(); if (!authMetadataPromiseRef.current) { authMetadataPromiseRef.current = (async () => { const { userID, accessToken } = await commCoreModule.getCommServicesAuthMetadata(); return { userID, accessToken }; })(); } React.useEffect(() => { const metadataEmitter = getCommServicesAuthMetadataEmitter(); const subscription = metadataEmitter.addListener( 'commServicesAuthMetadata', (authMetadata: UserLoginResponse) => { authMetadataPromiseRef.current = Promise.resolve({ userID: authMetadata.userId, accessToken: authMetadata.accessToken, }); }, ); return () => subscription.remove(); }, []); const getAuthMetadata = React.useCallback< () => Promise<{ +deviceID: string, +userID: string, +accessToken: string, }>, >(async () => { const deviceID = await getContentSigningKey(); const authMetadata = await authMetadataPromiseRef.current; const userID = authMetadata?.userID; const accessToken = authMetadata?.accessToken; if (!deviceID || !userID || !accessToken) { throw new Error('Identity service client is not initialized'); } return { deviceID, userID, accessToken }; }, []); const client = React.useMemo( () => ({ deleteUser: async () => { const { deviceID, userID, accessToken } = await getAuthMetadata(); return commRustModule.deleteUser(userID, deviceID, accessToken); }, - getKeyserverKeys: async (keyserverID: string): Promise => { + getKeyserverKeys: async ( + keyserverID: string, + ): Promise => { const { deviceID, userID, accessToken } = await getAuthMetadata(); const result = await commRustModule.getKeyserverKeys( userID, deviceID, accessToken, keyserverID, ); const resultObject = JSON.parse(result); const payload = resultObject?.payload; const keyserverKeys = { identityKeysBlob: payload ? JSON.parse(payload) : null, contentInitializationInfo: { prekey: resultObject?.contentPrekey, prekeySignature: resultObject?.contentPrekeySignature, oneTimeKey: resultObject?.oneTimeContentPrekey, }, notifInitializationInfo: { prekey: resultObject?.notifPrekey, prekeySignature: resultObject?.notifPrekeySignature, oneTimeKey: resultObject?.oneTimeNotifPrekey, }, payloadSignature: resultObject?.payloadSignature, socialProof: resultObject?.socialProof, }; if (!keyserverKeys.contentInitializationInfo.oneTimeKey) { throw new Error('Missing content one time key'); } if (!keyserverKeys.notifInitializationInfo.oneTimeKey) { throw new Error('Missing notif one time key'); } - return assertWithValidator(keyserverKeys, keyserverKeysValidator); + return assertWithValidator( + keyserverKeys, + deviceOlmOutboundKeysValidator, + ); }, registerUser: async (username: string, password: string) => { await commCoreModule.initializeCryptoAccount(); const [ { blobPayload, signature }, notificationsOneTimeKeys, primaryOneTimeKeys, prekeys, ] = await Promise.all([ commCoreModule.getUserPublicKey(), commCoreModule.getNotificationsOneTimeKeys(ONE_TIME_KEYS_NUMBER), commCoreModule.getPrimaryOneTimeKeys(ONE_TIME_KEYS_NUMBER), commCoreModule.generateAndGetPrekeys(), ]); const registrationResult = await commRustModule.registerUser( username, password, blobPayload, signature, prekeys.contentPrekey, prekeys.contentPrekeySignature, prekeys.notifPrekey, prekeys.notifPrekeySignature, getOneTimeKeyArray(primaryOneTimeKeys), getOneTimeKeyArray(notificationsOneTimeKeys), ); const { userID, accessToken } = JSON.parse(registrationResult); return { accessToken, userID, username }; }, }), [getAuthMetadata], ); const value = React.useMemo( () => ({ identityClient: client, }), [client], ); return ( {children} ); } export default IdentityServiceContextProvider; diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js index 5dcf01b05..f7183983f 100644 --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -1,129 +1,128 @@ // @flow import identityServiceConfig from 'lib/facts/identity-service.js'; import { type IdentityServiceAuthLayer, type IdentityServiceClient, - type KeyserverKeys, - keyserverKeysValidator, + type DeviceOlmOutboundKeys, + deviceOlmOutboundKeysValidator, } from 'lib/types/identity-service-types.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { VersionInterceptor, AuthInterceptor } from './interceptor.js'; import * as IdentityAuthClient from '../protobufs/identity-auth-client.cjs'; import * as IdentityAuthStructs from '../protobufs/identity-auth-structs.cjs'; import { Empty } from '../protobufs/identity-unauth-structs.cjs'; import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs'; class IdentityServiceClientWrapper implements IdentityServiceClient { authClient: ?IdentityAuthClient.IdentityClientServicePromiseClient; unauthClient: IdentityUnauthClient.IdentityClientServicePromiseClient; constructor(authLayer: ?IdentityServiceAuthLayer) { if (authLayer) { this.authClient = IdentityServiceClientWrapper.createAuthClient(authLayer); } this.unauthClient = IdentityServiceClientWrapper.createUnauthClient(); } static determineSocketAddr(): string { return process.env.IDENTITY_SOCKET_ADDR ?? identityServiceConfig.defaultURL; } static createAuthClient( authLayer: IdentityServiceAuthLayer, ): IdentityAuthClient.IdentityClientServicePromiseClient { const { userID, deviceID, commServicesAccessToken } = authLayer; const identitySocketAddr = IdentityServiceClientWrapper.determineSocketAddr(); const versionInterceptor = new VersionInterceptor(); const authInterceptor = new AuthInterceptor( userID, deviceID, commServicesAccessToken, ); const authClientOpts = { unaryInterceptors: [versionInterceptor, authInterceptor], }; return new IdentityAuthClient.IdentityClientServicePromiseClient( identitySocketAddr, null, authClientOpts, ); } static createUnauthClient(): IdentityUnauthClient.IdentityClientServicePromiseClient { const identitySocketAddr = IdentityServiceClientWrapper.determineSocketAddr(); const versionInterceptor = new VersionInterceptor(); const unauthClientOpts = { unaryInterceptors: [versionInterceptor], }; return new IdentityUnauthClient.IdentityClientServicePromiseClient( identitySocketAddr, null, unauthClientOpts, ); } deleteUser: () => Promise = async () => { if (!this.authClient) { throw new Error('Identity service client is not initialized'); } await this.authClient.deleteUser(new Empty()); }; - getKeyserverKeys: (keyserverID: string) => Promise = async ( - keyserverID: string, - ) => { - const client = this.authClient; - if (!client) { - throw new Error('Identity service client is not initialized'); - } - - const request = new IdentityAuthStructs.OutboundKeysForUserRequest(); - request.setUserId(keyserverID); - const response = await client.getKeyserverKeys(request); - - const keyserverInfo = response.getKeyserverInfo(); - const identityInfo = keyserverInfo?.getIdentityInfo(); - const contentPreKey = keyserverInfo?.getContentPrekey(); - const notifPreKey = keyserverInfo?.getNotifPrekey(); - const payload = identityInfo?.getPayload(); - - const keyserverKeys = { - identityKeysBlob: payload ? JSON.parse(payload) : null, - contentInitializationInfo: { - prekey: contentPreKey?.getPrekey(), - prekeySignature: contentPreKey?.getPrekeySignature(), - oneTimeKey: keyserverInfo?.getOneTimeContentPrekey(), - }, - notifInitializationInfo: { - prekey: notifPreKey?.getPrekey(), - prekeySignature: notifPreKey?.getPrekeySignature(), - oneTimeKey: keyserverInfo?.getOneTimeNotifPrekey(), - }, - payloadSignature: identityInfo?.getPayloadSignature(), - socialProof: identityInfo?.getSocialProof(), + getKeyserverKeys: (keyserverID: string) => Promise = + async (keyserverID: string) => { + const client = this.authClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + + const request = new IdentityAuthStructs.OutboundKeysForUserRequest(); + request.setUserId(keyserverID); + const response = await client.getKeyserverKeys(request); + + const keyserverInfo = response.getKeyserverInfo(); + const identityInfo = keyserverInfo?.getIdentityInfo(); + const contentPreKey = keyserverInfo?.getContentPrekey(); + const notifPreKey = keyserverInfo?.getNotifPrekey(); + const payload = identityInfo?.getPayload(); + + const keyserverKeys = { + identityKeysBlob: payload ? JSON.parse(payload) : null, + contentInitializationInfo: { + prekey: contentPreKey?.getPrekey(), + prekeySignature: contentPreKey?.getPrekeySignature(), + oneTimeKey: keyserverInfo?.getOneTimeContentPrekey(), + }, + notifInitializationInfo: { + prekey: notifPreKey?.getPrekey(), + prekeySignature: notifPreKey?.getPrekeySignature(), + oneTimeKey: keyserverInfo?.getOneTimeNotifPrekey(), + }, + payloadSignature: identityInfo?.getPayloadSignature(), + socialProof: identityInfo?.getSocialProof(), + }; + + if (!keyserverKeys.contentInitializationInfo.oneTimeKey) { + throw new Error('Missing content one time key'); + } + if (!keyserverKeys.notifInitializationInfo.oneTimeKey) { + throw new Error('Missing notif one time key'); + } + + return assertWithValidator(keyserverKeys, deviceOlmOutboundKeysValidator); }; - - if (!keyserverKeys.contentInitializationInfo.oneTimeKey) { - throw new Error('Missing content one time key'); - } - if (!keyserverKeys.notifInitializationInfo.oneTimeKey) { - throw new Error('Missing notif one time key'); - } - - return assertWithValidator(keyserverKeys, keyserverKeysValidator); - }; } export { IdentityServiceClientWrapper };