diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js index 5cd1387ce..0a52dc446 100644 --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -1,85 +1,93 @@ // @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 DeviceOlmOutboundKeys = { +identityKeysBlob: IdentityKeysBlob, +contentInitializationInfo: OlmSessionInitializationInfo, +notifInitializationInfo: OlmSessionInitializationInfo, +payloadSignature: string, +socialProof: ?string, }; export const deviceOlmOutboundKeysValidator: TInterface = tShape({ identityKeysBlob: identityKeysBlobValidator, contentInitializationInfo: olmSessionInitializationInfoValidator, notifInitializationInfo: olmSessionInitializationInfoValidator, payloadSignature: t.String, socialProof: t.maybe(t.String), }); +export type UserDevicesOlmOutboundKeys = { + +deviceID: string, + +keys: ?DeviceOlmOutboundKeys, +}; + export interface IdentityServiceClient { +deleteUser: () => Promise; +getKeyserverKeys: string => Promise; +registerUser?: ( username: string, password: string, ) => Promise; + +getOutboundKeysForUser?: ( + userID: 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/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js index f7183983f..20b0dbd1f 100644 --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -1,128 +1,207 @@ // @flow import identityServiceConfig from 'lib/facts/identity-service.js'; import { type IdentityServiceAuthLayer, type IdentityServiceClient, type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, + type UserDevicesOlmOutboundKeys, } 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(), }; 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); }; + + getOutboundKeysForUser: ( + userID: string, + ) => Promise = async (userID: string) => { + const client = this.authClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + + const request = new IdentityAuthStructs.OutboundKeysForUserRequest(); + request.setUserId(userID); + const response = await client.getOutboundKeysForUser(request); + const devicesMap = response.toObject()?.devicesMap; + + if (!devicesMap || !Array.isArray(devicesMap)) { + throw new Error('Invalid devicesMap'); + } + + const devicesKeys: (?UserDevicesOlmOutboundKeys)[] = devicesMap.map( + ([deviceID, outboundKeysInfo]) => { + const identityInfo = outboundKeysInfo?.identityInfo; + const payload = identityInfo?.payload; + const contentPreKey = outboundKeysInfo?.contentPrekey; + const notifPreKey = outboundKeysInfo?.notifPrekey; + + if (!(typeof deviceID === 'string')) { + console.log(`Invalid deviceID in devicesMap: ${deviceID}`); + return null; + } + + if ( + !outboundKeysInfo.oneTimeContentPrekey || + !outboundKeysInfo.oneTimeNotifPrekey + ) { + console.log(`Missing one time key for device ${deviceID}`); + return { + deviceID, + keys: null, + }; + } + + const deviceKeys = { + identityKeysBlob: payload ? JSON.parse(payload) : null, + contentInitializationInfo: { + prekey: contentPreKey?.prekey, + prekeySignature: contentPreKey?.prekeySignature, + oneTimeKey: outboundKeysInfo.oneTimeContentPrekey, + }, + notifInitializationInfo: { + prekey: notifPreKey?.prekey, + prekeySignature: notifPreKey?.prekeySignature, + oneTimeKey: outboundKeysInfo.oneTimeNotifPrekey, + }, + payloadSignature: identityInfo?.payloadSignature, + socialProof: identityInfo?.socialProof, + }; + + try { + const validatedKeys = assertWithValidator( + deviceKeys, + deviceOlmOutboundKeysValidator, + ); + return { + deviceID, + keys: validatedKeys, + }; + } catch (e) { + console.log(e); + return { + deviceID, + keys: null, + }; + } + }, + ); + + return devicesKeys.filter(Boolean); + }; } export { IdentityServiceClientWrapper };