Skip to content

Commit

Permalink
feat: open active account if the imported account is successful
Browse files Browse the repository at this point in the history
  • Loading branch information
kieranroneill committed Feb 12, 2024
1 parent beca6fe commit ad3d9eb
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 86 deletions.
8 changes: 2 additions & 6 deletions src/extension/features/accounts/thunks/saveNewAccountThunk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AsyncThunk, createAsyncThunk } from '@reduxjs/toolkit';
import { encode as encodeHex } from '@stablelib/hex';
import { sign } from 'tweetnacl';
import browser from 'webextension-polyfill';

// errors
Expand Down Expand Up @@ -35,12 +34,9 @@ const saveNewAccountThunk: AsyncThunk<
IAsyncThunkConfigWithRejectValue
>(
AccountsThunkEnum.SaveNewAccount,
async (
{ name, password, privateKey },
{ dispatch, getState, rejectWithValue }
) => {
async ({ name, password, privateKey }, { getState, rejectWithValue }) => {
const encodedPublicKey: string = encodeHex(
sign.keyPair.fromSecretKey(privateKey).publicKey
PrivateKeyService.extractPublicKeyFromPrivateKey(privateKey)
).toUpperCase();
const logger: ILogger = getState().system.logger;
let account: IAccount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import {
Location,
NavigateFunction,
useLocation,
useNavigate,
} from 'react-router-dom';

// components
import AssetAvatar from '@extension/components/AssetAvatar';
Expand All @@ -32,7 +38,7 @@ import PasswordInput, {
} from '@extension/components/PasswordInput';

// constants
import { DEFAULT_GAP } from '@extension/constants';
import { ACCOUNTS_ROUTE, DEFAULT_GAP } from '@extension/constants';

// enums
import { ErrorCodeEnum } from '@extension/enums';
Expand All @@ -52,7 +58,6 @@ import useAccountImportAssets from './hooks/useAccountImportAssets';

// selectors
import {
useSelectAccounts,
useSelectLogger,
useSelectPasswordLockPassword,
useSelectSelectedNetwork,
Expand All @@ -75,7 +80,6 @@ import type {
// utils
import convertPrivateKeyToAddress from '@extension/utils/convertPrivateKeyToAddress';
import ellipseAddress from '@extension/utils/ellipseAddress';
import isAccountKnown from '@extension/utils/isAccountKnown';
import decodePrivateKeyFromAccountImportSchema from './utils/decodePrivateKeyFromImportKeySchema';

interface IProps {
Expand All @@ -91,10 +95,11 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
}: IProps) => {
const { t } = useTranslation();
const dispatch: IAppThunkDispatch = useDispatch<IAppThunkDispatch>();
const location: Location = useLocation();
const navigate: NavigateFunction = useNavigate();
const passwordInputRef: MutableRefObject<HTMLInputElement | null> =
useRef<HTMLInputElement | null>(null);
// selectors
const accounts: IAccount[] = useSelectAccounts();
const logger: ILogger = useSelectLogger();
const network: INetwork | null = useSelectSelectedNetwork();
const passwordLockPassword: string | null = useSelectPasswordLockPassword();
Expand All @@ -119,10 +124,6 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
// states
const [address, setAddress] = useState<string | null>(null);
const [saving, setSaving] = useState<boolean>(false);
// misc
const isAccountAlreadyKnown: boolean = address
? isAccountKnown(accounts, address)
: false;
// handlers
const handleCancelClick = () => {
reset();
Expand Down Expand Up @@ -213,7 +214,7 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
addARC0200AssetHoldingsThunk({
accountId: account.id,
assets,
genesisHash: network?.genesisHash,
genesisHash: network.genesisHash,
})
).unwrap();
}
Expand All @@ -222,6 +223,15 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
case ErrorCodeEnum.InvalidPasswordError:
setPasswordError(t<string>('errors.inputs.invalidPassword'));

break;
case ErrorCodeEnum.PrivateKeyAlreadyExistsError:
logger.debug(
`${ScanQRCodeModalAccountImportContent.name}#${_functionName}: account already exists, carry on`
);

// clean up and close
handleOnComplete();

break;
default:
dispatch(
Expand All @@ -243,9 +253,6 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
return;
}

// clean up and close
handleOnComplete();

if (account) {
dispatch(
createNotification({
Expand All @@ -261,7 +268,23 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
type: 'success',
})
);

// if the page is on the account page, go to the new account
if (location.pathname.includes(ACCOUNTS_ROUTE)) {
navigate(
`${ACCOUNTS_ROUTE}/${AccountService.convertPublicKeyToAlgorandAddress(
account.publicKey
)}`,
{
preventScrollReset: true,
replace: true,
}
);
}
}

// clean up and close
handleOnComplete();
};
const handleKeyUpPasswordInput = async (
event: KeyboardEvent<HTMLInputElement>
Expand All @@ -279,57 +302,6 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
resetAccountImportAssets();
setSaving(false);
};
// renders
const renderFooter = () => {
// only show cancel button if the account has been added
if (isAccountAlreadyKnown) {
return (
<Button onClick={handleCancelClick} size="lg" variant="solid" w="full">
{t<string>('buttons.cancel')}
</Button>
);
}

return (
<VStack alignItems="flex-start" spacing={4} w="full">
{!settings.security.enablePasswordLock && !passwordLockPassword && (
<PasswordInput
error={passwordError}
hint={t<string>('captions.mustEnterPasswordToImportAccount')}
onChange={onPasswordChange}
onKeyUp={handleKeyUpPasswordInput}
inputRef={passwordInputRef}
value={password}
/>
)}

<HStack spacing={4} w="full">
{/*cancel button*/}
<Button
onClick={handleCancelClick}
size="lg"
variant="outline"
w="full"
>
{t<string>('buttons.cancel')}
</Button>

{/*import button*/}
{!isAccountAlreadyKnown && (
<Button
isLoading={saving}
onClick={handleImportClick}
size="lg"
variant="solid"
w="full"
>
{t<string>('buttons.import')}
</Button>
)}
</HStack>
</VStack>
);
};

useEffect(() => {
if (passwordInputRef.current) {
Expand Down Expand Up @@ -375,9 +347,6 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
end: 10,
start: 10,
})}
{...(isAccountAlreadyKnown && {
warningLabel: t<string>('captions.accountAlreadyAdded'),
})}
/>
)}
</VStack>
Expand Down Expand Up @@ -433,7 +402,43 @@ const ScanQRCodeModalAccountImportContent: FC<IProps> = ({
</ModalBody>

{/*footer*/}
<ModalFooter p={DEFAULT_GAP}>{renderFooter()}</ModalFooter>
<ModalFooter p={DEFAULT_GAP}>
<VStack alignItems="flex-start" spacing={4} w="full">
{!settings.security.enablePasswordLock && !passwordLockPassword && (
<PasswordInput
error={passwordError}
hint={t<string>('captions.mustEnterPasswordToImportAccount')}
onChange={onPasswordChange}
onKeyUp={handleKeyUpPasswordInput}
inputRef={passwordInputRef}
value={password}
/>
)}

<HStack spacing={4} w="full">
{/*cancel button*/}
<Button
onClick={handleCancelClick}
size="lg"
variant="outline"
w="full"
>
{t<string>('buttons.cancel')}
</Button>

{/*import button*/}
<Button
isLoading={loading || saving}
onClick={handleImportClick}
size="lg"
variant="solid"
w="full"
>
{t<string>('buttons.import')}
</Button>
</HStack>
</VStack>
</ModalFooter>
</>
);
};
Expand Down
42 changes: 29 additions & 13 deletions src/extension/services/PrivateKeyService/PrivateKeyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,31 @@ export default class PrivateKeyService {
this.storageManager = storageManager || new StorageManager();
}

/**
* public static functions
*/

/**
* Convenience that extracts the raw public key from a public key.
* @param {Uint8Array} privateKey - the raw private key.
* @returns {Uint8Array} the public key for the supplied private key.
* @throws {MalformedDataError} If the private key is the incorrect format (should have been created using
* {@link http://ed25519.cr.yp.to/ ed25519}).
*/
public static extractPublicKeyFromPrivateKey(
privateKey: Uint8Array
): Uint8Array {
let keyPair: SignKeyPair;

try {
keyPair = sign.keyPair.fromSecretKey(privateKey);

return keyPair.publicKey;
} catch (error) {
throw new MalformedDataError(error.message);
}
}

/**
* private functions
*/
Expand Down Expand Up @@ -328,7 +353,7 @@ export default class PrivateKeyService {
* @param {string} password - the password used to initialize the private key storage.
* @returns {IPrivateKey | null} the initialized private key item.
* @throws {InvalidPasswordError} If the password is invalid.
* @throws {MalformedDataError} If there the private key is the incorrect format (should have been created using
* @throws {MalformedDataError} iIf the private key is the incorrect format (should have been created using
* {@link http://ed25519.cr.yp.to/ ed25519}).
* @throws {EncryptionError} If there was a problem with the encryption or the private key is invalid.
*/
Expand All @@ -340,7 +365,6 @@ export default class PrivateKeyService {
const isPasswordValid: boolean = await this.verifyPassword(password);
let encodedPublicKey: string;
let encryptedPrivateKey: Uint8Array;
let keyPair: SignKeyPair;
let now: Date;
let passwordTag: IPasswordTag | null;
let privateKeyItem: IPrivateKey | null;
Expand All @@ -354,17 +378,9 @@ export default class PrivateKeyService {
throw new InvalidPasswordError();
}

try {
keyPair = sign.keyPair.fromSecretKey(privateKey);
} catch (error) {
this.logger?.debug(
`${PrivateKeyService.name}#${_functionName}(): ${error.message}`
);

throw new MalformedDataError(error.message);
}

encodedPublicKey = encodeHex(keyPair.publicKey);
encodedPublicKey = encodeHex(
PrivateKeyService.extractPublicKeyFromPrivateKey(privateKey)
);

this.logger?.debug(
`${PrivateKeyService.name}#${_functionName}(): encrypting private key for public key "${encodedPublicKey}"`
Expand Down
1 change: 1 addition & 0 deletions src/extension/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const translation: IResourceLanguage = {
importAccount: 'You are about to import the following account.',
importExistingAccount: `Import an existing account using you mnemonic seed phrase.`,
importExistingAccountComplete: `To finalize we will encrypt your account keys with your password and you will be able to start using this account.`,
importingAccount: 'Importing new account and adding assets.',
importRekeyedAccount: `Import an existing account that has been rekeyed. You will need the mnemonic seed phrase of the authorized account and the address of the rekeyed account.`,
initializingWalletConnect:
'Putting the final touches into your WalletConnect interface.',
Expand Down

0 comments on commit ad3d9eb

Please sign in to comment.