From e790db93b094df5510206e5cc716902bf2e910b4 Mon Sep 17 00:00:00 2001 From: Hanwen Cheng Date: Wed, 17 Jun 2020 08:13:59 +0200 Subject: [PATCH] feat: support import with mini secret key (#2944) * feat: support import with mini secret key * fix validation check * re-order import * reoder imports * refactor * validation logic * renaming * remove duplicated * fix lint * Update packages/page-accounts/src/Accounts/PasswordInput.tsx Co-authored-by: Jaco Greeff * Update packages/page-accounts/src/Accounts/PasswordInput.tsx Co-authored-by: Jaco Greeff * Update packages/page-accounts/src/Accounts/modals/Create.tsx Co-authored-by: Jaco Greeff * Update packages/page-accounts/src/Accounts/modals/Qr.tsx Co-authored-by: Jaco Greeff Co-authored-by: Jaco Greeff --- .../src/Accounts/PasswordInput.tsx | 76 +++++++++++++++++++ .../src/Accounts/modals/Create.tsx | 52 ++++--------- .../page-accounts/src/Accounts/modals/Qr.tsx | 46 +++++++++-- 3 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 packages/page-accounts/src/Accounts/PasswordInput.tsx diff --git a/packages/page-accounts/src/Accounts/PasswordInput.tsx b/packages/page-accounts/src/Accounts/PasswordInput.tsx new file mode 100644 index 0000000000..605c7d89b6 --- /dev/null +++ b/packages/page-accounts/src/Accounts/PasswordInput.tsx @@ -0,0 +1,76 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Modal, Password } from '@polkadot/react-components'; +import keyring from '@polkadot/ui-keyring'; + +import React, { useCallback, useState } from 'react'; + +import { useTranslation } from '../translate'; + +type Props = { + password: string; + onChange: (password: string, isPasswordValid: boolean) => void; + onEnter: () => void; +} + +export default function PasswordInput ({ onChange, onEnter, password }: Props): React.ReactElement { + const { t } = useTranslation(); + const [isPassValid, setPassValid] = useState(false); + const [{ isPass2Valid, password2 }, setPassword2] = useState({ isPass2Valid: false, password2: '' }); + + const _onPasswordChange = useCallback( + (password: string) => { + const isPassValid = keyring.isPassValid(password); + + setPassValid(isPassValid); + + const isValid = isPassValid && isPass2Valid; + + onChange(password, isValid); + }, + [onChange, isPass2Valid] + ); + + const onPassword2Change = useCallback( + (password2: string) => { + const isPass2Valid = keyring.isPassValid(password2) && (password2 === password); + + setPassword2({ isPass2Valid, password2 }); + + const isValid = isPassValid && isPass2Valid; + + onChange(password, isValid); + }, + [password, onChange, isPassValid] + ); + + return ( + + + ('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')} + isError={!isPassValid} + label={t('password')} + onChange={_onPasswordChange} + onEnter={onEnter} + value={password} + /> + ('Verify the password entered above.')} + isError={!isPass2Valid} + label={t('password (repeat)')} + onChange={onPassword2Change} + onEnter={onEnter} + value={password2} + /> + + +

{t('The password and password confirmation for this account. This is required to authenticate any transactions made and to encrypt the keypair.')}

+
+
+ ); +} diff --git a/packages/page-accounts/src/Accounts/modals/Create.tsx b/packages/page-accounts/src/Accounts/modals/Create.tsx index a105463584..21da387b82 100644 --- a/packages/page-accounts/src/Accounts/modals/Create.tsx +++ b/packages/page-accounts/src/Accounts/modals/Create.tsx @@ -11,7 +11,7 @@ import FileSaver from 'file-saver'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { DEV_PHRASE } from '@polkadot/keyring/defaults'; -import { AddressRow, Button, Dropdown, Expander, Input, InputAddress, Modal, Password } from '@polkadot/react-components'; +import { AddressRow, Button, Dropdown, Expander, Input, InputAddress, Modal } from '@polkadot/react-components'; import { useApi, useToggle } from '@polkadot/react-hooks'; import keyring from '@polkadot/ui-keyring'; import uiSettings from '@polkadot/ui-settings'; @@ -20,6 +20,7 @@ import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from import { getEnvironment } from '@polkadot/react-api/util'; import { useTranslation } from '../../translate'; +import PasswordInput from '../PasswordInput'; import CreateConfirmation from './CreateConfirmation'; interface Props extends ModalProps { @@ -166,9 +167,8 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ const [{ address, deriveError, derivePath, isSeedValid, pairType, seed, seedType }, setAddress] = useState(generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', propsType)); const [isConfirmationOpen, toggleConfirmation] = useToggle(); const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' }); - const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' }); - const [{ isPass2Valid, password2 }, setPassword2] = useState({ isPass2Valid: false, password2: '' }); - const isValid = !!address && !deriveError && isNameValid && isPassValid && isPass2Valid && isSeedValid; + const [{ isPasswordValid, password }, setPassword] = useState({ isPasswordValid: false, password: '' }); + const isValid = !!address && !deriveError && isNameValid && isPasswordValid && isSeedValid; const seedOpt = useMemo(() => ( isDevelopment ? [{ text: t('Development'), value: 'dev' }] @@ -178,16 +178,6 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ { text: t('Raw seed'), value: 'raw' } ), [isDevelopment, t]); - const _onChangePass = useCallback( - (password: string) => setPassword({ isPassValid: keyring.isPassValid(password), password }), - [] - ); - - const _onChangePass2 = useCallback( - (password2: string) => setPassword2({ isPass2Valid: keyring.isPassValid(password2) && (password2 === password), password2 }), - [password] - ); - const _onChangeDerive = useCallback( (newDerivePath: string) => setAddress(updateAddress(seed, newDerivePath, seedType, pairType)), [pairType, seed, seedType] @@ -217,6 +207,11 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ [] ); + const _onPasswordChange = useCallback( + (password: string, isPasswordValid: boolean) => setPassword({ isPasswordValid, password }), + [] + ); + const _onCommit = useCallback( (): void => { if (!isValid) { @@ -306,31 +301,10 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ

