diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -53,3 +53,5 @@ +accessToken: string, +username: string, }; + +export const ONE_TIME_KEYS_NUMBER = 10; diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -44,6 +44,7 @@ CalendarThreadFilter, SetCalendarDeletedFilterPayload, } from './filter-types.js'; +import type { IdentityRegisterResult } from './identity-service-types.js'; import type { IntegrityStore } from './integrity-types.js'; import type { KeyserverStore, @@ -374,6 +375,21 @@ +payload: RegisterResult, +loadingInfo: LoadingInfo, } + | { + +type: 'IDENTITY_REGISTER_STARTED', + +payload?: void, + +loadingInfo: LoadingInfo, + } + | { + +type: 'IDENTITY_REGISTER_FAILED', + +payload: Error, + +loadingInfo: LoadingInfo, + } + | { + +type: 'IDENTITY_REGISTER_SUCCESS', + +payload: IdentityRegisterResult, + +loadingInfo: LoadingInfo, + } | { +type: 'CHANGE_KEYSERVER_USER_PASSWORD_STARTED', +payload?: void, diff --git a/native/account/register-panel.react.js b/native/account/register-panel.react.js --- a/native/account/register-panel.react.js +++ b/native/account/register-panel.react.js @@ -46,7 +46,11 @@ import { useSelector } from '../redux/redux-utils.js'; import { nativeLogInExtraInfoSelector } from '../selectors/account-selectors.js'; import type { KeyPressEvent } from '../types/react-native.js'; -import { AppOutOfDateAlertDetails } from '../utils/alert-messages.js'; +import { + AppOutOfDateAlertDetails, + UsernameReservedAlertDetails, + UsernameTakenAlertDetails, +} from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; import { nativeNotificationsSessionCreator } from '../utils/crypto-utils.js'; import { type StateContainer } from '../utils/state-container.js'; @@ -365,16 +369,15 @@ } catch (e) { if (e.message === 'username_reserved') { Alert.alert( - 'Username reserved', - 'This username is currently reserved. Please contact support@' + - 'comm.app if you would like to claim this account.', + UsernameReservedAlertDetails.title, + UsernameReservedAlertDetails.message, [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], { cancelable: false }, ); } else if (e.message === 'username_taken') { Alert.alert( - 'Username taken', - 'An account with that username already exists', + UsernameTakenAlertDetails.title, + UsernameTakenAlertDetails.message, [{ text: 'OK', onPress: this.onUsernameAlertAcknowledged }], { cancelable: false }, ); diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js --- a/native/account/registration/registration-server-call.js +++ b/native/account/registration/registration-server-call.js @@ -6,11 +6,14 @@ import { keyserverRegisterActionTypes, keyserverRegister, + useIdentityRegister, + identityRegisterActionTypes, } from 'lib/actions/user-actions.js'; import type { LogInStartingPayload } from 'lib/types/account-types.js'; import { useServerCall } from 'lib/utils/action-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; +import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; import { setURLPrefix } from 'lib/utils/url-utils.js'; import type { @@ -24,7 +27,11 @@ } from '../../avatars/avatar-hooks.js'; import { useSelector } from '../../redux/redux-utils.js'; import { nativeLogInExtraInfoSelector } from '../../selectors/account-selectors.js'; -import { AppOutOfDateAlertDetails } from '../../utils/alert-messages.js'; +import { + AppOutOfDateAlertDetails, + UsernameReservedAlertDetails, + UsernameTakenAlertDetails, +} from '../../utils/alert-messages.js'; import Alert from '../../utils/alert.js'; import { setNativeCredentials } from '../native-credentials.js'; import { useSIWEServerCall } from '../siwe-hooks.js'; @@ -59,17 +66,62 @@ const logInExtraInfo = useSelector(nativeLogInExtraInfoSelector); const dispatchActionPromise = useDispatchActionPromise(); - const callRegister = useServerCall(keyserverRegister); + const callKeyserverRegister = useServerCall(keyserverRegister); + const callIdentityRegister = useIdentityRegister(); + + const identityRegisterUsernameAccount = React.useCallback( + async (accountSelection: UsernameAccountSelection) => { + const identityRegisterPromise = (async () => { + try { + const result = await callIdentityRegister( + accountSelection.username, + accountSelection.password, + ); + await setNativeCredentials({ + username: accountSelection.username, + password: accountSelection.password, + }); + return result; + } catch (e) { + if (e.message === 'username reserved') { + Alert.alert( + UsernameReservedAlertDetails.title, + UsernameReservedAlertDetails.message, + ); + } else if (e.message === 'username already exists') { + Alert.alert( + UsernameTakenAlertDetails.title, + UsernameTakenAlertDetails.message, + ); + } else if (e.message === 'Unsupported version') { + Alert.alert( + AppOutOfDateAlertDetails.title, + AppOutOfDateAlertDetails.message, + ); + } else { + Alert.alert('Unknown error', 'Uhh... try again?'); + } + throw e; + } + })(); + void dispatchActionPromise( + identityRegisterActionTypes, + identityRegisterPromise, + ); + await identityRegisterPromise; + }, + [callIdentityRegister, dispatchActionPromise], + ); - const registerUsernameAccount = React.useCallback( + const keyserverRegisterUsernameAccount = React.useCallback( async ( accountSelection: UsernameAccountSelection, keyserverURL: string, ) => { const extraInfo = await logInExtraInfo(); - const registerPromise = (async () => { + const keyserverRegisterPromise = (async () => { try { - const result = await callRegister( + const result = await callKeyserverRegister( { ...extraInfo, username: accountSelection.username, @@ -87,14 +139,13 @@ } catch (e) { if (e.message === 'username_reserved') { Alert.alert( - 'Username reserved', - 'This username is currently reserved. Please contact support@' + - 'comm.app if you would like to claim this account.', + UsernameReservedAlertDetails.title, + UsernameReservedAlertDetails.message, ); } else if (e.message === 'username_taken') { Alert.alert( - 'Username taken', - 'An account with that username already exists', + UsernameTakenAlertDetails.title, + UsernameTakenAlertDetails.message, ); } else if (e.message === 'client_version_unsupported') { Alert.alert( @@ -109,13 +160,13 @@ })(); void dispatchActionPromise( keyserverRegisterActionTypes, - registerPromise, + keyserverRegisterPromise, undefined, ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload), ); - await registerPromise; + await keyserverRegisterPromise; }, - [logInExtraInfo, callRegister, dispatchActionPromise], + [logInExtraInfo, callKeyserverRegister, dispatchActionPromise], ); const siweServerCall = useSIWEServerCall(); @@ -130,8 +181,16 @@ return; } const { accountSelection, avatarData, keyserverURL } = input; - if (accountSelection.accountType === 'username') { - await registerUsernameAccount(accountSelection, keyserverURL); + if ( + accountSelection.accountType === 'username' && + !usingCommServicesAccessToken + ) { + await keyserverRegisterUsernameAccount( + accountSelection, + keyserverURL, + ); + } else if (accountSelection.accountType === 'username') { + await identityRegisterUsernameAccount(accountSelection); } else { try { await siweServerCall(accountSelection, { @@ -157,7 +216,13 @@ } }, ), - [currentStep, registerUsernameAccount, siweServerCall, dispatch], + [ + currentStep, + keyserverRegisterUsernameAccount, + identityRegisterUsernameAccount, + siweServerCall, + dispatch, + ], ); // STEP 2: SETTING AVATAR diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -2,12 +2,14 @@ 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, OutboundKeyInfoResponse, UserLoginResponse, } from 'lib/types/identity-service-types.js'; +import { ONE_TIME_KEYS_NUMBER } from 'lib/types/identity-service-types.js'; import { getCommServicesAuthMetadataEmitter } from '../event-emitters/csa-auth-metadata-emitter.js'; import { commCoreModule, commRustModule } from '../native-modules.js'; @@ -87,6 +89,34 @@ } return resultObject; }, + 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]); diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -1225,12 +1225,13 @@ Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum Error { - #[display(...)] + #[display(fmt = "{}", "_0.message()")] TonicGRPC(Status), - #[display(...)] + #[display(fmt = "{}", "_0")] SerdeJson(serde_json::Error), - #[display(...)] + #[display(fmt = "Missing response data")] MissingResponseData, + #[display(fmt = "{}", "_0")] GRPClient(grpc_clients::error::Error), } diff --git a/native/utils/alert-messages.js b/native/utils/alert-messages.js --- a/native/utils/alert-messages.js +++ b/native/utils/alert-messages.js @@ -18,3 +18,15 @@ 'Your app version is pretty old, and the server doesn’t know how ' + `to speak to it anymore. Please use the ${platformStore} to update!`, }; + +export const UsernameReservedAlertDetails: AlertDetails = { + title: 'Username reserved', + message: + 'This username is currently reserved. Please contact support@' + + 'comm.app if you would like to claim this account.', +}; + +export const UsernameTakenAlertDetails: AlertDetails = { + title: 'Username taken', + message: 'An account with that username already exists', +};