Merge pull request #1273 from nymtech/fix/wallet-store-ids-in-state

wallet: store ids in state and require password to switch account
This commit is contained in:
Jon Häggblad
2022-05-17 22:21:01 +02:00
committed by GitHub
20 changed files with 302 additions and 172 deletions
+1
View File
@@ -4,6 +4,7 @@
### Added
- wallet: require password to switch accounts
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
Generated
+1
View File
@@ -3178,6 +3178,7 @@ dependencies = [
"proxy-helpers",
"publicsuffix",
"rand 0.7.3",
"rocket",
"serde",
"socks5-requests",
"sqlx",
+1 -2
View File
@@ -43,13 +43,12 @@ fn main() {
mixnet::account::does_password_file_exist,
mixnet::account::get_balance,
mixnet::account::list_accounts,
mixnet::account::sign_in_decrypted_account,
mixnet::account::logout,
mixnet::account::remove_account_for_password,
mixnet::account::remove_password,
mixnet::account::show_mnemonic_for_account_in_password,
mixnet::account::sign_in_decrypted_account,
mixnet::account::sign_in_with_password,
mixnet::account::sign_in_with_password_and_account_id,
mixnet::account::switch_network,
mixnet::account::validate_mnemonic,
mixnet::admin::get_contract_settings,
@@ -4,7 +4,7 @@ use crate::error::BackendError;
use crate::network::Network as WalletNetwork;
use crate::network_config;
use crate::nymd_client;
use crate::state::State;
use crate::state::{State, WalletAccountIds};
use crate::wallet_storage::{self, DEFAULT_LOGIN_ID};
use bip39::{Language, Mnemonic};
@@ -229,6 +229,10 @@ async fn _connect_with_mnemonic(
};
// Register all the clients
{
let mut w_state = state.write().await;
w_state.logout();
}
for client in clients {
let network: WalletNetwork = client.network.into();
let mut w_state = state.write().await;
@@ -395,9 +399,9 @@ pub async fn sign_in_with_password(
}
fn extract_first_mnemonic(
stored_account: &wallet_storage::StoredLogin,
stored_login: &wallet_storage::StoredLogin,
) -> Result<Mnemonic, BackendError> {
let mnemonic = match stored_account {
let mnemonic = match stored_login {
wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(),
wallet_storage::StoredLogin::Multiple(ref accounts) => {
// Login using the first account in the list
@@ -413,6 +417,44 @@ fn extract_first_mnemonic(
Ok(mnemonic)
}
#[tauri::command]
pub async fn sign_in_with_password_and_account_id(
account_id: &str,
password: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, BackendError> {
log::info!("Signing in with password");
// Currently we only support a single, default, id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
let account_id = wallet_storage::AccountId::new(account_id.to_string());
let password = wallet_storage::UserPassword::new(password.to_string());
let stored_login = wallet_storage::load_existing_login(&login_id, &password)?;
let mnemonic = extract_mnemonic(&stored_login, &account_id)?;
let first_login_id_when_converting = login_id.into();
set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()).await?;
_connect_with_mnemonic(mnemonic, state).await
}
fn extract_mnemonic(
stored_login: &wallet_storage::StoredLogin,
account_id: &wallet_storage::AccountId,
) -> Result<Mnemonic, BackendError> {
let mnemonic = match stored_login {
wallet_storage::StoredLogin::Mnemonic(_) => {
return Err(BackendError::WalletNoSuchAccountIdInWalletLogin);
}
wallet_storage::StoredLogin::Multiple(ref accounts) => accounts
.get_account(account_id)
.ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?
.mnemonic()
.clone(),
};
Ok(mnemonic)
}
#[tauri::command]
pub fn remove_password() -> Result<(), BackendError> {
log::info!("Removing password");
@@ -476,11 +518,31 @@ async fn set_state_with_all_accounts(
.collect();
for account in &all_accounts {
log::trace!("account: {:?}", account);
log::trace!("account: {:?}", account.id());
}
let all_account_ids: Vec<WalletAccountIds> = all_accounts
.iter()
.map(|account| {
let mnemonic = account.mnemonic();
let addresses: HashMap<WalletNetwork, cosmrs::AccountId> = WalletNetwork::iter()
.map(|network| {
let config_network: Network = network.into();
(
network,
derive_address(mnemonic.clone(), config_network.bech32_prefix()).unwrap(),
)
})
.collect();
WalletAccountIds {
id: account.id().clone(),
addresses,
}
})
.collect();
let mut w_state = state.write().await;
w_state.set_all_accounts(all_accounts);
w_state.set_all_accounts(all_account_ids);
Ok(())
}
@@ -523,16 +585,13 @@ pub async fn list_accounts(
) -> Result<Vec<AccountEntry>, BackendError> {
log::trace!("Listing accounts");
let state = state.read().await;
let network: Network = state.current_network().into();
let prefix = network.bech32_prefix();
let network = state.current_network();
let all_accounts = state
.get_all_accounts()
.map(|account| AccountEntry {
id: account.id().to_string(),
address: derive_address(account.mnemonic().clone(), prefix)
.unwrap()
.to_string(),
id: account.id.to_string(),
address: account.addresses[&network].to_string(),
})
.map(|account| {
log::trace!("{:?}", account);
@@ -552,8 +611,16 @@ pub fn show_mnemonic_for_account_in_password(
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
let account_id = wallet_storage::AccountId::new(account_id);
let password = wallet_storage::UserPassword::new(password);
let stored_account = wallet_storage::load_existing_login(&login_id, &password)?;
let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?;
Ok(mnemonic.to_string())
}
fn _show_mnemonic_for_account_in_password(
login_id: &wallet_storage::LoginId,
account_id: &wallet_storage::AccountId,
password: &wallet_storage::UserPassword,
) -> Result<bip39::Mnemonic, BackendError> {
let stored_account = wallet_storage::load_existing_login(login_id, password)?;
let mnemonic = match stored_account {
wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(),
wallet_storage::StoredLogin::Multiple(ref accounts) => {
@@ -561,36 +628,13 @@ pub fn show_mnemonic_for_account_in_password(
log::debug!("{:?}", account);
}
accounts
.get_account(&account_id)
.get_account(account_id)
.ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?
.mnemonic()
.clone()
}
};
Ok(mnemonic.to_string())
}
#[tauri::command]
pub async fn sign_in_decrypted_account(
account_id: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, BackendError> {
log::info!("Signing in to already decrypted account: {account_id}");
let mnemonic = {
let state = state.read().await;
let account = &state
.get_all_accounts()
.find(|a| a.id().as_ref() == account_id)
.ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?;
account.mnemonic().clone()
};
{
state.write().await.logout();
}
_connect_with_mnemonic(mnemonic, state).await
Ok(mnemonic)
}
#[cfg(test)]
+10 -4
View File
@@ -1,6 +1,5 @@
use crate::error::BackendError;
use crate::network::Network;
use crate::wallet_storage::account_data::WalletAccount;
use crate::{config, network_config};
use strum::IntoEnumIterator;
@@ -49,7 +48,7 @@ pub struct State {
// All the accounts the we get from decrypting the wallet. We hold on to these for being able to
// switch accounts on-the-fly
all_accounts: Vec<WalletAccount>,
all_accounts: Vec<WalletAccountIds>,
/// Validators that have been fetched dynamically, probably during startup.
fetched_validators: config::OptionalValidators,
@@ -58,6 +57,13 @@ pub struct State {
validator_metadata: HashMap<Url, ValidatorMetadata>,
}
pub(crate) struct WalletAccountIds {
// The wallet account id
pub id: crate::wallet_storage::AccountId,
// The set of corresponding network identities derived from the mnemonic
pub addresses: HashMap<Network, cosmrs::AccountId>,
}
impl State {
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
self
@@ -117,11 +123,11 @@ impl State {
self.current_network
}
pub(crate) fn set_all_accounts(&mut self, all_accounts: Vec<WalletAccount>) {
pub(crate) fn set_all_accounts(&mut self, all_accounts: Vec<WalletAccountIds>) {
self.all_accounts = all_accounts
}
pub(crate) fn get_all_accounts(&self) -> impl Iterator<Item = &WalletAccount> {
pub(crate) fn get_all_accounts(&self) -> impl Iterator<Item = &WalletAccountIds> {
self.all_accounts.iter()
}
@@ -4,8 +4,16 @@ import { useClipboard } from 'use-clipboard-copy';
import { AccountsContext } from 'src/context';
import { AccountAvatar } from './AccountAvatar';
export const AccountItem = ({ name, address }: { name: string; address: string }) => {
const { selectedAccount, handleSelectAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext);
export const AccountItem = ({
name,
address,
onSelectAccount,
}: {
name: string;
address: string;
onSelectAccount: () => void;
}) => {
const { selectedAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext);
const { copy, copied } = useClipboard({ copiedTimeout: 1000 });
return (
<ListItem
@@ -13,7 +21,7 @@ export const AccountItem = ({ name, address }: { name: string; address: string }
disableGutters
sx={selectedAccount?.id === name ? { bgcolor: 'rgba(33, 208, 115, 0.1)' } : {}}
>
<ListItemButton disableRipple onClick={() => handleSelectAccount(name)}>
<ListItemButton disableRipple onClick={onSelectAccount}>
<ListItemAvatar sx={{ minWidth: 0, mr: 2 }}>
<AccountAvatar name={name} />
</ListItemAvatar>
@@ -1,9 +1,9 @@
import React, { useContext, useState } from 'react';
import { AccountsContext, AppContext } from 'src/context';
import { EditAccountModal } from './EditAccountModal';
import { AddAccountModal } from './AddAccountModal';
import { AccountsModal } from './AccountsModal';
import { MnemonicModal } from './MnemonicModal';
import { EditAccountModal } from './modals/EditAccountModal';
import { AddAccountModal } from './modals/AddAccountModal';
import { AccountsModal } from './modals/AccountsModal';
import { MnemonicModal } from './modals/MnemonicModal';
import { AccountOverview } from './AccountOverview';
import { MultiAccountHowTo } from './MultiAccountHowTo';
@@ -1,17 +1,36 @@
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material';
import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material';
import { AccountsContext } from 'src/context';
import { AccountItem } from './AccountItem';
import { AccountItem } from '../AccountItem';
import { ConfirmPasswordModal } from './ConfirmPasswordModal';
export const AccountsModal = ({ onClose }: { onClose?: () => void }) => {
const { accounts, dialogToDisplay, setDialogToDisplay } = useContext(AccountsContext);
export const AccountsModal = () => {
const { accounts, dialogToDisplay, setDialogToDisplay, setError, handleSelectAccount, selectedAccount } =
useContext(AccountsContext);
const [accountToSwitchTo, setAccountToSwitchTo] = useState<string>();
const handleClose = () => {
setDialogToDisplay(undefined);
onClose?.();
setError(undefined);
setAccountToSwitchTo(undefined);
};
if (accountToSwitchTo)
return (
<ConfirmPasswordModal
accountName={accountToSwitchTo}
onClose={() => {
handleClose();
setDialogToDisplay('Accounts');
}}
onConfirm={async (password) => {
const isSuccessful = await handleSelectAccount({ password, accountName: accountToSwitchTo });
if (isSuccessful) handleClose();
}}
/>
);
return (
<Dialog open={dialogToDisplay === 'Accounts'} onClose={handleClose} fullWidth hideBackdrop>
<DialogTitle>
@@ -27,7 +46,16 @@ export const AccountsModal = ({ onClose }: { onClose?: () => void }) => {
</DialogTitle>
<DialogContent sx={{ padding: 0 }}>
{accounts?.map(({ id, address }) => (
<AccountItem name={id} address={address} key={address} />
<AccountItem
name={id}
address={address}
key={address}
onSelectAccount={() => {
if (selectedAccount?.id !== id) {
setAccountToSwitchTo(id);
}
}}
/>
))}
</DialogContent>
<DialogActions sx={{ p: 3 }}>
@@ -2,24 +2,21 @@ import React, { useContext, useEffect, useState } from 'react';
import {
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
Stack,
TextField,
Typography,
} from '@mui/material';
import { ArrowBackSharp } from '@mui/icons-material';
import { useClipboard } from 'use-clipboard-copy';
import { createMnemonic, validateMnemonic } from 'src/requests';
import { AccountsContext } from 'src/context';
import { PasswordInput } from 'src/components';
import { Mnemonic } from '../Mnemonic';
import { Console } from 'src/utils/console';
import { match } from 'assert';
import { AccountsContext } from 'src/context';
import { ConfirmPassword, Mnemonic } from 'src/components';
import { MnemonicInput } from 'src/components/textfields';
const createAccountSteps = [
'Copy and save mnemonic for your new account',
@@ -66,27 +63,18 @@ const ImportMnemonic = ({
};
return (
<Box sx={{ mt: 1 }}>
<>
<DialogContent>
<Stack spacing={2} alignItems="center">
{error && (
<Typography variant="body1" sx={{ color: 'error.main', mb: 2 }}>
{error}
</Typography>
)}
<TextField
multiline
placeholder="Mnemonic to import"
rows={3}
value={value}
onChange={(e) => {
onChange(e.target.value);
setError(undefined);
}}
fullWidth
type="password"
/>
</Stack>
<Typography variant="body1" sx={{ color: 'error.main', my: 2 }}>
{error}
</Typography>
<MnemonicInput
mnemonic={value}
onUpdateMnemonic={(mnemon) => {
onChange(mnemon);
setError(undefined);
}}
/>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 0 }}>
<Button
@@ -100,7 +88,7 @@ const ImportMnemonic = ({
Next
</Button>
</DialogActions>
</Box>
</>
);
};
@@ -110,16 +98,16 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => {
const nameValidation = /^([a-zA-Z0-9\s]){1,20}$/;
const handleNext = (value: string) => {
if (!nameValidation.test(value))
[setError('Account name must contain only letters and numbers and be between 1 and 20 characters')];
else onNext(value);
const handleNext = (accountName: string) => {
if (!nameValidation.test(accountName)) {
setError('Account name must contain only letters and numbers and be between 1 and 20 characters');
} else onNext(value);
};
return (
<Box sx={{ mt: 1 }}>
<>
<DialogContent>
<Typography variant="body1" sx={{ color: 'error.main', mb: 2 }}>
<Typography variant="body1" sx={{ color: 'error.main', my: 2 }}>
{error}
</Typography>
<TextField
@@ -144,43 +132,7 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => {
Next
</Button>
</DialogActions>
</Box>
);
};
const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => {
const [value, setValue] = useState('');
const { isLoading, error } = useContext(AccountsContext);
return (
<Box sx={{ mt: 1 }}>
<DialogContent>
{error && (
<Typography variant="body1" sx={{ color: 'error.main', mb: 2 }}>
{error}
</Typography>
)}
<PasswordInput
password={value}
onUpdatePassword={(pswrd) => setValue(pswrd)}
label="Confirm password"
autoFocus
/>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 0 }}>
<Button
disabled={!value.length || isLoading}
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => onConfirm(value)}
endIcon={isLoading && <CircularProgress size={20} />}
>
Add account
</Button>
</DialogActions>
</Box>
</>
);
};
@@ -191,24 +143,25 @@ export const AddAccountModal = () => {
accountName: '',
});
const { dialogToDisplay, setDialogToDisplay, handleAddAccount, setError } = useContext(AccountsContext);
const { dialogToDisplay, setDialogToDisplay, handleAddAccount, setError, isLoading, error } =
useContext(AccountsContext);
const generateMnemonic = async () => {
const mnemon = await createMnemonic();
setData((d) => ({ ...d, mnemonic: mnemon }));
};
const handleClose = () => {
setDialogToDisplay('Accounts');
resetState();
};
const resetState = () => {
setData({ mnemonic: '', accountName: '' });
setStep(0);
setError(undefined);
};
const handleClose = () => {
setDialogToDisplay('Accounts');
resetState();
};
useEffect(() => {
if (dialogToDisplay === 'Add') generateMnemonic();
if (dialogToDisplay === 'Accounts') resetState();
@@ -260,6 +213,7 @@ export const AddAccountModal = () => {
case 2:
return (
<ConfirmPassword
buttonTitle="Add account"
onConfirm={async (password) => {
if (data.accountName && data.mnemonic) {
try {
@@ -271,6 +225,8 @@ export const AddAccountModal = () => {
}
}
}}
isLoading={isLoading}
error={error}
/>
);
default:
@@ -0,0 +1,34 @@
import React, { useContext } from 'react';
import { Box, Dialog, DialogTitle, IconButton, Typography } from '@mui/material';
import { ArrowBack } from '@mui/icons-material';
import { ConfirmPassword } from 'src/components/ConfirmPassword';
import { AccountsContext } from 'src/context';
export const ConfirmPasswordModal = ({
accountName,
onClose,
onConfirm,
}: {
accountName?: string;
onClose: () => void;
onConfirm: (password: string) => Promise<void>;
}) => {
const { isLoading, error } = useContext(AccountsContext);
return (
<Dialog open={Boolean(accountName)} onClose={onClose} fullWidth hideBackdrop>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Switch account</Typography>
<IconButton onClick={onClose}>
<ArrowBack />
</IconButton>
</Box>
<Typography variant="body1" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
</DialogTitle>
<ConfirmPassword onConfirm={onConfirm} error={error} isLoading={isLoading} buttonTitle="Switch account" />
</Dialog>
);
};
@@ -13,8 +13,7 @@ import {
import { ArrowBackSharp } from '@mui/icons-material';
import { AccountsContext } from 'src/context';
import { useClipboard } from 'use-clipboard-copy';
import { Mnemonic } from '../Mnemonic';
import { PasswordInput } from '../textfields';
import { PasswordInput, Mnemonic } from 'src/components';
export const MnemonicModal = () => {
const [password, setPassword] = useState('');
@@ -26,7 +25,7 @@ export const MnemonicModal = () => {
setDialogToDisplay,
accountMnemonic,
setAccountMnemonic,
handleGetAcccountMnemonic,
handleGetAccountMnemonic,
error,
setError,
isLoading,
@@ -86,7 +85,7 @@ export const MnemonicModal = () => {
onClick={async () => {
if (accountMnemonic?.accountName) {
setError(undefined);
await handleGetAcccountMnemonic({ password, accountName: accountMnemonic?.accountName });
await handleGetAccountMnemonic({ password, accountName: accountMnemonic?.accountName });
}
}}
endIcon={isLoading && <CircularProgress size={20} />}
@@ -0,0 +1,47 @@
import React, { useState } from 'react';
import { Button, CircularProgress, DialogActions, DialogContent, Typography } from '@mui/material';
import { PasswordInput } from './textfields';
export const ConfirmPassword = ({
error,
isLoading,
onConfirm,
buttonTitle,
}: {
error?: string;
isLoading?: boolean;
buttonTitle: string;
onConfirm: (password: string) => void;
}) => {
const [value, setValue] = useState('');
return (
<>
<DialogContent>
<Typography variant="body1" sx={{ color: 'error.main', my: 2 }}>
{error}
</Typography>
<PasswordInput
password={value}
onUpdatePassword={(pswrd) => setValue(pswrd)}
placeholder="Confirm password"
autoFocus
/>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 0 }}>
<Button
disabled={!value.length || isLoading}
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => onConfirm(value)}
endIcon={isLoading && <CircularProgress size={20} />}
>
{buttonTitle}
</Button>
</DialogActions>
</>
);
};
+16 -14
View File
@@ -1,19 +1,21 @@
export * from './ErrorFallback';
export * from './CopyToClipboard';
export * from './NymCard';
export * from './Nav';
export * from './NodeTypeSelector';
export * from './RequestStatus';
export * from './NoClientError';
export * from './SuccessResponse';
export * from './TransactionDetails';
export * from './NymLogo';
export * from './Fee';
export * from './AppBar';
export * from './NetworkSelector';
export * from './ClientAddress';
export * from './ConfirmPassword';
export * from './CopyToClipboard';
export * from './ErrorFallback';
export * from './Fee';
export * from './InfoToolTip';
export * from './LoadingPage';
export * from './Mnemonic';
export * from './Nav';
export * from './NetworkSelector';
export * from './NoClientError';
export * from './NodeTypeSelector';
export * from './NymCard';
export * from './NymLogo';
export * from './RequestStatus';
export * from './SuccessResponse';
export * from './textfields';
export * from './Title';
export * from './TokenPoolSelector';
export * from './LoadingPage';
export * from './textfields';
export * from './TransactionDetails';
+4 -2
View File
@@ -36,10 +36,11 @@ export const MnemonicInput: React.FC<{
export const PasswordInput: React.FC<{
password: string;
error?: string;
label: string;
label?: string;
placeholder?: string;
autoFocus?: boolean;
onUpdatePassword: (password: string) => void;
}> = ({ password, label, error, autoFocus, onUpdatePassword }) => {
}> = ({ password, label, placeholder, error, autoFocus, onUpdatePassword }) => {
const [showPassword, setShowPassword] = useState(false);
return (
@@ -47,6 +48,7 @@ export const PasswordInput: React.FC<{
<Box>
<TextField
label={label}
placeholder={placeholder}
fullWidth
value={password}
onChange={(e) => onUpdatePassword(e.target.value)}
+11 -7
View File
@@ -16,11 +16,11 @@ type TAccounts = {
setAccountMnemonic: Dispatch<SetStateAction<TAccountMnemonic>>;
handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void;
setDialogToDisplay: (dialog?: TAccountsDialog) => void;
handleSelectAccount: (accountId: string) => void;
handleSelectAccount: (data: { accountName: string; password: string }) => Promise<boolean>;
handleAccountToEdit: (accountId: string) => void;
handleEditAccount: (account: AccountEntry) => void;
handleImportAccount: (account: AccountEntry) => void;
handleGetAcccountMnemonic: (data: { password: string; accountName: string }) => void;
handleGetAccountMnemonic: (data: { password: string; accountName: string }) => void;
};
export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import' | 'Mnemonic';
@@ -75,15 +75,19 @@ export const AccountsProvider: React.FC = ({ children }) => {
const handleAccountToEdit = (accountName: string) =>
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
const handleSelectAccount = async (accountName: string) => {
if (accountName !== selectedAccount?.id) {
await onAccountChange(accountName);
const handleSelectAccount = async ({ accountName, password }: { accountName: string; password: string }) => {
try {
await onAccountChange({ accountId: accountName, password });
const match = accounts?.find((acc) => acc.id === accountName);
setSelectedAccount(match);
return true;
} catch (e) {
setError('Error switching account. Please check your password');
return false;
}
};
const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => {
const handleGetAccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => {
try {
setIsLoading(true);
const mnemonic = await showMnemonicForAccount({ password, accountName });
@@ -124,7 +128,7 @@ export const AccountsProvider: React.FC = ({ children }) => {
handleAccountToEdit,
handleSelectAccount,
handleImportAccount,
handleGetAcccountMnemonic,
handleGetAccountMnemonic,
}),
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
)}
+4 -4
View File
@@ -55,7 +55,7 @@ type TAppContext = {
handleShowTerminal: () => void;
signInWithPassword: (password: string) => void;
logOut: () => void;
onAccountChange: (accountId: string) => void;
onAccountChange: ({ accountId, password }: { accountId: string; password: string }) => void;
};
export const AppContext = createContext({} as TAppContext);
@@ -165,15 +165,15 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
enqueueSnackbar('Successfully logged out', { variant: 'success' });
};
const onAccountChange = async (accountId: string) => {
const onAccountChange = async ({ accountId, password }: { accountId: string; password: string }) => {
if (network) {
setIsLoading(true);
try {
await switchAccount(accountId);
await switchAccount({ accountId, password });
await loadAccount(network);
enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true });
} catch (e) {
enqueueSnackbar(`Error swtiching account: ${e}`, { variant: 'error' });
throw new Error(`Error swtiching account: ${e}`);
} finally {
setIsLoading(false);
}
+6 -7
View File
@@ -36,14 +36,13 @@ export const MockAccountsProvider: React.FC = ({ children }) => {
const handleAccountToEdit = (accountName: string) =>
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
const handleSelectAccount = async (accountName: string) => {
if (accountName !== selectedAccount?.id) {
const match = accounts?.find((acc) => acc.id === accountName);
setSelectedAccount(match);
}
const handleSelectAccount = async ({ accountName }: { accountName: string; password: string }) => {
const match = accounts?.find((acc) => acc.id === accountName);
setSelectedAccount(match);
return true;
};
const handleGetAcccountMnemonic = async ({ accountName }: { password: string; accountName: string }) => {
const handleGetAccountMnemonic = async ({ accountName }: { password: string; accountName: string }) => {
try {
setIsLoading(true);
const mnemonic = 'test mnemonic';
@@ -73,7 +72,7 @@ export const MockAccountsProvider: React.FC = ({ children }) => {
handleAccountToEdit,
handleSelectAccount,
handleImportAccount,
handleGetAcccountMnemonic,
handleGetAccountMnemonic,
}),
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
)}
+2 -2
View File
@@ -62,6 +62,6 @@ export const showMnemonicForAccount = async ({ password, accountName }: { passwo
return res;
};
export const switchAccount = async (accountId: string) => {
await invoke('sign_in_decrypted_account', { accountId });
export const switchAccount = async ({ accountId, password }: { accountId: string; password: string }) => {
await invoke('sign_in_with_password_and_account_id', { accountId, password });
};