From 0cbcc001800eebea88571c902d6a70a5a6fd1a0c Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Wed, 18 Aug 2021 14:18:09 +1000 Subject: [PATCH] Adding registration stages file. Somehow became untracked. --- .../registration/RegistrationStages.tsx | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 ts/components/session/registration/RegistrationStages.tsx diff --git a/ts/components/session/registration/RegistrationStages.tsx b/ts/components/session/registration/RegistrationStages.tsx new file mode 100644 index 000000000..1490ff002 --- /dev/null +++ b/ts/components/session/registration/RegistrationStages.tsx @@ -0,0 +1,215 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { PromiseUtils, StringUtils, ToastUtils, UserUtils } from '../../../session/utils'; +import { getConversationController } from '../../../session/conversations'; +import { createOrUpdateItem, removeAll } from '../../../data/data'; +import { SignUpTab } from './SignUpTab'; +import { SignInTab } from './SignInTab'; +import { trigger } from '../../../shims/events'; +import { + generateMnemonic, + registerSingleDevice, + sessionGenerateKeyPair, + signInByLinkingDevice, +} from '../../../util/accountManager'; +import { fromHex } from '../../../session/utils/String'; +import { TaskTimedOutError } from '../../../session/utils/Promise'; +import { mn_decode } from '../../../session/crypto/mnemonic'; +import { getSwarmPollingInstance } from '../../../session/snode_api/swarmPolling'; + +export const MAX_USERNAME_LENGTH = 20; +// tslint:disable: use-simple-attributes + +export async function resetRegistration() { + await removeAll(); + await window.storage.reset(); + await window.storage.fetch(); + getConversationController().reset(); + await getConversationController().load(); +} + +/** + * Returns undefined if an error happened, or the trim userName. + * + * Be sure to use the trimmed userName for creating the account. + */ +const displayNameIsValid = (displayName: string): undefined | string => { + const trimName = displayName.trim(); + + if (!trimName) { + window?.log?.warn('invalid trimmed name for registration'); + ToastUtils.pushToastError('invalidDisplayName', window.i18n('displayNameEmpty')); + return undefined; + } + return trimName; +}; + +export async function signUp(signUpDetails: { + displayName: string; + generatedRecoveryPhrase: string; +}) { + const { displayName, generatedRecoveryPhrase } = signUpDetails; + window?.log?.info('SIGNING UP'); + + const trimName = displayNameIsValid(displayName); + // shows toast to user about the error + if (!trimName) { + return; + } + + try { + await resetRegistration(); + await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName); + await createOrUpdateItem({ + id: 'hasSyncedInitialConfigurationItem', + value: true, + }); + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + + ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`); + window?.log?.warn('exception during registration:', e); + } +} + +/** + * Sign in/restore from seed. + * Ask for a display name, as we will drop incoming ConfigurationMessages if any are saved on the swarm. + * We will handle a ConfigurationMessage + */ +export async function signInWithRecovery(signInDetails: { + displayName: string; + userRecoveryPhrase: string; +}) { + const { displayName, userRecoveryPhrase } = signInDetails; + window?.log?.info('RESTORING FROM SEED'); + const trimName = displayNameIsValid(displayName); + // shows toast to user about the error + if (!trimName) { + return; + } + + try { + await resetRegistration(); + + await registerSingleDevice(userRecoveryPhrase, 'english', trimName); + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`); + window?.log?.warn('exception during registration:', e); + } +} + +/** + * This is will try to sign in with the user recovery phrase. + * If no ConfigurationMessage is received in 60seconds, the loading will be canceled. + */ +export async function signInWithLinking(signInDetails: { userRecoveryPhrase: string }) { + const { userRecoveryPhrase } = signInDetails; + window?.log?.info('LINKING DEVICE'); + + try { + await resetRegistration(); + await signInByLinkingDevice(userRecoveryPhrase, 'english'); + let displayNameFromNetwork = ''; + await getSwarmPollingInstance().start(); + + await PromiseUtils.waitForTask(done => { + window.Whisper.events.on('configurationMessageReceived', (displayName: string) => { + window.Whisper.events.off('configurationMessageReceived'); + UserUtils.setSignInByLinking(false); + done(displayName); + + displayNameFromNetwork = displayName; + }); + }, 60000); + if (displayNameFromNetwork.length) { + // display name, avatars, groups and contacts should already be handled when this event was triggered. + window?.log?.info('We got a displayName from network: '); + } else { + window?.log?.info('Got a config message from network but without a displayName...'); + throw new Error('Got a config message from network but without a displayName...'); + } + // Do not set the lastProfileUpdateTimestamp. + // We expect to get a display name from a configuration message while we are loading messages of this user + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + if (e instanceof TaskTimedOutError) { + ToastUtils.pushToastError( + 'registrationError', + 'Could not find your display name. Please Sign In by Restoring Your Account instead.' + ); + } else { + ToastUtils.pushToastError( + 'registrationError', + `Error: ${e.message || 'Something went wrong'}` + ); + } + window?.log?.warn('exception during registration:', e); + } +} + +export enum RegistrationPhase { + Start, + SignIn, + SignUp, +} + +interface RegistrationPhaseContext { + registrationPhase: RegistrationPhase; + setRegistrationPhase: (phase: RegistrationPhase) => void; +} + +export const RegistrationContext = createContext({ + registrationPhase: RegistrationPhase.Start, + setRegistrationPhase: () => undefined, +}); + +export const RegistrationStages = () => { + const [generatedRecoveryPhrase, setGeneratedRecoveryPhrase] = useState(''); + const [hexGeneratedPubKey, setHexGeneratedPubKey] = useState(''); + const [registrationPhase, setRegistrationPhase] = useState(RegistrationPhase.Start); + + useEffect(() => { + void generateMnemonicAndKeyPair(); + void resetRegistration(); + }, []); + + const generateMnemonicAndKeyPair = async () => { + if (generatedRecoveryPhrase === '') { + const mnemonic = await generateMnemonic(); + + let seedHex = mn_decode(mnemonic); + // handle shorter than 32 bytes seeds + const privKeyHexLength = 32 * 2; + if (seedHex.length !== privKeyHexLength) { + seedHex = seedHex.concat('0'.repeat(32)); + seedHex = seedHex.substring(0, privKeyHexLength); + } + const seed = fromHex(seedHex); + const keyPair = await sessionGenerateKeyPair(seed); + const newHexPubKey = StringUtils.decode(keyPair.pubKey, 'hex'); + + setGeneratedRecoveryPhrase(mnemonic); + setHexGeneratedPubKey(newHexPubKey); // our 'frontend' sessionID + } + }; + + return ( +
+ + {(registrationPhase === RegistrationPhase.Start || + registrationPhase === RegistrationPhase.SignUp) && ( + + )} + {(registrationPhase === RegistrationPhase.Start || + registrationPhase === RegistrationPhase.SignIn) && } + +
+ ); +};