{t('The secret seed value for this account. Ensure that you keep this in a safe place, with access to the seed you can re-create the account.')}

- - - ('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')} - isError={!isPassValid} - label={t('password')} - onChange={_onChangePass} - onEnter={_onCommit} - value={password} - /> - ('Verify the password entered above.')} - isError={!isPass2Valid} - label={t('password (repeat)')} - onChange={_onChangePass2} - onEnter={_onCommit} - value={password2} - /> - - -

{t('The password and password confirmation for this account. This is required to authenticate any transactions made and to encrypt the keypair.')}

-
-
+ (null); + const [isAddress, setIsAddress] = useState(false); + const [address, setAddress] = useState(''); + const [{ isPasswordValid, password }, setPassword] = useState({ isPasswordValid: false, password: '' }); + + const isValid = !!address && isNameValid && (isAddress || isPasswordValid); const _onNameChange = useCallback( (name: string) => setName({ isNameValid: !!name.trim(), name }), [] ); + const _onPasswordChange = useCallback( + (password: string, isPasswordValid: boolean) => setPassword({ isPasswordValid, password }), + [] + ); + const _onScan = useCallback( (scanned: Scanned): void => { setScanned(scanned); + const { content, genesisHash } = scanned; + + setIsAddress(scanned.isAddress); + + if (scanned.isAddress) { + setAddress(content); + } else { + const pair = keyring.createFromUri(content, { genesisHash, name: name.trim() }, 'sr25519'); + + setAddress(pair.address); + } if (scanned.name) { _onNameChange(scanned.name); } }, - [_onNameChange] + [_onNameChange, name] ); const _onSave = useCallback( (): void => { - if (!scanned || !isNameValid) { + if (!scanned || !isValid) { return; } - // TODO Handle where !isAddress - const { content, genesisHash } = scanned; + const { content, genesisHash, isAddress } = scanned; + + if (isAddress) { + keyring.addExternal(content, { genesisHash, name: name.trim() }); + } else { + keyring.addUri(content, password, { genesisHash, name: name.trim() }, 'sr25519'); + } keyring.addExternal(content, { genesisHash, name: name.trim() }); InputAddress.setLastValue('account', content); @@ -64,7 +91,7 @@ function QrModal ({ className = '', onClose, onStatusChange }: Props): React.Rea }); onClose(); }, - [isNameValid, name, onClose, onStatusChange, scanned, t] + [isValid, name, onClose, onStatusChange, password, scanned, t] ); return ( @@ -103,6 +130,13 @@ function QrModal ({ className = '', onClose, onStatusChange }: Props): React.Rea

{t('The local name for this account. Changing this does not affect your on-line identity, so this is only used to indicate the name of the account locally.')}

+ {!isAddress && ( + + )} ) : ( @@ -122,7 +156,7 @@ function QrModal ({ className = '', onClose, onStatusChange }: Props): React.Rea