diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js index 94eb39eab..9723d192c 100644 --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -1,818 +1,833 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import { extractKeyserverIDFromID, sortThreadIDsPerKeyserver, sortCalendarQueryPerKeyserver, } from '../keyserver-conn/keyserver-call-utils.js'; import type { CallKeyserverEndpoint } from '../keyserver-conn/keyserver-conn-types.js'; import { preRequestUserStateSelector } from '../selectors/account-selectors.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import threadWatcher from '../shared/thread-watcher.js'; import type { LogInInfo, LogInResult, RegisterResult, RegisterInfo, UpdateUserSettingsRequest, PolicyAcknowledgmentRequest, ClaimUsernameResponse, LogInRequest, KeyserverAuthResult, KeyserverAuthInfo, KeyserverAuthRequest, ClientLogInResponse, KeyserverLogOutResult, LogOutResult, } from '../types/account-types.js'; import type { UpdateUserAvatarRequest, UpdateUserAvatarResponse, } from '../types/avatar-types.js'; import type { RawEntryInfo, CalendarQuery } from '../types/entry-types.js'; import type { IdentityAuthResult } from '../types/identity-service-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, } from '../types/message-types.js'; import type { GetSessionPublicKeysArgs, GetOlmSessionInitializationDataResponse, } from '../types/request-types.js'; import type { UserSearchResult, ExactUserSearchResult, } from '../types/search-types.js'; import type { SessionPublicKeys, PreRequestUserState, } from '../types/session-types.js'; import type { SubscriptionUpdateRequest, SubscriptionUpdateResult, } from '../types/subscription-types.js'; import type { RawThreadInfos } from '../types/thread-types'; import type { CurrentUserInfo, UserInfo, PasswordUpdate, LoggedOutUserInfo, } from '../types/user-types.js'; import type { CallSingleKeyserverEndpoint, CallSingleKeyserverEndpointOptions, } from '../utils/call-single-keyserver-endpoint.js'; import { getConfig } from '../utils/config.js'; import { useKeyserverCall } from '../utils/keyserver-call.js'; import { useSelector } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; import sleep from '../utils/sleep.js'; import { ashoatKeyserverID } from '../utils/validation-utils.js'; const loggedOutUserInfo: LoggedOutUserInfo = { anonymous: true, }; export type KeyserverLogOutInput = { +preRequestUserState: PreRequestUserState, +keyserverIDs?: $ReadOnlyArray, }; const logOutActionTypes = Object.freeze({ started: 'LOG_OUT_STARTED', success: 'LOG_OUT_SUCCESS', failed: 'LOG_OUT_FAILED', }); const logOut = ( callKeyserverEndpoint: CallKeyserverEndpoint, allKeyserverIDs: $ReadOnlyArray, ): ((input: KeyserverLogOutInput) => Promise) => async input => { const { preRequestUserState } = input; const keyserverIDs = input.keyserverIDs ?? allKeyserverIDs; const requests: { [string]: {} } = {}; for (const keyserverID of keyserverIDs) { requests[keyserverID] = {}; } let response = null; try { response = await Promise.race([ callKeyserverEndpoint('log_out', requests), (async () => { await sleep(500); throw new Error('log_out took more than 500ms'); })(), ]); } catch {} const currentUserInfo = response ? loggedOutUserInfo : null; return { currentUserInfo, preRequestUserState, keyserverIDs }; }; function useLogOut(): ( keyserverIDs?: $ReadOnlyArray, ) => Promise { const preRequestUserState = useSelector(preRequestUserStateSelector); const callKeyserverLogOut = useKeyserverCall(logOut); const commServicesAccessToken = useSelector( state => state.commServicesAccessToken, ); return React.useCallback( async (keyserverIDs?: $ReadOnlyArray) => { const { keyserverIDs: _, ...result } = await callKeyserverLogOut({ preRequestUserState, keyserverIDs, }); return { ...result, preRequestUserState: { ...result.preRequestUserState, commServicesAccessToken, }, }; }, [callKeyserverLogOut, commServicesAccessToken, preRequestUserState], ); } const claimUsernameActionTypes = Object.freeze({ started: 'CLAIM_USERNAME_STARTED', success: 'CLAIM_USERNAME_SUCCESS', failed: 'CLAIM_USERNAME_FAILED', }); const claimUsernameCallSingleKeyserverEndpointOptions = { timeout: 500 }; const claimUsername = ( callKeyserverEndpoint: CallKeyserverEndpoint, ): (() => Promise) => async () => { const requests = { [ashoatKeyserverID]: {} }; const responses = await callKeyserverEndpoint('claim_username', requests, { ...claimUsernameCallSingleKeyserverEndpointOptions, }); const response = responses[ashoatKeyserverID]; return { message: response.message, signature: response.signature, }; }; function useClaimUsername(): () => Promise { return useKeyserverCall(claimUsername); } const deleteKeyserverAccountActionTypes = Object.freeze({ started: 'DELETE_KEYSERVER_ACCOUNT_STARTED', success: 'DELETE_KEYSERVER_ACCOUNT_SUCCESS', failed: 'DELETE_KEYSERVER_ACCOUNT_FAILED', }); const deleteKeyserverAccount = ( callKeyserverEndpoint: CallKeyserverEndpoint, allKeyserverIDs: $ReadOnlyArray, ): ((input: KeyserverLogOutInput) => Promise) => async input => { const { preRequestUserState } = input; const keyserverIDs = input.keyserverIDs ?? allKeyserverIDs; const requests: { [string]: {} } = {}; for (const keyserverID of keyserverIDs) { requests[keyserverID] = {}; } await callKeyserverEndpoint('delete_account', requests); return { currentUserInfo: loggedOutUserInfo, preRequestUserState, keyserverIDs, }; }; function useDeleteKeyserverAccount(): ( keyserverIDs?: $ReadOnlyArray, ) => Promise { const preRequestUserState = useSelector(preRequestUserStateSelector); const callKeyserverDeleteAccount = useKeyserverCall(deleteKeyserverAccount); return React.useCallback( (keyserverIDs?: $ReadOnlyArray) => callKeyserverDeleteAccount({ preRequestUserState, keyserverIDs }), [callKeyserverDeleteAccount, preRequestUserState], ); } const deleteAccountActionTypes = Object.freeze({ started: 'DELETE_ACCOUNT_STARTED', success: 'DELETE_ACCOUNT_SUCCESS', failed: 'DELETE_ACCOUNT_FAILED', }); function useDeleteAccount(): () => Promise { const client = React.useContext(IdentityClientContext); const identityClient = client?.identityClient; const preRequestUserState = useSelector(preRequestUserStateSelector); const callKeyserverDeleteAccount = useKeyserverCall(deleteKeyserverAccount); const commServicesAccessToken = useSelector( state => state.commServicesAccessToken, ); return React.useCallback(async () => { const identityPromise = (async () => { if (!usingCommServicesAccessToken) { return undefined; } if (!identityClient) { throw new Error('Identity service client is not initialized'); } return await identityClient.deleteUser(); })(); const [keyserverResult] = await Promise.all([ callKeyserverDeleteAccount({ preRequestUserState, }), identityPromise, ]); const { keyserverIDs: _, ...result } = keyserverResult; return { ...result, preRequestUserState: { ...result.preRequestUserState, commServicesAccessToken, }, }; }, [ callKeyserverDeleteAccount, commServicesAccessToken, identityClient, preRequestUserState, ]); } const keyserverRegisterActionTypes = Object.freeze({ started: 'KEYSERVER_REGISTER_STARTED', success: 'KEYSERVER_REGISTER_SUCCESS', failed: 'KEYSERVER_REGISTER_FAILED', }); const registerCallSingleKeyserverEndpointOptions = { timeout: 60000 }; const keyserverRegister = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): (( registerInfo: RegisterInfo, options?: CallSingleKeyserverEndpointOptions, ) => Promise) => async (registerInfo, options) => { const deviceTokenUpdateRequest = registerInfo.deviceTokenUpdateRequest[ashoatKeyserverID]; const response = await callSingleKeyserverEndpoint( 'create_account', { ...registerInfo, deviceTokenUpdateRequest, platformDetails: getConfig().platformDetails, }, { ...registerCallSingleKeyserverEndpointOptions, ...options, }, ); return { currentUserInfo: response.currentUserInfo, rawMessageInfos: response.rawMessageInfos, threadInfos: response.cookieChange.threadInfos, userInfos: response.cookieChange.userInfos, calendarQuery: registerInfo.calendarQuery, }; }; export type KeyserverAuthInput = $ReadOnly<{ ...KeyserverAuthInfo, +preRequestUserInfo: ?CurrentUserInfo, }>; const keyserverAuthActionTypes = Object.freeze({ started: 'KEYSERVER_AUTH_STARTED', success: 'KEYSERVER_AUTH_SUCCESS', failed: 'KEYSERVER_AUTH_FAILED', }); const keyserverAuthCallSingleKeyserverEndpointOptions = { timeout: 60000 }; const keyserverAuth = ( callKeyserverEndpoint: CallKeyserverEndpoint, ): ((input: KeyserverAuthInput) => Promise) => async keyserverAuthInfo => { const watchedIDs = threadWatcher.getWatchedIDs(); const { logInActionSource, calendarQuery, keyserverData, deviceTokenUpdateInput, preRequestUserInfo, ...restLogInInfo } = keyserverAuthInfo; const keyserverIDs = Object.keys(keyserverData); const watchedIDsPerKeyserver = sortThreadIDsPerKeyserver(watchedIDs); const calendarQueryPerKeyserver = sortCalendarQueryPerKeyserver( calendarQuery, keyserverIDs, ); const requests: { [string]: KeyserverAuthRequest } = {}; for (const keyserverID of keyserverIDs) { requests[keyserverID] = { ...restLogInInfo, deviceTokenUpdateRequest: deviceTokenUpdateInput[keyserverID], watchedIDs: watchedIDsPerKeyserver[keyserverID] ?? [], calendarQuery: calendarQueryPerKeyserver[keyserverID], platformDetails: getConfig().platformDetails, initialContentEncryptedMessage: keyserverData[keyserverID].initialContentEncryptedMessage, initialNotificationsEncryptedMessage: keyserverData[keyserverID].initialNotificationsEncryptedMessage, source: logInActionSource, }; } const responses: { +[string]: ClientLogInResponse } = await callKeyserverEndpoint( 'keyserver_auth', requests, keyserverAuthCallSingleKeyserverEndpointOptions, ); const userInfosArrays = []; let threadInfos: RawThreadInfos = {}; const calendarResult: WritableCalendarResult = { calendarQuery: keyserverAuthInfo.calendarQuery, rawEntryInfos: [], }; const messagesResult: WritableGenericMessagesResult = { messageInfos: [], truncationStatus: {}, watchedIDsAtRequestTime: watchedIDs, currentAsOf: {}, }; let updatesCurrentAsOf: { +[string]: number } = {}; for (const keyserverID in responses) { threadInfos = { ...responses[keyserverID].cookieChange.threadInfos, ...threadInfos, }; if (responses[keyserverID].rawEntryInfos) { calendarResult.rawEntryInfos = calendarResult.rawEntryInfos.concat( responses[keyserverID].rawEntryInfos, ); } messagesResult.messageInfos = messagesResult.messageInfos.concat( responses[keyserverID].rawMessageInfos, ); messagesResult.truncationStatus = { ...messagesResult.truncationStatus, ...responses[keyserverID].truncationStatuses, }; messagesResult.currentAsOf = { ...messagesResult.currentAsOf, [keyserverID]: responses[keyserverID].serverTime, }; updatesCurrentAsOf = { ...updatesCurrentAsOf, [keyserverID]: responses[keyserverID].serverTime, }; userInfosArrays.push(responses[keyserverID].userInfos); userInfosArrays.push(responses[keyserverID].cookieChange.userInfos); } const userInfos = mergeUserInfos(...userInfosArrays); return { threadInfos, currentUserInfo: responses[ashoatKeyserverID].currentUserInfo, calendarResult, messagesResult, userInfos, updatesCurrentAsOf, logInActionSource: keyserverAuthInfo.logInActionSource, notAcknowledgedPolicies: responses[ashoatKeyserverID].notAcknowledgedPolicies, preRequestUserInfo, }; }; function useKeyserverAuth(): ( input: KeyserverAuthInfo, ) => Promise { const preRequestUserInfo = useSelector(state => state.currentUserInfo); const callKeyserverAuth = useKeyserverCall(keyserverAuth); return React.useCallback( (input: KeyserverAuthInfo) => callKeyserverAuth({ preRequestUserInfo, ...input }), [callKeyserverAuth, preRequestUserInfo], ); } const identityRegisterActionTypes = Object.freeze({ started: 'IDENTITY_REGISTER_STARTED', success: 'IDENTITY_REGISTER_SUCCESS', failed: 'IDENTITY_REGISTER_FAILED', }); function useIdentityRegister(): ( username: string, password: string, ) => Promise { const client = React.useContext(IdentityClientContext); const identityClient = client?.identityClient; return React.useCallback( (username, password) => { if (!identityClient) { throw new Error('Identity service client is not initialized'); } if (!identityClient.registerUser) { throw new Error('Register user method unimplemented'); } return identityClient.registerUser(username, password); }, [identityClient], ); } +const identityGenerateNonceActionTypes = Object.freeze({ + started: 'IDENTITY_GENERATE_NONCE_STARTED', + success: 'IDENTITY_GENERATE_NONCE_SUCCESS', + failed: 'IDENTITY_GENERATE_NONCE_FAILED', +}); +function useIdentityGenerateNonce(): () => Promise { + const client = React.useContext(IdentityClientContext); + const identityClient = client?.identityClient; + invariant(identityClient, 'Identity client should be set'); + return identityClient.generateNonce; +} + function mergeUserInfos( ...userInfoArrays: Array<$ReadOnlyArray> ): UserInfo[] { const merged: { [string]: UserInfo } = {}; for (const userInfoArray of userInfoArrays) { for (const userInfo of userInfoArray) { merged[userInfo.id] = userInfo; } } const flattened = []; for (const id in merged) { flattened.push(merged[id]); } return flattened; } type WritableGenericMessagesResult = { messageInfos: RawMessageInfo[], truncationStatus: MessageTruncationStatuses, watchedIDsAtRequestTime: string[], currentAsOf: { [keyserverID: string]: number }, }; type WritableCalendarResult = { rawEntryInfos: RawEntryInfo[], calendarQuery: CalendarQuery, }; const identityLogInActionTypes = Object.freeze({ started: 'IDENTITY_LOG_IN_STARTED', success: 'IDENTITY_LOG_IN_SUCCESS', failed: 'IDENTITY_LOG_IN_FAILED', }); function useIdentityPasswordLogIn(): ( username: string, password: string, ) => Promise { const client = React.useContext(IdentityClientContext); const identityClient = client?.identityClient; return React.useCallback( (username, password) => { if (!identityClient) { throw new Error('Identity service client is not initialized'); } return identityClient.logInPasswordUser(username, password); }, [identityClient], ); } const logInActionTypes = Object.freeze({ started: 'LOG_IN_STARTED', success: 'LOG_IN_SUCCESS', failed: 'LOG_IN_FAILED', }); const logInCallSingleKeyserverEndpointOptions = { timeout: 60000 }; const logIn = ( callKeyserverEndpoint: CallKeyserverEndpoint, ): ((input: LogInInfo) => Promise) => async logInInfo => { const watchedIDs = threadWatcher.getWatchedIDs(); const { logInActionSource, calendarQuery, keyserverIDs: inputKeyserverIDs, ...restLogInInfo } = logInInfo; // Eventually the list of keyservers will be fetched from the // identity service const keyserverIDs = inputKeyserverIDs ?? [ashoatKeyserverID]; const watchedIDsPerKeyserver = sortThreadIDsPerKeyserver(watchedIDs); const calendarQueryPerKeyserver = sortCalendarQueryPerKeyserver( calendarQuery, keyserverIDs, ); const requests: { [string]: LogInRequest } = {}; for (const keyserverID of keyserverIDs) { requests[keyserverID] = { ...restLogInInfo, deviceTokenUpdateRequest: logInInfo.deviceTokenUpdateRequest[keyserverID], source: logInActionSource, watchedIDs: watchedIDsPerKeyserver[keyserverID] ?? [], calendarQuery: calendarQueryPerKeyserver[keyserverID], platformDetails: getConfig().platformDetails, }; } const responses: { +[string]: ClientLogInResponse } = await callKeyserverEndpoint( 'log_in', requests, logInCallSingleKeyserverEndpointOptions, ); const userInfosArrays = []; let threadInfos: RawThreadInfos = {}; const calendarResult: WritableCalendarResult = { calendarQuery: logInInfo.calendarQuery, rawEntryInfos: [], }; const messagesResult: WritableGenericMessagesResult = { messageInfos: [], truncationStatus: {}, watchedIDsAtRequestTime: watchedIDs, currentAsOf: {}, }; let updatesCurrentAsOf: { +[string]: number } = {}; for (const keyserverID in responses) { threadInfos = { ...responses[keyserverID].cookieChange.threadInfos, ...threadInfos, }; if (responses[keyserverID].rawEntryInfos) { calendarResult.rawEntryInfos = calendarResult.rawEntryInfos.concat( responses[keyserverID].rawEntryInfos, ); } messagesResult.messageInfos = messagesResult.messageInfos.concat( responses[keyserverID].rawMessageInfos, ); messagesResult.truncationStatus = { ...messagesResult.truncationStatus, ...responses[keyserverID].truncationStatuses, }; messagesResult.currentAsOf = { ...messagesResult.currentAsOf, [keyserverID]: responses[keyserverID].serverTime, }; updatesCurrentAsOf = { ...updatesCurrentAsOf, [keyserverID]: responses[keyserverID].serverTime, }; userInfosArrays.push(responses[keyserverID].userInfos); userInfosArrays.push(responses[keyserverID].cookieChange.userInfos); } const userInfos = mergeUserInfos(...userInfosArrays); return { threadInfos, currentUserInfo: responses[ashoatKeyserverID].currentUserInfo, calendarResult, messagesResult, userInfos, updatesCurrentAsOf, logInActionSource: logInInfo.logInActionSource, notAcknowledgedPolicies: responses[ashoatKeyserverID].notAcknowledgedPolicies, }; }; function useLogIn(): (input: LogInInfo) => Promise { return useKeyserverCall(logIn); } const changeKeyserverUserPasswordActionTypes = Object.freeze({ started: 'CHANGE_KEYSERVER_USER_PASSWORD_STARTED', success: 'CHANGE_KEYSERVER_USER_PASSWORD_SUCCESS', failed: 'CHANGE_KEYSERVER_USER_PASSWORD_FAILED', }); const changeKeyserverUserPassword = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): ((passwordUpdate: PasswordUpdate) => Promise) => async passwordUpdate => { await callSingleKeyserverEndpoint('update_account', passwordUpdate); }; const searchUsersActionTypes = Object.freeze({ started: 'SEARCH_USERS_STARTED', success: 'SEARCH_USERS_SUCCESS', failed: 'SEARCH_USERS_FAILED', }); const searchUsers = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): ((usernamePrefix: string) => Promise) => async usernamePrefix => { const response = await callSingleKeyserverEndpoint('search_users', { prefix: usernamePrefix, }); return { userInfos: response.userInfos, }; }; const exactSearchUserActionTypes = Object.freeze({ started: 'EXACT_SEARCH_USER_STARTED', success: 'EXACT_SEARCH_USER_SUCCESS', failed: 'EXACT_SEARCH_USER_FAILED', }); const exactSearchUser = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): ((username: string) => Promise) => async username => { const response = await callSingleKeyserverEndpoint('exact_search_user', { username, }); return { userInfo: response.userInfo, }; }; const updateSubscriptionActionTypes = Object.freeze({ started: 'UPDATE_SUBSCRIPTION_STARTED', success: 'UPDATE_SUBSCRIPTION_SUCCESS', failed: 'UPDATE_SUBSCRIPTION_FAILED', }); const updateSubscription = ( callKeyserverEndpoint: CallKeyserverEndpoint, ): (( input: SubscriptionUpdateRequest, ) => Promise) => async input => { const keyserverID = extractKeyserverIDFromID(input.threadID); const requests = { [keyserverID]: input }; const responses = await callKeyserverEndpoint( 'update_user_subscription', requests, ); const response = responses[keyserverID]; return { threadID: input.threadID, subscription: response.threadSubscription, }; }; function useUpdateSubscription(): ( input: SubscriptionUpdateRequest, ) => Promise { return useKeyserverCall(updateSubscription); } const setUserSettingsActionTypes = Object.freeze({ started: 'SET_USER_SETTINGS_STARTED', success: 'SET_USER_SETTINGS_SUCCESS', failed: 'SET_USER_SETTINGS_FAILED', }); const setUserSettings = ( callKeyserverEndpoint: CallKeyserverEndpoint, allKeyserverIDs: $ReadOnlyArray, ): ((input: UpdateUserSettingsRequest) => Promise) => async input => { const requests: { [string]: UpdateUserSettingsRequest } = {}; for (const keyserverID of allKeyserverIDs) { requests[keyserverID] = input; } await callKeyserverEndpoint('update_user_settings', requests); }; function useSetUserSettings(): ( input: UpdateUserSettingsRequest, ) => Promise { return useKeyserverCall(setUserSettings); } const getSessionPublicKeys = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): ((data: GetSessionPublicKeysArgs) => Promise) => async data => { return await callSingleKeyserverEndpoint('get_session_public_keys', data); }; const getOlmSessionInitializationDataActionTypes = Object.freeze({ started: 'GET_OLM_SESSION_INITIALIZATION_DATA_STARTED', success: 'GET_OLM_SESSION_INITIALIZATION_DATA_SUCCESS', failed: 'GET_OLM_SESSION_INITIALIZATION_DATA_FAILED', }); const getOlmSessionInitializationData = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): (( options?: ?CallSingleKeyserverEndpointOptions, ) => Promise) => async options => { return await callSingleKeyserverEndpoint( 'get_olm_session_initialization_data', {}, options, ); }; const policyAcknowledgmentActionTypes = Object.freeze({ started: 'POLICY_ACKNOWLEDGMENT_STARTED', success: 'POLICY_ACKNOWLEDGMENT_SUCCESS', failed: 'POLICY_ACKNOWLEDGMENT_FAILED', }); const policyAcknowledgment = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): ((policyRequest: PolicyAcknowledgmentRequest) => Promise) => async policyRequest => { await callSingleKeyserverEndpoint('policy_acknowledgment', policyRequest); }; const updateUserAvatarActionTypes = Object.freeze({ started: 'UPDATE_USER_AVATAR_STARTED', success: 'UPDATE_USER_AVATAR_SUCCESS', failed: 'UPDATE_USER_AVATAR_FAILED', }); const updateUserAvatar = ( callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint, ): (( avatarDBContent: UpdateUserAvatarRequest, ) => Promise) => async avatarDBContent => { const { updates }: UpdateUserAvatarResponse = await callSingleKeyserverEndpoint('update_user_avatar', avatarDBContent); return { updates }; }; const resetUserStateActionType = 'RESET_USER_STATE'; const setAccessTokenActionType = 'SET_ACCESS_TOKEN'; export { changeKeyserverUserPasswordActionTypes, changeKeyserverUserPassword, claimUsernameActionTypes, useClaimUsername, useDeleteKeyserverAccount, deleteKeyserverAccountActionTypes, getSessionPublicKeys, getOlmSessionInitializationDataActionTypes, getOlmSessionInitializationData, mergeUserInfos, logIn as logInRawAction, identityLogInActionTypes, useIdentityPasswordLogIn, useLogIn, logInActionTypes, useLogOut, logOutActionTypes, keyserverRegister, keyserverRegisterActionTypes, searchUsers, searchUsersActionTypes, exactSearchUser, exactSearchUserActionTypes, useSetUserSettings, setUserSettingsActionTypes, useUpdateSubscription, updateSubscriptionActionTypes, policyAcknowledgment, policyAcknowledgmentActionTypes, updateUserAvatarActionTypes, updateUserAvatar, resetUserStateActionType, setAccessTokenActionType, deleteAccountActionTypes, useDeleteAccount, keyserverAuthActionTypes, useKeyserverAuth, identityRegisterActionTypes, useIdentityRegister, + identityGenerateNonceActionTypes, + useIdentityGenerateNonce, }; diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js index 0bc1388b0..996d462bc 100644 --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -1,123 +1,124 @@ // @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; +logInPasswordUser: ( username: string, password: string, ) => Promise; +getOutboundKeysForUser: ( userID: string, ) => Promise; + +generateNonce: () => 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 identityAuthResultValidator: TInterface = tShape({ userID: t.String, accessToken: t.String, username: t.String, }); export type IdentityDeviceKeyUpload = { +keyPayload: string, +keyPayloadSignature: string, +contentPrekey: string, +contentPrekeySignature: string, +notifPrekey: string, +notifPrekeySignature: string, +contentOneTimeKeys: $ReadOnlyArray, +notifOneTimeKeys: $ReadOnlyArray, }; export const ONE_TIME_KEYS_NUMBER = 10; export const identityDeviceTypes = Object.freeze({ KEYSERVER: 0, WEB: 1, IOS: 2, ANDROID: 3, WINDOWS: 4, MAC_OS: 5, }); diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js index c63ed9829..8494f910e 100644 --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -1,1365 +1,1381 @@ // @flow import type { LogOutResult, KeyserverLogOutResult, LogInStartingPayload, LogInResult, RegisterResult, DefaultNotificationPayload, ClaimUsernameResponse, KeyserverAuthResult, } from './account-types.js'; import type { ActivityUpdateSuccessPayload, QueueActivityUpdatesPayload, SetThreadUnreadStatusPayload, } from './activity-types.js'; import type { UpdateUserAvatarRequest, UpdateUserAvatarResponse, } from './avatar-types.js'; import type { CryptoStore } from './crypto-types.js'; import type { GetVersionActionPayload, LastCommunicatedPlatformDetails, } from './device-types.js'; import type { DraftStore } from './draft-types.js'; import type { EnabledApps, SupportedApps } from './enabled-apps.js'; import type { RawEntryInfo, EntryStore, SaveEntryPayload, CreateEntryPayload, DeleteEntryResult, RestoreEntryPayload, FetchEntryInfosResult, CalendarQueryUpdateResult, CalendarQueryUpdateStartingPayload, CalendarQuery, FetchRevisionsForEntryPayload, } from './entry-types.js'; import type { CalendarFilter, CalendarThreadFilter, SetCalendarDeletedFilterPayload, } from './filter-types.js'; import type { IdentityAuthResult } from './identity-service-types.js'; import type { IntegrityStore } from './integrity-types.js'; import type { KeyserverStore, AddKeyserverPayload, RemoveKeyserverPayload, } from './keyserver-types.js'; import type { LifecycleState } from './lifecycle-state-types.js'; import type { FetchInviteLinksResponse, InviteLink, InviteLinksStore, InviteLinkVerificationResponse, DisableInviteLinkPayload, } from './link-types.js'; import type { LoadingStatus, LoadingInfo } from './loading-types.js'; import type { UpdateMultimediaMessageMediaPayload } from './media-types.js'; import type { MessageReportCreationResult } from './message-report-types.js'; import type { MessageStore, RawMultimediaMessageInfo, FetchMessageInfosPayload, SendMessagePayload, EditMessagePayload, SaveMessagesPayload, NewMessagesPayload, MessageStorePrunePayload, LocallyComposedMessageInfo, SimpleMessagesPayload, FetchPinnedMessagesResult, SearchMessagesResponse, } from './message-types.js'; import type { RawReactionMessageInfo } from './messages/reaction.js'; import type { RawTextMessageInfo } from './messages/text.js'; import type { BaseNavInfo, WebNavInfo } from './nav-types.js'; import { type ForcePolicyAcknowledgmentPayload, type PolicyAcknowledgmentPayload, type UserPolicies, } from './policy-types.js'; import type { RelationshipErrors } from './relationship-types.js'; import type { EnabledReports, ClearDeliveredReportsPayload, QueueReportsPayload, ReportStore, } from './report-types.js'; import type { ProcessServerRequestAction, GetOlmSessionInitializationDataResponse, } from './request-types.js'; import type { UserSearchResult, ExactUserSearchResult, } from './search-types.js'; import type { SetSessionPayload } from './session-types.js'; import type { ConnectionIssue, StateSyncFullActionPayload, StateSyncIncrementalActionPayload, } from './socket-types.js'; import { type ClientStore } from './store-ops-types.js'; import type { SubscriptionUpdateResult } from './subscription-types.js'; import type { GlobalThemeInfo } from './theme-types.js'; import type { ThreadActivityStore } from './thread-activity-types.js'; import type { ThreadStore, ChangeThreadSettingsPayload, LeaveThreadPayload, NewThreadResult, ThreadJoinPayload, ToggleMessagePinResult, LegacyThreadStore, RoleModificationPayload, RoleDeletionPayload, } from './thread-types.js'; import type { ClientUpdatesResultWithUserInfos } from './update-types.js'; import type { CurrentUserInfo, UserInfos, UserStore } from './user-types.js'; import type { SetDeviceTokenActionPayload } from '../actions/device-actions.js'; import type { UpdateConnectionStatusPayload, SetLateResponsePayload, UpdateKeyserverReachabilityPayload, } from '../keyserver-conn/keyserver-conn-types.js'; import type { NotifPermissionAlertInfo } from '../utils/push-alerts.js'; export type BaseAppState = { +navInfo: NavInfo, +currentUserInfo: ?CurrentUserInfo, +draftStore: DraftStore, +entryStore: EntryStore, +threadStore: ThreadStore, +userStore: UserStore, +messageStore: MessageStore, +loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } }, +calendarFilters: $ReadOnlyArray, +notifPermissionAlertInfo: NotifPermissionAlertInfo, +actualizedCalendarQuery: CalendarQuery, +watchedThreadIDs: $ReadOnlyArray, +lifecycleState: LifecycleState, +enabledApps: EnabledApps, +reportStore: ReportStore, +nextLocalID: number, +dataLoaded: boolean, +userPolicies: UserPolicies, +commServicesAccessToken: ?string, +inviteLinksStore: InviteLinksStore, +keyserverStore: KeyserverStore, +threadActivityStore: ThreadActivityStore, +integrityStore: IntegrityStore, +globalThemeInfo: GlobalThemeInfo, +customServer: ?string, ... }; export type NativeAppState = BaseAppState<>; export type WebAppState = BaseAppState<> & { +cryptoStore: ?CryptoStore, +pushApiPublicKey: ?string, ... }; export type AppState = NativeAppState | WebAppState; export type ClientWebInitialReduxStateResponse = { +navInfo: WebNavInfo, +currentUserInfo: CurrentUserInfo, +entryStore: EntryStore, +threadStore: ThreadStore, +userInfos: UserInfos, +messageStore: MessageStore, +pushApiPublicKey: ?string, +commServicesAccessToken: null, +inviteLinksStore: InviteLinksStore, +keyserverInfo: WebInitialKeyserverInfo, }; export type ServerWebInitialReduxStateResponse = { +navInfo: WebNavInfo, +currentUserInfo: CurrentUserInfo, +entryStore: EntryStore, +threadStore: LegacyThreadStore, +userInfos: UserInfos, +messageStore: MessageStore, +pushApiPublicKey: ?string, +commServicesAccessToken: null, +inviteLinksStore: InviteLinksStore, +keyserverInfo: WebInitialKeyserverInfo, }; export type WebInitialKeyserverInfo = { +sessionID: ?string, +updatesCurrentAsOf: number, }; export type BaseAction = | { +type: '@@redux/INIT', +payload?: void, } | { +type: 'FETCH_ENTRIES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_ENTRIES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_ENTRIES_SUCCESS', +payload: FetchEntryInfosResult, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_SUCCESS', +payload: LogOutResult, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_SUCCESS', +payload: ClaimUsernameResponse, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_KEYSERVER_ACCOUNT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_KEYSERVER_ACCOUNT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_KEYSERVER_ACCOUNT_SUCCESS', +payload: KeyserverLogOutResult, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_SUCCESS', +payload: LogOutResult, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_LOCAL_ENTRY', +payload: RawEntryInfo, } | { +type: 'CREATE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_ENTRY_SUCCESS', +payload: CreateEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_SUCCESS', +payload: SaveEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'CONCURRENT_MODIFICATION_RESET', +payload: { +id: string, +dbText: string, }, } | { +type: 'DELETE_ENTRY_STARTED', +loadingInfo: LoadingInfo, +payload: { +localID: ?string, +serverID: ?string, }, } | { +type: 'DELETE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ENTRY_SUCCESS', +payload: ?DeleteEntryResult, +loadingInfo: LoadingInfo, } | { +type: 'IDENTITY_LOG_IN_STARTED', +loadingInfo: LoadingInfo, +payload?: void, } | { +type: 'IDENTITY_LOG_IN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'IDENTITY_LOG_IN_SUCCESS', +payload: IdentityAuthResult, +loadingInfo: LoadingInfo, } | { +type: 'KEYSERVER_AUTH_STARTED', +loadingInfo: LoadingInfo, +payload: LogInStartingPayload, } | { +type: 'KEYSERVER_AUTH_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'KEYSERVER_AUTH_SUCCESS', +payload: KeyserverAuthResult, +loadingInfo: LoadingInfo, } | { +type: 'LOG_IN_STARTED', +loadingInfo: LoadingInfo, +payload: LogInStartingPayload, } | { +type: 'LOG_IN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LOG_IN_SUCCESS', +payload: LogInResult, +loadingInfo: LoadingInfo, } | { +type: 'KEYSERVER_REGISTER_STARTED', +loadingInfo: LoadingInfo, +payload: LogInStartingPayload, } | { +type: 'KEYSERVER_REGISTER_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'KEYSERVER_REGISTER_SUCCESS', +payload: RegisterResult, +loadingInfo: LoadingInfo, } | { +type: 'IDENTITY_REGISTER_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'IDENTITY_REGISTER_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'IDENTITY_REGISTER_SUCCESS', +payload: IdentityAuthResult, +loadingInfo: LoadingInfo, } + | { + +type: 'IDENTITY_GENERATE_NONCE_STARTED', + +payload?: void, + +loadingInfo: LoadingInfo, + } + | { + +type: 'IDENTITY_GENERATE_NONCE_FAILED', + +error: true, + +payload: Error, + +loadingInfo: LoadingInfo, + } + | { + +type: 'IDENTITY_GENERATE_NONCE_SUCCESS', + +payload?: void, + +loadingInfo: LoadingInfo, + } | { +type: 'CHANGE_KEYSERVER_USER_PASSWORD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_KEYSERVER_USER_PASSWORD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_KEYSERVER_USER_PASSWORD_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_SUCCESS', +payload: LeaveThreadPayload, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_SUCCESS', +payload: NewThreadResult, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_SUCCESS', +payload: FetchRevisionsForEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_SUCCESS', +payload: RestoreEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_SUCCESS', +payload: ThreadJoinPayload, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_SUCCESS', +payload: LeaveThreadPayload, +loadingInfo: LoadingInfo, } | { +type: 'SET_NEW_SESSION', +payload: SetSessionPayload, } | { +type: 'persist/REHYDRATE', +payload: ?BaseAppState<>, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_SUCCESS', +payload: FetchMessageInfosPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_SUCCESS', +payload: FetchMessageInfosPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_SUCCESS', +payload: SimpleMessagesPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_TEXT_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawTextMessageInfo, } | { +type: 'SEND_TEXT_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, }, +loadingInfo?: LoadingInfo, } | { +type: 'SEND_TEXT_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawMultimediaMessageInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, }, +loadingInfo?: LoadingInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REACTION_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawReactionMessageInfo, } | { +type: 'SEND_REACTION_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, +targetMessageID: string, +reaction: string, +action: string, }, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REACTION_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_SUCCESS', +payload: UserSearchResult, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_SUCCESS', +payload: ExactUserSearchResult, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_DRAFT', +payload: { +key: string, +text: string, }, } | { +type: 'MOVE_DRAFT', +payload: { +oldKey: string, +newKey: string, }, } | { +type: 'SET_CLIENT_DB_STORE', +payload: ClientStore, } | { +type: 'UPDATE_ACTIVITY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_ACTIVITY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_ACTIVITY_SUCCESS', +payload: ActivityUpdateSuccessPayload, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_SUCCESS', +payload: SetDeviceTokenActionPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_SUCCESS', +payload?: ClearDeliveredReportsPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_SUCCESS', +payload?: ClearDeliveredReportsPayload, +loadingInfo: LoadingInfo, } | { +type: 'QUEUE_REPORTS', +payload: QueueReportsPayload, } | { +type: 'SET_URL_PREFIX', +payload: string, } | { +type: 'SAVE_MESSAGES', +payload: SaveMessagesPayload, } | { +type: 'UPDATE_CALENDAR_THREAD_FILTER', +payload: CalendarThreadFilter, } | { +type: 'CLEAR_CALENDAR_THREAD_FILTER', +payload?: void, } | { +type: 'SET_CALENDAR_DELETED_FILTER', +payload: SetCalendarDeletedFilterPayload, } | { +type: 'UPDATE_SUBSCRIPTION_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_SUBSCRIPTION_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_SUBSCRIPTION_SUCCESS', +payload: SubscriptionUpdateResult, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_QUERY_STARTED', +loadingInfo: LoadingInfo, +payload?: CalendarQueryUpdateStartingPayload, } | { +type: 'UPDATE_CALENDAR_QUERY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_QUERY_SUCCESS', +payload: CalendarQueryUpdateResult, +loadingInfo: LoadingInfo, } | { +type: 'FULL_STATE_SYNC', +payload: StateSyncFullActionPayload, } | { +type: 'INCREMENTAL_STATE_SYNC', +payload: StateSyncIncrementalActionPayload, } | ProcessServerRequestAction | { +type: 'UPDATE_CONNECTION_STATUS', +payload: UpdateConnectionStatusPayload, } | { +type: 'QUEUE_ACTIVITY_UPDATES', +payload: QueueActivityUpdatesPayload, } | { +type: 'UNSUPERVISED_BACKGROUND', +payload: { +keyserverID: string }, } | { +type: 'UPDATE_LIFECYCLE_STATE', +payload: LifecycleState, } | { +type: 'ENABLE_APP', +payload: SupportedApps, } | { +type: 'DISABLE_APP', +payload: SupportedApps, } | { +type: 'UPDATE_REPORTS_ENABLED', +payload: Partial, } | { +type: 'PROCESS_UPDATES', +payload: ClientUpdatesResultWithUserInfos, } | { +type: 'PROCESS_MESSAGES', +payload: NewMessagesPayload, } | { +type: 'MESSAGE_STORE_PRUNE', +payload: MessageStorePrunePayload, } | { +type: 'SET_LATE_RESPONSE', +payload: SetLateResponsePayload, } | { +type: 'UPDATE_KEYSERVER_REACHABILITY', +payload: UpdateKeyserverReachabilityPayload, } | { +type: 'REQUEST_ACCESS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'REQUEST_ACCESS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'REQUEST_ACCESS_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_MULTIMEDIA_MESSAGE_MEDIA', +payload: UpdateMultimediaMessageMediaPayload, } | { +type: 'CREATE_LOCAL_MESSAGE', +payload: LocallyComposedMessageInfo, } | { +type: 'UPDATE_RELATIONSHIPS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_RELATIONSHIPS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_RELATIONSHIPS_SUCCESS', +payload: RelationshipErrors, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_STARTED', +payload: { +threadID: string, +unread: boolean, }, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_SUCCESS', +payload: SetThreadUnreadStatusPayload, } | { +type: 'SET_USER_SETTINGS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SET_USER_SETTINGS_SUCCESS', +payload: DefaultNotificationPayload, } | { +type: 'SET_USER_SETTINGS_FAILED', +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_SUCCESS', +payload: MessageReportCreationResult, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FORCE_POLICY_ACKNOWLEDGMENT', +payload: ForcePolicyAcknowledgmentPayload, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_SUCCESS', +payload: PolicyAcknowledgmentPayload, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_STARTED', +payload: LogInStartingPayload, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_SUCCESS', +payload: LogInResult, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'RECORD_NOTIF_PERMISSION_ALERT', +payload: { +time: number }, } | { +type: 'UPDATE_USER_AVATAR_STARTED', +payload: UpdateUserAvatarRequest, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_USER_AVATAR_SUCCESS', +payload: UpdateUserAvatarResponse, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_USER_AVATAR_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_EDIT_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'SEND_EDIT_MESSAGE_SUCCESS', +payload: EditMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_EDIT_MESSAGE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'TOGGLE_MESSAGE_PIN_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'TOGGLE_MESSAGE_PIN_SUCCESS', +payload: ToggleMessagePinResult, +loadingInfo: LoadingInfo, } | { +type: 'TOGGLE_MESSAGE_PIN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PINNED_MESSAGES_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'FETCH_PINNED_MESSAGES_SUCCESS', +payload: FetchPinnedMessagesResult, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PINNED_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'VERIFY_INVITE_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'VERIFY_INVITE_LINK_SUCCESS', +payload: InviteLinkVerificationResponse, +loadingInfo: LoadingInfo, } | { +type: 'VERIFY_INVITE_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_SUCCESS', +payload: FetchInviteLinksResponse, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_COMMUNITY_FILTER', +payload: string, } | { +type: 'CLEAR_CALENDAR_COMMUNITY_FILTER', +payload: void, } | { +type: 'UPDATE_CHAT_COMMUNITY_FILTER', +payload: string, } | { +type: 'CLEAR_CHAT_COMMUNITY_FILTER', +payload: void, } | { +type: 'SEARCH_MESSAGES_STARTED', +payload: void, +loadingInfo?: LoadingInfo, } | { +type: 'SEARCH_MESSAGES_SUCCESS', +payload: SearchMessagesResponse, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_SUCCESS', +payload: InviteLink, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DISABLE_INVITE_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'DISABLE_INVITE_LINK_SUCCESS', +payload: DisableInviteLinkPayload, +loadingInfo: LoadingInfo, } | { +type: 'DISABLE_INVITE_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_SUCCESS', +payload: GetOlmSessionInitializationDataResponse, +loadingInfo: LoadingInfo, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_DATA_LOADED', +payload: { +dataLoaded: boolean, }, } | { +type: 'GET_VERSION_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'GET_VERSION_SUCCESS', +payload: GetVersionActionPayload, +loadingInfo: LoadingInfo, } | { +type: 'GET_VERSION_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_LAST_COMMUNICATED_PLATFORM_DETAILS', +payload: LastCommunicatedPlatformDetails, } | { +type: 'RESET_USER_STATE', +payload?: void } | { +type: 'MODIFY_COMMUNITY_ROLE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'MODIFY_COMMUNITY_ROLE_SUCCESS', +payload: RoleModificationPayload, +loadingInfo: LoadingInfo, } | { +type: 'MODIFY_COMMUNITY_ROLE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_COMMUNITY_ROLE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'DELETE_COMMUNITY_ROLE_SUCCESS', +payload: RoleDeletionPayload, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_COMMUNITY_ROLE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_ACCESS_TOKEN', +payload: string, } | { +type: 'UPDATE_THREAD_LAST_NAVIGATED', +payload: { +threadID: string, +time: number }, } | { +type: 'UPDATE_INTEGRITY_STORE', +payload: { +threadIDsToHash?: $ReadOnlyArray, +threadHashingStatus?: 'starting' | 'running' | 'completed', }, } | { +type: 'UPDATE_THEME_INFO', +payload: Partial, } | { +type: 'ADD_KEYSERVER', +payload: AddKeyserverPayload, } | { +type: 'REMOVE_KEYSERVER', +payload: RemoveKeyserverPayload, } | { +type: 'SET_CUSTOM_SERVER', +payload: string, } | { +type: 'SET_CONNECTION_ISSUE', +payload: { +connectionIssue: ?ConnectionIssue, +keyserverID: string }, } | { +type: 'SET_SESSION_RECOVERY_IN_PROGRESS', +payload: { +sessionRecoveryInProgress: boolean, +keyserverID: string }, }; export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string); export type SuperAction = { type: string, payload?: ActionPayload, loadingInfo?: LoadingInfo, error?: boolean, }; type ThunkedAction = (dispatch: Dispatch) => void; export type PromisedAction = (dispatch: Dispatch) => Promise; export type Dispatch = ((promisedAction: PromisedAction) => Promise) & ((thunkedAction: ThunkedAction) => void) & ((action: SuperAction) => boolean); // This is lifted from redux-persist/lib/constants.js // I don't want to add redux-persist to the web/server bundles... // import { REHYDRATE } from 'redux-persist'; export const rehydrateActionType = 'persist/REHYDRATE'; diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js index 8f9a8007f..2486d7564 100644 --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -1,282 +1,283 @@ // @flow import * as React from 'react'; import { getOneTimeKeyValues } from 'lib/shared/crypto-utils.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { type IdentityKeysBlob, identityKeysBlobValidator, } from 'lib/types/crypto-types.js'; import { type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, type IdentityServiceClient, type UserDevicesOlmOutboundKeys, type UserLoginResponse, ONE_TIME_KEYS_NUMBER, identityAuthResultValidator, } 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 { useSelector } from '../redux/redux-utils.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; type Props = { +children: React.Node, }; function IdentityServiceContextProvider(props: Props): React.Node { const { children } = props; const userIDPromiseRef = React.useRef>(); if (!userIDPromiseRef.current) { userIDPromiseRef.current = (async () => { const { userID } = await commCoreModule.getCommServicesAuthMetadata(); return userID; })(); } React.useEffect(() => { const metadataEmitter = getCommServicesAuthMetadataEmitter(); const subscription = metadataEmitter.addListener( 'commServicesAuthMetadata', (authMetadata: UserLoginResponse) => { userIDPromiseRef.current = Promise.resolve(authMetadata.userId); }, ); return () => subscription.remove(); }, []); const accessToken = useSelector(state => state.commServicesAccessToken); const getAuthMetadata = React.useCallback< () => Promise<{ +deviceID: string, +userID: string, +accessToken: string, }>, >(async () => { const deviceID = await getContentSigningKey(); const userID = await userIDPromiseRef.current; if (!deviceID || !userID || !accessToken) { throw new Error('Identity service client is not initialized'); } return { deviceID, userID, accessToken }; }, [accessToken]); const client = React.useMemo( () => ({ deleteUser: async () => { const { deviceID, userID, accessToken: token, } = await getAuthMetadata(); return commRustModule.deleteUser(userID, deviceID, token); }, getKeyserverKeys: async ( keyserverID: string, ): Promise => { const { deviceID, userID, accessToken: token, } = await getAuthMetadata(); const result = await commRustModule.getKeyserverKeys( userID, deviceID, token, 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, deviceOlmOutboundKeysValidator, ); }, getOutboundKeysForUser: async ( targetUserID: string, ): Promise => { const { deviceID: authDeviceID, userID, accessToken: token, } = await getAuthMetadata(); const result = await commRustModule.getOutboundKeysForUser( userID, authDeviceID, token, targetUserID, ); const resultArray = JSON.parse(result); return resultArray .map(outboundKeysInfo => { try { const payload = outboundKeysInfo?.payload; const identityKeysBlob: IdentityKeysBlob = assertWithValidator( payload ? JSON.parse(payload) : null, identityKeysBlobValidator, ); const deviceID = identityKeysBlob.primaryIdentityPublicKeys.ed25519; if ( !outboundKeysInfo.oneTimeContentPrekey || !outboundKeysInfo.oneTimeNotifPrekey ) { console.log(`Missing one time key for device ${deviceID}`); return { deviceID, keys: null, }; } const deviceKeys = { identityKeysBlob, contentInitializationInfo: { prekey: outboundKeysInfo?.contentPrekey, prekeySignature: outboundKeysInfo?.contentPrekeySignature, oneTimeKey: outboundKeysInfo?.oneTimeContentPrekey, }, notifInitializationInfo: { prekey: outboundKeysInfo?.notifPrekey, prekeySignature: outboundKeysInfo?.notifPrekeySignature, oneTimeKey: outboundKeysInfo?.oneTimeNotifPrekey, }, payloadSignature: outboundKeysInfo?.payloadSignature, socialProof: outboundKeysInfo?.socialProof, }; try { const validatedKeys = assertWithValidator( deviceKeys, deviceOlmOutboundKeysValidator, ); return { deviceID, keys: validatedKeys, }; } catch (e) { console.log(e); return { deviceID, keys: null, }; } } catch (e) { console.log(e); return null; } }) .filter(Boolean); }, registerUser: async (username: string, password: string) => { await commCoreModule.initializeCryptoAccount(); const [ { blobPayload, signature }, { contentOneTimeKeys, notificationsOneTimeKeys }, prekeys, ] = await Promise.all([ commCoreModule.getUserPublicKey(), commCoreModule.getOneTimeKeys(ONE_TIME_KEYS_NUMBER), commCoreModule.validateAndGetPrekeys(), ]); const registrationResult = await commRustModule.registerUser( username, password, blobPayload, signature, prekeys.contentPrekey, prekeys.contentPrekeySignature, prekeys.notifPrekey, prekeys.notifPrekeySignature, getOneTimeKeyValues(contentOneTimeKeys), getOneTimeKeyValues(notificationsOneTimeKeys), ); const { userID, accessToken: token } = JSON.parse(registrationResult); const identityAuthResult = { accessToken: token, userID, username }; return assertWithValidator( identityAuthResult, identityAuthResultValidator, ); }, logInPasswordUser: async (username: string, password: string) => { await commCoreModule.initializeCryptoAccount(); const [ { blobPayload, signature }, { contentOneTimeKeys, notificationsOneTimeKeys }, prekeys, ] = await Promise.all([ commCoreModule.getUserPublicKey(), commCoreModule.getOneTimeKeys(ONE_TIME_KEYS_NUMBER), commCoreModule.validateAndGetPrekeys(), ]); const loginResult = await commRustModule.logInPasswordUser( username, password, blobPayload, signature, prekeys.contentPrekey, prekeys.contentPrekeySignature, prekeys.notifPrekey, prekeys.notifPrekeySignature, getOneTimeKeyValues(contentOneTimeKeys), getOneTimeKeyValues(notificationsOneTimeKeys), ); const { userID, accessToken: token } = JSON.parse(loginResult); const identityAuthResult = { accessToken: token, userID, username }; return assertWithValidator( identityAuthResult, identityAuthResultValidator, ); }, + generateNonce: commRustModule.generateNonce, }), [getAuthMetadata], ); const value = React.useMemo( () => ({ identityClient: client, getAuthMetadata, }), [client, getAuthMetadata], ); return ( {children} ); } export default IdentityServiceContextProvider; diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js index 6eabce172..78e3a3237 100644 --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -1,318 +1,323 @@ // @flow import { Login } from '@commapp/opaque-ke-wasm'; import identityServiceConfig from 'lib/facts/identity-service.js'; import { type IdentityServiceAuthLayer, type IdentityServiceClient, type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, type UserDevicesOlmOutboundKeys, type IdentityAuthResult, type IdentityDeviceKeyUpload, identityDeviceTypes, identityAuthResultValidator, } from 'lib/types/identity-service-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; import { assertWithValidator } from 'lib/utils/validation-utils.js'; import { VersionInterceptor, AuthInterceptor } from './interceptor.js'; import { initOpaque } from '../crypto/opaque-utils.js'; import * as IdentityAuthClient from '../protobufs/identity-auth-client.cjs'; import * as IdentityAuthStructs from '../protobufs/identity-auth-structs.cjs'; import { DeviceKeyUpload, Empty, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, } from '../protobufs/identity-unauth-structs.cjs'; import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs'; class IdentityServiceClientWrapper implements IdentityServiceClient { authClient: ?IdentityAuthClient.IdentityClientServicePromiseClient; unauthClient: IdentityUnauthClient.IdentityClientServicePromiseClient; getDeviceKeyUpload: () => Promise; constructor( authLayer: ?IdentityServiceAuthLayer, getDeviceKeyUpload: () => Promise, ) { if (authLayer) { this.authClient = IdentityServiceClientWrapper.createAuthClient(authLayer); } this.unauthClient = IdentityServiceClientWrapper.createUnauthClient(); this.getDeviceKeyUpload = getDeviceKeyUpload; } 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); }; logInPasswordUser: ( username: string, password: string, ) => Promise = async ( username: string, password: string, ) => { const client = this.unauthClient; if (!client) { throw new Error('Identity service client is not initialized'); } const [identityDeviceKeyUpload] = await Promise.all([ this.getDeviceKeyUpload(), initOpaque(), ]); const { keyPayload, keyPayloadSignature, contentPrekey, contentPrekeySignature, notifPrekey, notifPrekeySignature, contentOneTimeKeys, notifOneTimeKeys, } = identityDeviceKeyUpload; const contentOneTimeKeysArray = [...contentOneTimeKeys]; const notifOneTimeKeysArray = [...notifOneTimeKeys]; const opaqueLogin = new Login(); const startRequestBytes = opaqueLogin.start(password); const identityKeyInfo = new IdentityKeyInfo(); identityKeyInfo.setPayload(keyPayload); identityKeyInfo.setPayloadSignature(keyPayloadSignature); const contentPrekeyUpload = new Prekey(); contentPrekeyUpload.setPrekey(contentPrekey); contentPrekeyUpload.setPrekeySignature(contentPrekeySignature); const notifPrekeyUpload = new Prekey(); notifPrekeyUpload.setPrekey(notifPrekey); notifPrekeyUpload.setPrekeySignature(notifPrekeySignature); const deviceKeyUpload = new DeviceKeyUpload(); deviceKeyUpload.setDeviceKeyInfo(identityKeyInfo); deviceKeyUpload.setContentUpload(contentPrekeyUpload); deviceKeyUpload.setNotifUpload(notifPrekeyUpload); deviceKeyUpload.setOneTimeContentPrekeysList(contentOneTimeKeysArray); deviceKeyUpload.setOneTimeNotifPrekeysList(notifOneTimeKeysArray); deviceKeyUpload.setDeviceType(identityDeviceTypes.WEB); const loginStartRequest = new OpaqueLoginStartRequest(); loginStartRequest.setUsername(username); loginStartRequest.setOpaqueLoginRequest(startRequestBytes); loginStartRequest.setDeviceKeyUpload(deviceKeyUpload); let loginStartResponse; try { loginStartResponse = await client.logInPasswordUserStart(loginStartRequest); } catch (e) { console.log('Error calling logInPasswordUserStart:', e); throw new Error(getMessageForException(e) ?? 'unknown'); } const finishRequestBytes = opaqueLogin.finish( loginStartResponse.getOpaqueLoginResponse_asU8(), ); const loginFinishRequest = new OpaqueLoginFinishRequest(); loginFinishRequest.setSessionId(loginStartResponse.getSessionId()); loginFinishRequest.setOpaqueLoginUpload(finishRequestBytes); let loginFinishResponse; try { loginFinishResponse = await client.logInPasswordUserFinish(loginFinishRequest); } catch (e) { console.log('Error calling logInPasswordUserFinish:', e); throw new Error(getMessageForException(e) ?? 'unknown'); } const userID = loginFinishResponse.getUserId(); const accessToken = loginFinishResponse.getAccessToken(); const identityAuthResult = { accessToken, userID, username }; return assertWithValidator(identityAuthResult, identityAuthResultValidator); }; + + generateNonce: () => Promise = async () => { + const result = await this.unauthClient.generateNonce(new Empty()); + return result.getNonce(); + }; } export { IdentityServiceClientWrapper };