From 5039930508c67ab658aaf1b3a087910714693a70 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 12 Dec 2019 15:30:46 +1100 Subject: [PATCH] add basic secondary device link to new registration page needs to handle errors and popup to show secret words, ... --- js/views/session_registration_view.js | 86 --------- ts/components/session/RegistrationTabs.tsx | 199 +++++++++++++++------ 2 files changed, 144 insertions(+), 141 deletions(-) diff --git a/js/views/session_registration_view.js b/js/views/session_registration_view.js index 23ad5b8c5..bc2b71de0 100644 --- a/js/views/session_registration_view.js +++ b/js/views/session_registration_view.js @@ -138,89 +138,3 @@ }, }); })(); - -/* - - async cancelSecondaryDevice() { - Whisper.events.off( - 'secondaryDeviceRegistration', - this.onSecondaryDeviceRegistered - ); - this.$('#register-secondary-device') - .removeAttr('disabled') - .text('Link'); - this.$('#cancel-secondary-device').hide(); - this.$('.standalone-secondary-device #pubkey').text(''); - await this.resetRegistration(); - }, - async registerSecondaryDevice() { - if (textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { - return; - } - await this.resetRegistration(); - textsecure.storage.put('secondaryDeviceStatus', 'ongoing'); - this.$('#register-secondary-device') - .attr('disabled', 'disabled') - .text('Sending...'); - this.$('#cancel-secondary-device').show(); - const mnemonic = this.$('#mnemonic-display').text(); - const language = this.$('#mnemonic-display-language').val(); - const primaryPubKey = this.$('#primary-pubkey').val(); - this.$('.standalone-secondary-device #error').hide(); - // Ensure only one listener - Whisper.events.off( - 'secondaryDeviceRegistration', - this.onSecondaryDeviceRegistered - ); - Whisper.events.once( - 'secondaryDeviceRegistration', - this.onSecondaryDeviceRegistered - ); - const onError = async error => { - this.$('.standalone-secondary-device #error') - .text(error) - .show(); - await this.resetRegistration(); - this.$('#register-secondary-device') - .removeAttr('disabled') - .text('Link'); - this.$('#cancel-secondary-device').hide(); - }; - const c = new Whisper.Conversation({ - id: primaryPubKey, - type: 'private', - }); - const validationError = c.validateNumber(); - if (validationError) { - onError('Invalid public key'); - return; - } - try { - await this.accountManager.registerSingleDevice( - mnemonic, - language, - null - ); - await this.accountManager.requestPairing(primaryPubKey); - const pubkey = textsecure.storage.user.getNumber(); - const words = window.mnemonic.pubkey_to_secret_words(pubkey); - - this.$('.standalone-secondary-device #pubkey').text( - `Here is your secret:\n${words}` - ); - } catch (e) { - onError(e); - } - }, - - - async onSecondaryDeviceRegistered() { - clearInterval(this.pairingInterval); - // Ensure the left menu is updated - Whisper.events.trigger('userChanged', { isSecondaryDevice: true }); - // will re-run the background initialisation - Whisper.events.trigger('registration_done'); - this.$el.trigger('openInbox'); - }, - -*/ diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx index 809d15059..c488830fd 100644 --- a/ts/components/session/RegistrationTabs.tsx +++ b/ts/components/session/RegistrationTabs.tsx @@ -19,7 +19,7 @@ enum SignInMode { enum SignUpMode { Default, - SessionIDGenerated, + SessionIDShown, } enum TabType { @@ -37,7 +37,8 @@ interface State { passwordErrorString: string; passwordFieldsMatch: boolean; mnemonicSeed: string; - hexEncodedPubKey: string; + hexGeneratedPubKey: string; + primaryDevicePubKey: string; } const Tab = ({ @@ -85,6 +86,12 @@ export class RegistrationTabs extends React.Component { this ); this.onSignUpGetStartedClick = this.onSignUpGetStartedClick.bind(this); + this.onSecondDeviceSessionIDChanged = this.onSecondDeviceSessionIDChanged.bind( + this + ); + this.onSecondaryDeviceRegistered = this.onSecondaryDeviceRegistered.bind( + this + ); this.state = { selectedTab: TabType.Create, @@ -96,7 +103,8 @@ export class RegistrationTabs extends React.Component { passwordErrorString: '', passwordFieldsMatch: false, mnemonicSeed: '', - hexEncodedPubKey: '', + hexGeneratedPubKey: '', + primaryDevicePubKey: '', }; this.accountManager = window.getAccountManager(); @@ -105,9 +113,38 @@ export class RegistrationTabs extends React.Component { } public render() { + this.generateMnemonicAndKeyPair().ignore(); + return this.renderTabs(); } + private async generateMnemonicAndKeyPair() { + if (this.state.mnemonicSeed === '') { + const language = 'english'; + const mnemonic = await this.accountManager.generateMnemonic(language); + + let seedHex = window.mnemonic.mn_decode(mnemonic, language); + // handle shorter than 32 bytes seeds + const privKeyHexLength = 32 * 2; + if (seedHex.length !== privKeyHexLength) { + seedHex = seedHex.concat(seedHex); + seedHex = seedHex.substring(0, privKeyHexLength); + } + const privKeyHex = window.mnemonic.sc_reduce32(seedHex); + const privKey = window.dcodeIO.ByteBuffer.wrap( + privKeyHex, + 'hex' + ).toArrayBuffer(); + const keyPair = await window.libsignal.Curve.async.createKeyPair(privKey); + const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex'); + + this.setState({ + mnemonicSeed: mnemonic, + hexGeneratedPubKey, // our 'frontend' sessionID + }); + } + } + private renderTabs() { const { selectedTab } = this.state; const { i18n } = this.props; @@ -139,7 +176,14 @@ export class RegistrationTabs extends React.Component { } private readonly handleTabSelect = (tabType: TabType): void => { - this.setState({ selectedTab: tabType }); + if (tabType !== TabType.SignIn) { + this.cancelSecondaryDevice().ignore(); + } + this.setState({ + selectedTab: tabType, + signInMode: SignInMode.Default, + signUpMode: SignUpMode.Default, + }); }; private onSeedChanged(val: string) { @@ -186,7 +230,7 @@ export class RegistrationTabs extends React.Component {
{i18n('yourUniqueSessionID')}
- {this.renderEnterSessionID(false, this.state.hexEncodedPubKey)} + {this.renderEnterSessionID(false, this.state.hexGeneratedPubKey)} {this.renderSignUpButton()} {this.getRenderTermsConditionAgreement()} @@ -244,30 +288,7 @@ export class RegistrationTabs extends React.Component { private async onSignUpGenerateSessionIDClick() { this.setState({ - signUpMode: SignUpMode.SessionIDGenerated, - }); - - const language = 'english'; - const mnemonic = await this.accountManager.generateMnemonic(language); - - let seedHex = window.mnemonic.mn_decode(mnemonic, language); - // handle shorter than 32 bytes seeds - const privKeyHexLength = 32 * 2; - if (seedHex.length !== privKeyHexLength) { - seedHex = seedHex.concat(seedHex); - seedHex = seedHex.substring(0, privKeyHexLength); - } - const privKeyHex = window.mnemonic.sc_reduce32(seedHex); - const privKey = window.dcodeIO.ByteBuffer.wrap( - privKeyHex, - 'hex' - ).toArrayBuffer(); - const keyPair = await window.libsignal.Curve.async.createKeyPair(privKey); - const hexEncodedPubKey = Buffer.from(keyPair.pubKey).toString('hex'); - - this.setState({ - mnemonicSeed: mnemonic, - hexEncodedPubKey, // our 'frontend' sessionID + signUpMode: SignUpMode.SessionIDShown, }); } @@ -357,12 +378,25 @@ export class RegistrationTabs extends React.Component { className="session-signin-enter-session-id" contentEditable={contentEditable} placeholder={enterSessionIDHere} + onInput={(e: any) => { + if (contentEditable) { + this.onSecondDeviceSessionIDChanged(e); + } + }} > {text} ); } + private onSecondDeviceSessionIDChanged(e: any) { + e.preventDefault(); + const hexEncodedPubKey = e.target.innerHTML; + this.setState({ + primaryDevicePubKey: hexEncodedPubKey, + }); + } + private renderSignInButtons() { const { signInMode } = this.state; const { i18n } = this.props; @@ -413,7 +447,11 @@ export class RegistrationTabs extends React.Component { return ( { - this.register('english').ignore(); + if (this.state.signInMode === SignInMode.UsingSeed) { + this.register('english').ignore(); + } else { + this.registerSecondaryDevice().ignore(); + } }} buttonType={SessionButtonType.FullGreen} text={this.props.i18n('continueYourSession')} @@ -425,9 +463,10 @@ export class RegistrationTabs extends React.Component { return ( { + this.cancelSecondaryDevice().ignore(); this.setState({ signInMode: SignInMode.UsingSeed, - hexEncodedPubKey: '', + primaryDevicePubKey: '', mnemonicSeed: '', displayName: '', signUpMode: SignUpMode.Default, @@ -445,7 +484,6 @@ export class RegistrationTabs extends React.Component { onClick={() => { this.setState({ signInMode: SignInMode.LinkingDevice, - hexEncodedPubKey: '', mnemonicSeed: '', displayName: '', signUpMode: SignUpMode.Default, @@ -486,15 +524,6 @@ export class RegistrationTabs extends React.Component { const passwordValidation = this.validatePassword(); if (passwordValidation) { this.setState({ passwordErrorString: passwordValidation }); - - /*this.$passwordInput.addClass('error-input'); - this.$passwordConfirmationInput.addClass('error-input'); - - this.$passwordInput.removeClass('match-input'); - this.$passwordConfirmationInput.removeClass('match-input'); - - this.$passwordInputError.text(passwordValidation); - this.$passwordInputError.show();*/ } else { // Show green box around inputs that match const input = this.trim(this.state.password); @@ -506,24 +535,11 @@ export class RegistrationTabs extends React.Component { passwordErrorString: '', passwordFieldsMatch, }); - - /* - this.$passwordInput.addClass('match-input'); //if password matches each other - this.$passwordInput.removeClass('error-input'); - this.$passwordConfirmationInput.removeClass('error-input'); - this.$passwordInputError.text(''); - this.$passwordInputError.hide();*/ } } private sanitiseNameInput(val: string) { return val.trim().replace(window.displayNameRegex, ''); - - /* if (_.isEmpty(newVal)) { - this.$('#save-button').attr('disabled', 'disabled'); - - } - this.$('#save-button').removeAttr('disabled'); */ } private async resetRegistration() { @@ -544,7 +560,6 @@ export class RegistrationTabs extends React.Component { // Make sure the password is valid if (this.validatePassword()) { //this.showToast(i18n('invalidPassword')); - return; } if (!mnemonicSeed) { @@ -574,4 +589,78 @@ export class RegistrationTabs extends React.Component { //this.log(e); } } + + private async cancelSecondaryDevice() { + window.Whisper.events.off( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + + await this.resetRegistration(); + } + + private async registerSecondaryDevice() { + // tslint:disable-next-line: no-backbone-get-set-outside-model + if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { + return; + } + await this.resetRegistration(); + window.textsecure.storage.put('secondaryDeviceStatus', 'ongoing'); + + const primaryPubKey = this.state.primaryDevicePubKey; + + // Ensure only one listener + window.Whisper.events.off( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + window.Whisper.events.once( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + + const onError = async (error: any) => { + window.console.error(error); + + await this.resetRegistration(); + }; + + const c = new window.Whisper.Conversation({ + id: primaryPubKey, + type: 'private', + }); + + const validationError = c.validateNumber(); + if (validationError) { + onError('Invalid public key').ignore(); + + return; + } + try { + const fakeMnemonic = this.state.mnemonicSeed; + + await this.accountManager.registerSingleDevice( + fakeMnemonic, + 'english', + null + ); + + await this.accountManager.requestPairing(primaryPubKey); + const pubkey = window.textsecure.storage.user.getNumber(); + const words = window.mnemonic.pubkey_to_secret_words(pubkey); + window.console.log('pubkey_to_secret_words'); + window.console.log(`Here is your secret:\n${words}`); + } catch (e) { + window.console.log(e); + //onError(e); + } + } + + private async onSecondaryDeviceRegistered() { + // Ensure the left menu is updated + trigger('userChanged', { isSecondaryDevice: true }); + // will re-run the background initialisation + trigger('registration_done'); + trigger('openInbox'); + } }